Skip to content

Commit 239b2ee

Browse files
authored
implement database iterator functionality (#326)
add low-level custom iterator for rosedb
1 parent 7a43753 commit 239b2ee

File tree

7 files changed

+703
-2
lines changed

7 files changed

+703
-2
lines changed

examples/iterate/main.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
)
99

1010
// this file shows how to use the iterate operations of rosedb
11-
// you can use Ascend, Descend(and some other similar methods) to iterate all keys and values in order.
1211
func main() {
1312
// specify the options
1413
options := rosedb.DefaultOptions
@@ -28,12 +27,19 @@ func main() {
2827
_ = db.Close()
2928
}()
3029

30+
// prepare sample data
3131
_ = db.Put([]byte("key13"), []byte("value13"))
3232
_ = db.Put([]byte("key11"), []byte("value11"))
3333
_ = db.Put([]byte("key35"), []byte("value35"))
3434
_ = db.Put([]byte("key27"), []byte("value27"))
3535
_ = db.Put([]byte("key41"), []byte("value41"))
3636

37+
dbIteratorExample(db)
38+
customIteratorExample(db)
39+
}
40+
41+
// dbIteratorExample demonstrates the built-in database iterator methods
42+
func dbIteratorExample(db *rosedb.DB) {
3743
// iterate all keys in order
3844
db.AscendKeys(nil, true, func(k []byte) (bool, error) {
3945
fmt.Println("key = ", string(k))
@@ -57,10 +63,43 @@ func main() {
5763
fmt.Printf("key = %s, value = %s\n", string(k), string(v))
5864
return true, nil
5965
})
60-
6166
// you can also use some other similar methods to iterate the data.
6267
// db.AscendRange()
6368
// db.AscendGreaterOrEqual()
6469
// db.DescendRange()
6570
// db.DescendLessOrEqual()
6671
}
72+
73+
// customIteratorExample demonstrates how to use the low-level iterator API
74+
func customIteratorExample(db *rosedb.DB) {
75+
// 1: Using iterator with ContinueOnError = true
76+
iterOpts := rosedb.DefaultIteratorOptions
77+
iterOpts.ContinueOnError = true
78+
iter1 := db.NewIterator(iterOpts)
79+
defer iter1.Close()
80+
81+
for iter1.Rewind(); iter1.Valid(); iter1.Next() {
82+
item := iter1.Item()
83+
if item != nil {
84+
fmt.Printf("key = %s, value = %s\n", string(item.Key), string(item.Value))
85+
}
86+
}
87+
if err := iter1.Err(); err != nil {
88+
fmt.Printf("Iterator encountered errors but continued: %v\n", err)
89+
}
90+
91+
// 2: Using iterator with ContinueOnError = false
92+
iterOpts.ContinueOnError = false
93+
iter2 := db.NewIterator(iterOpts)
94+
defer iter2.Close()
95+
96+
for iter2.Rewind(); iter2.Valid(); iter2.Next() {
97+
item := iter2.Item()
98+
if item != nil {
99+
fmt.Printf("key = %s, value = %s\n", string(item.Key), string(item.Value))
100+
}
101+
}
102+
if err := iter2.Err(); err != nil {
103+
fmt.Printf("Iterator stopped due to error: %v\n", err)
104+
}
105+
}

index/btree.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,126 @@ func (mt *MemoryBTree) DescendLessOrEqual(key []byte, handleFn func(key []byte,
147147
return cont
148148
})
149149
}
150+
151+
func (mt *MemoryBTree) Iterator(reverse bool) IndexIterator {
152+
if mt.tree == nil {
153+
return nil
154+
}
155+
mt.lock.RLock()
156+
defer mt.lock.RUnlock()
157+
158+
return newMemoryBTreeIterator(mt.tree, reverse)
159+
}
160+
161+
// memoryBTreeIterator represents a B-tree index iterator implementation
162+
type memoryBTreeIterator struct {
163+
tree *btree.BTree // underlying B-tree implementation
164+
reverse bool // indicates whether to traverse in descending order
165+
current *item // current element being traversed
166+
valid bool // indicates if the iterator is valid
167+
}
168+
169+
func newMemoryBTreeIterator(tree *btree.BTree, reverse bool) *memoryBTreeIterator {
170+
var current *item
171+
var valid bool
172+
if tree.Len() > 0 {
173+
if reverse {
174+
current = tree.Max().(*item)
175+
} else {
176+
current = tree.Min().(*item)
177+
}
178+
valid = true
179+
}
180+
return &memoryBTreeIterator{
181+
tree: tree.Clone(),
182+
reverse: reverse,
183+
current: current,
184+
valid: valid,
185+
}
186+
}
187+
188+
func (it *memoryBTreeIterator) Rewind() {
189+
if it.tree == nil || it.tree.Len() == 0 {
190+
return
191+
}
192+
if it.reverse {
193+
it.current = it.tree.Max().(*item)
194+
} else {
195+
it.current = it.tree.Min().(*item)
196+
}
197+
}
198+
199+
func (it *memoryBTreeIterator) Seek(key []byte) {
200+
if it.tree == nil || !it.valid {
201+
return
202+
}
203+
seekItem := &item{key: key}
204+
it.valid = false
205+
if it.reverse {
206+
it.tree.DescendLessOrEqual(seekItem, func(i btree.Item) bool {
207+
it.current = i.(*item)
208+
it.valid = true
209+
return false
210+
})
211+
} else {
212+
it.tree.AscendGreaterOrEqual(seekItem, func(i btree.Item) bool {
213+
it.current = i.(*item)
214+
it.valid = true
215+
return false
216+
})
217+
}
218+
}
219+
220+
func (it *memoryBTreeIterator) Next() {
221+
if it.tree == nil || !it.valid {
222+
return
223+
}
224+
it.valid = false
225+
if it.reverse {
226+
it.tree.DescendLessOrEqual(it.current, func(i btree.Item) bool {
227+
if !i.(*item).Less(it.current) {
228+
return true
229+
}
230+
it.current = i.(*item)
231+
it.valid = true
232+
return false
233+
})
234+
} else {
235+
it.tree.AscendGreaterOrEqual(it.current, func(i btree.Item) bool {
236+
if !it.current.Less(i.(*item)) {
237+
return true
238+
}
239+
it.current = i.(*item)
240+
it.valid = true
241+
return false
242+
})
243+
}
244+
if !it.valid {
245+
it.current = nil
246+
}
247+
}
248+
249+
func (it *memoryBTreeIterator) Valid() bool {
250+
return it.valid
251+
}
252+
253+
func (it *memoryBTreeIterator) Key() []byte {
254+
if !it.valid {
255+
return nil
256+
}
257+
return it.current.key
258+
}
259+
260+
func (it *memoryBTreeIterator) Value() *wal.ChunkPosition {
261+
if !it.valid {
262+
return nil
263+
}
264+
return it.current.pos
265+
}
266+
267+
func (it *memoryBTreeIterator) Close() {
268+
it.tree.Clear(true)
269+
it.tree = nil
270+
it.current = nil
271+
it.valid = false
272+
}

