Skip to content

Commit 849cd28

Browse files
mconcattac0turtle
andauthored
IAVL Iterator (#440)
* move traverse into CPS * add iterator * add ascending * fix iter.valid * fix test * gofmt * simplify logicl, add comments * rm using iterator inside iavl * add detailed comments * add comments per review * modify to closure independent * apply review, add docs, separate iterator.go * fix lint * fix fix lint * add more docs Co-authored-by: Marko <[email protected]>
1 parent 209a946 commit 849cd28

File tree

4 files changed

+238
-57
lines changed

4 files changed

+238
-57
lines changed

immutable_tree.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ func (t *ImmutableTree) IterateRange(start, end []byte, ascending bool, fn func(
181181
if t.root == nil {
182182
return false
183183
}
184-
return t.root.traverseInRange(t, start, end, ascending, false, 0, false, func(node *Node, _ uint8) bool {
184+
return t.root.traverseInRange(t, start, end, ascending, false, false, func(node *Node) bool {
185185
if node.height == 0 {
186186
return fn(node.key, node.value)
187187
}
@@ -196,7 +196,7 @@ func (t *ImmutableTree) IterateRangeInclusive(start, end []byte, ascending bool,
196196
if t.root == nil {
197197
return false
198198
}
199-
return t.root.traverseInRange(t, start, end, ascending, true, 0, false, func(node *Node, _ uint8) bool {
199+
return t.root.traverseInRange(t, start, end, ascending, true, false, func(node *Node) bool {
200200
if node.height == 0 {
201201
return fn(node.key, node.value, node.version)
202202
}

iterator.go

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
package iavl
2+
3+
// NOTE: This file favors int64 as opposed to int for size/counts.
4+
// The Tree on the other hand favors int. This is intentional.
5+
6+
import (
7+
"bytes"
8+
9+
dbm "github.com/tendermint/tm-db"
10+
)
11+
12+
type traversal struct {
13+
tree *ImmutableTree
14+
start, end []byte // iteration domain
15+
ascending bool // ascending traversal
16+
inclusive bool // end key inclusiveness
17+
post bool // postorder traversal
18+
delayedNodes *delayedNodes // delayed nodes to be traversed
19+
}
20+
21+
func (node *Node) newTraversal(tree *ImmutableTree, start, end []byte, ascending bool, inclusive bool, post bool) *traversal {
22+
return &traversal{
23+
tree: tree,
24+
start: start,
25+
end: end,
26+
ascending: ascending,
27+
inclusive: inclusive,
28+
post: post,
29+
delayedNodes: &delayedNodes{{node, true}}, // set initial traverse to the node
30+
}
31+
}
32+
33+
// delayedNode represents the delayed iteration on the nodes.
34+
// When delayed is set to true, the delayedNode should be expanded, and their
35+
// children should be traversed. When delayed is set to false, the delayedNode is
36+
// already have expanded, and it could be immediately returned.
37+
type delayedNode struct {
38+
node *Node
39+
delayed bool
40+
}
41+
42+
type delayedNodes []delayedNode
43+
44+
func (nodes *delayedNodes) pop() (*Node, bool) {
45+
node := (*nodes)[len(*nodes)-1]
46+
*nodes = (*nodes)[:len(*nodes)-1]
47+
return node.node, node.delayed
48+
}
49+
50+
func (nodes *delayedNodes) push(node *Node, delayed bool) {
51+
*nodes = append(*nodes, delayedNode{node, delayed})
52+
}
53+
54+
func (nodes *delayedNodes) length() int {
55+
return len(*nodes)
56+
}
57+
58+
// `traversal` returns the delayed execution of recursive traversal on a tree.
59+
//
60+
// `traversal` will traverse the tree in a depth-first manner. To handle locating
61+
// the next element, and to handle unwinding, the traversal maintains its future
62+
// iteration under `delayedNodes`. At each call of `next()`, it will retrieve the
63+
// next element from the `delayedNodes` and acts accordingly. The `next()` itself
64+
// defines how to unwind the delayed nodes stack. The caller can either call the
65+
// next traversal to proceed, or simply discard the `traversal` struct to stop iteration.
66+
//
67+
// At the each step of `next`, the `delayedNodes` can have one of the three states:
68+
// 1. It has length of 0, meaning that their is no more traversable nodes.
69+
// 2. It has length of 1, meaning that the traverse is being started from the initial node.
70+
// 3. It has length of 2>=, meaning that there are delayed nodes to be traversed.
71+
//
72+
// When the `delayedNodes` are not empty, `next` retrieves the first `delayedNode` and initially check:
73+
// 1. If it is not an delayed node (node.delayed == false) it immediately returns it.
74+
//
75+
// A. If the `node` is a branch node:
76+
// 1. If the traversal is postorder, then append the current node to the t.delayedNodes,
77+
// with `delayed` set to false. This makes the current node returned *after* all the children
78+
// are traversed, without being expanded.
79+
// 2. Append the traversable children nodes into the `delayedNodes`, with `delayed` set to true. This
80+
// makes the children nodes to be traversed, and expanded with their respective children.
81+
// 3. If the traversal is preorder, (with the children to be traversed already pushed to the
82+
// `delayedNodes`), returns the current node.
83+
// 4. Call `traversal.next()` to further traverse through the `delayedNodes`.
84+
//
85+
// B. If the `node` is a leaf node, it will be returned without expand, by the following process:
86+
// 1. If the traversal is postorder, the current node will be append to the `delayedNodes` with `delayed`
87+
// set to false, and immediately returned at the subsequent call of `traversal.next()` at the last line.
88+
// 2. If the traversal is preorder, the current node will be returned.
89+
func (t *traversal) next() *Node {
90+
// End of traversal.
91+
if t.delayedNodes.length() == 0 {
92+
return nil
93+
}
94+
95+
node, delayed := t.delayedNodes.pop()
96+
97+
// Already expanded, immediately return.
98+
if !delayed || node == nil {
99+
return node
100+
}
101+
102+
afterStart := t.start == nil || bytes.Compare(t.start, node.key) < 0
103+
startOrAfter := afterStart || bytes.Equal(t.start, node.key)
104+
beforeEnd := t.end == nil || bytes.Compare(node.key, t.end) < 0
105+
if t.inclusive {
106+
beforeEnd = beforeEnd || bytes.Equal(node.key, t.end)
107+
}
108+
109+
// case of postorder. A-1 and B-1
110+
// Recursively process left sub-tree, then right-subtree, then node itself.
111+
if t.post && (!node.isLeaf() || (startOrAfter && beforeEnd)) {
112+
t.delayedNodes.push(node, false)
113+
}
114+
115+
// case of branch node, traversing children. A-2.
116+
if !node.isLeaf() {
117+
// if node is a branch node and the order is ascending,
118+
// We traverse through the left subtree, then the right subtree.
119+
if t.ascending {
120+
if beforeEnd {
121+
// push the delayed traversal for the right nodes,
122+
t.delayedNodes.push(node.getRightNode(t.tree), true)
123+
}
124+
if afterStart {
125+
// push the delayed traversal for the left nodes,
126+
t.delayedNodes.push(node.getLeftNode(t.tree), true)
127+
}
128+
} else {
129+
// if node is a branch node and the order is not ascending
130+
// We traverse through the right subtree, then the left subtree.
131+
if afterStart {
132+
// push the delayed traversal for the left nodes,
133+
t.delayedNodes.push(node.getLeftNode(t.tree), true)
134+
}
135+
if beforeEnd {
136+
// push the delayed traversal for the right nodes,
137+
t.delayedNodes.push(node.getRightNode(t.tree), true)
138+
}
139+
}
140+
}
141+
142+
// case of preorder traversal. A-3 and B-2.
143+
// Process root then (recursively) processing left child, then process right child
144+
if !t.post && (!node.isLeaf() || (startOrAfter && beforeEnd)) {
145+
return node
146+
}
147+
148+
// Keep traversing and expanding the remaning delayed nodes. A-4.
149+
return t.next()
150+
}
151+
152+
// Iterator is a dbm.Iterator for ImmutableTree
153+
type Iterator struct {
154+
start, end []byte
155+
156+
key, value []byte
157+
158+
valid bool
159+
160+
t *traversal
161+
}
162+
163+
func (t *ImmutableTree) Iterator(start, end []byte, ascending bool) *Iterator {
164+
iter := &Iterator{
165+
start: start,
166+
end: end,
167+
valid: true,
168+
t: t.root.newTraversal(t, start, end, ascending, false, false),
169+
}
170+
171+
iter.Next()
172+
return iter
173+
}
174+
175+
var _ dbm.Iterator = &Iterator{}
176+
177+
// Domain implements dbm.Iterator.
178+
func (iter *Iterator) Domain() ([]byte, []byte) {
179+
return iter.start, iter.end
180+
}
181+
182+
// Valid implements dbm.Iterator.
183+
func (iter *Iterator) Valid() bool {
184+
return iter.valid
185+
}
186+
187+
// Key implements dbm.Iterator
188+
func (iter *Iterator) Key() []byte {
189+
return iter.key
190+
}
191+
192+
// Value implements dbm.Iterator
193+
func (iter *Iterator) Value() []byte {
194+
return iter.value
195+
}
196+
197+
// Next implements dbm.Iterator
198+
func (iter *Iterator) Next() {
199+
if iter.t == nil {
200+
return
201+
}
202+
203+
node := iter.t.next()
204+
if node == nil {
205+
iter.t = nil
206+
iter.valid = false
207+
return
208+
}
209+
210+
if node.height == 0 {
211+
iter.key, iter.value = node.key, node.value
212+
return
213+
}
214+
215+
iter.Next()
216+
}
217+
218+
// Close implements dbm.Iterator
219+
func (iter *Iterator) Close() error {
220+
iter.t = nil
221+
iter.valid = false
222+
return nil
223+
}
224+
225+
// Error implements dbm.Iterator
226+
func (iter *Iterator) Error() error {
227+
return nil
228+
}

node.go

Lines changed: 6 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -440,74 +440,27 @@ func (node *Node) calcBalance(t *ImmutableTree) int {
440440

441441
// traverse is a wrapper over traverseInRange when we want the whole tree
442442
func (node *Node) traverse(t *ImmutableTree, ascending bool, cb func(*Node) bool) bool {
443-
return node.traverseInRange(t, nil, nil, ascending, false, 0, false, func(node *Node, depth uint8) bool {
443+
return node.traverseInRange(t, nil, nil, ascending, false, false, func(node *Node) bool {
444444
return cb(node)
445445
})
446446
}
447447

448448
// traversePost is a wrapper over traverseInRange when we want the whole tree post-order
449449
func (node *Node) traversePost(t *ImmutableTree, ascending bool, cb func(*Node) bool) bool {
450-
return node.traverseInRange(t, nil, nil, ascending, false, 0, true, func(node *Node, depth uint8) bool {
450+
return node.traverseInRange(t, nil, nil, ascending, false, true, func(node *Node) bool {
451451
return cb(node)
452452
})
453453
}
454454

455-
func (node *Node) traverseInRange(t *ImmutableTree, start, end []byte, ascending bool, inclusive bool, depth uint8, post bool, cb func(*Node, uint8) bool) bool {
456-
if node == nil {
457-
return false
458-
}
459-
afterStart := start == nil || bytes.Compare(start, node.key) < 0
460-
startOrAfter := start == nil || bytes.Compare(start, node.key) <= 0
461-
beforeEnd := end == nil || bytes.Compare(node.key, end) < 0
462-
if inclusive {
463-
beforeEnd = end == nil || bytes.Compare(node.key, end) <= 0
464-
}
465-
466-
// Run callback per inner/leaf node.
455+
func (node *Node) traverseInRange(tree *ImmutableTree, start, end []byte, ascending bool, inclusive bool, post bool, cb func(*Node) bool) bool {
467456
stop := false
468-
if !post && (!node.isLeaf() || (startOrAfter && beforeEnd)) {
469-
stop = cb(node, depth)
457+
t := node.newTraversal(tree, start, end, ascending, inclusive, post)
458+
for node2 := t.next(); node2 != nil; node2 = t.next() {
459+
stop = cb(node2)
470460
if stop {
471461
return stop
472462
}
473463
}
474-
475-
if !node.isLeaf() {
476-
if ascending {
477-
// check lower nodes, then higher
478-
if afterStart {
479-
stop = node.getLeftNode(t).traverseInRange(t, start, end, ascending, inclusive, depth+1, post, cb)
480-
}
481-
if stop {
482-
return stop
483-
}
484-
if beforeEnd {
485-
stop = node.getRightNode(t).traverseInRange(t, start, end, ascending, inclusive, depth+1, post, cb)
486-
}
487-
} else {
488-
// check the higher nodes first
489-
if beforeEnd {
490-
stop = node.getRightNode(t).traverseInRange(t, start, end, ascending, inclusive, depth+1, post, cb)
491-
}
492-
if stop {
493-
return stop
494-
}
495-
if afterStart {
496-
stop = node.getLeftNode(t).traverseInRange(t, start, end, ascending, inclusive, depth+1, post, cb)
497-
}
498-
}
499-
}
500-
if stop {
501-
return stop
502-
}
503-
504-
if post && (!node.isLeaf() || (startOrAfter && beforeEnd)) {
505-
stop = cb(node, depth)
506-
if stop {
507-
return stop
508-
}
509-
}
510-
511464
return stop
512465
}
513466

proof_range.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,8 +433,8 @@ func (t *ImmutableTree) getRangeProof(keyStart, keyEnd []byte, limit int) (proof
433433
var leafCount = 1 // from left above.
434434
var pathCount = 0
435435

436-
t.root.traverseInRange(t, afterLeft, nil, true, false, 0, false,
437-
func(node *Node, depth uint8) (stop bool) {
436+
t.root.traverseInRange(t, afterLeft, nil, true, false, false,
437+
func(node *Node) (stop bool) {
438438

439439
// Track when we diverge from path, or when we've exhausted path,
440440
// since the first allPathToLeafs shouldn't include it.

0 commit comments

Comments
 (0)