index/btree_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package index
33
import (
44
"bytes"
55
"fmt"
6+
"github.com/stretchr/testify/assert"
67
"testing"
78

89
"github.com/rosedblabs/wal"
@@ -193,3 +194,93 @@ func TestMemoryBTree_AscendGreaterOrEqual_DescendLessOrEqual(t *testing.T) {
193194
return true, nil
194195
})
195196
}
197+
198+
func TestMemoryBTree_Iterator(t *testing.T) {
199+
mt := newBTree()
200+
// Test iterator for empty tree
201+
it1 := mt.Iterator(false)
202+
assert.Equal(t, false, it1.Valid())
203+
204+
// Build test data
205+
testData := map[string]*wal.ChunkPosition{
206+
"acee": {SegmentId: 1, BlockNumber: 2, ChunkOffset: 3, ChunkSize: 100},
207+
"bbcd": {SegmentId: 2, BlockNumber: 3, ChunkOffset: 4, ChunkSize: 200},
208+
"code": {SegmentId: 3, BlockNumber: 4, ChunkOffset: 5, ChunkSize: 300},
209+
"eede": {SegmentId: 4, BlockNumber: 5, ChunkOffset: 6, ChunkSize: 400},
210+
}
211+
212+
// Insert test data
213+
for k, v := range testData {
214+
mt.Put([]byte(k), v)
215+
}
216+
217+
// Test ascending iteration
218+
iter := mt.Iterator(false)
219+
var prevKey string
220+
count := 0
221+
for iter.Rewind(); iter.Valid(); iter.Next() {
222+
currKey := string(iter.Key())
223+
pos := iter.Value()
224+
225+
// Verify key order
226+
if prevKey != "" {
227+
assert.True(t, currKey > prevKey)
228+
}
229+
230+
// Verify value correctness
231+
expectedPos := testData[currKey]
232+
assert.Equal(t, expectedPos, pos)
233+
234+
prevKey = currKey
235+
count++
236+
}
237+
assert.Equal(t, len(testData), count)
238+
239+
// Test descending iteration
240+
iter = mt.Iterator(true)
241+
prevKey = ""
242+
count = 0
243+
for iter.Rewind(); iter.Valid(); iter.Next() {
244+
currKey := string(iter.Key())
245+
pos := iter.Value()
246+
247+
// Verify key order
248+
if prevKey != "" {
249+
assert.True(t, currKey < prevKey)
250+
}
251+
252+
// Verify value correctness
253+
expectedPos := testData[currKey]
254+
assert.Equal(t, expectedPos, pos)
255+
256+
prevKey = currKey
257+
count++
258+
}
259+
assert.Equal(t, len(testData), count)
260+
261+
// Test Seek operation
262+
testCases := []struct {
263+
seekKey string
264+
expectKey string
265+
shouldFind bool
266+
}{
267+
{"b", "bbcd", true}, // Should find bbcd
268+
{"cc", "code", true}, // Should find code
269+
{"d", "eede", true}, // Should find eede
270+
{"f", "", false}, // Should not find any element
271+
{"aaa", "acee", true}, // Should find acee
272+
}
273+
274+
for _, tc := range testCases {
275+
iter = mt.Iterator(false)
276+
iter.Seek([]byte(tc.seekKey))
277+
278+
if tc.shouldFind {
279+
assert.True(t, iter.Valid())
280+
assert.Equal(t, tc.expectKey, string(iter.Key()))
281+
assert.Equal(t, testData[tc.expectKey], iter.Value())
282+
} else {
283+
assert.False(t, iter.Valid())
284+
}
285+
}
286+
}

index/index.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ type Indexer interface {
4242
// DescendLessOrEqual iterates in descending order, starting from key <= given key,
4343
// invoking handleFn. Stops if handleFn returns false.
4444
DescendLessOrEqual(key []byte, handleFn func(key []byte, position *wal.ChunkPosition) (bool, error))
45+
46+
// IndexIterator returns an index iterator.
47+
Iterator(reverse bool) IndexIterator
4548
}
4649

4750
type IndexerType = byte
@@ -61,3 +64,27 @@ func NewIndexer() Indexer {
6164
panic("unexpected index type")
6265
}
6366
}
67+
68+
// IndexIterator represents a generic index iterator interface.
69+
type IndexIterator interface {
70+
// Rewind resets the iterator to its initial position.
71+
Rewind()
72+
73+
// Seek positions the cursor to the element with the specified key.
74+
Seek(key []byte)
75+
76+
// Next moves the cursor to the next element.
77+
Next()
78+
79+
// Valid checks if the iterator is still valid for reading.
80+
Valid() bool
81+
82+
// Key returns the key of the current element.
83+
Key() []byte
84+
85+
// Value returns the value (chunk position) of the current element.
86+
Value() *wal.ChunkPosition
87+
88+
// Close releases the resources associated with the iterator.
89+
Close()
90+
}

0 commit comments

Comments
 (0)