Skip to content

Commit 243c991

Browse files
committed
Add index iterator interface with B-tree implementation and tests
1 parent 65a1b4d commit 243c991

File tree

3 files changed

+244
-0
lines changed

3 files changed

+244
-0
lines changed

index/btree.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,129 @@ func (mt *MemoryBTree) DescendLessOrEqual(key []byte, handleFn func(key []byte,
147147
return cont
148148
})
149149
}
150+
151+
func (mt *MemoryBTree) Iterator(reverse bool) Iterator {
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.valid {
190+
return
191+
}
192+
if it.reverse {
193+
if mx, ok := it.tree.Max().(*item); ok {
194+
it.current = mx
195+
}
196+
} else {
197+
if mi, ok := it.tree.Min().(*item); ok {
198+
it.current = mi
199+
}
200+
}
201+
}
202+
203+
func (it *memoryBTreeIterator) Seek(key []byte) {
204+
if !it.valid {
205+
return
206+
}
207+
seekItem := &item{key: key}
208+
it.valid = false
209+
if it.reverse {
210+
it.tree.DescendLessOrEqual(seekItem, func(i btree.Item) bool {
211+
it.current = i.(*item)
212+
it.valid = true
213+
return false
214+
})
215+
} else {
216+
it.tree.AscendGreaterOrEqual(seekItem, func(i btree.Item) bool {
217+
it.current = i.(*item)
218+
it.valid = true
219+
return false
220+
})
221+
}
222+
}
223+
224+
func (it *memoryBTreeIterator) Next() {
225+
if !it.valid {
226+
return
227+
}
228+
it.valid = false
229+
if it.reverse {
230+
it.tree.DescendLessOrEqual(it.current, func(i btree.Item) bool {
231+
if !i.(*item).Less(it.current) {
232+
return true
233+
}
234+
it.current = i.(*item)
235+
it.valid = true
236+
return false
237+
})
238+
} else {
239+
it.tree.AscendGreaterOrEqual(it.current, func(i btree.Item) bool {
240+
if !it.current.Less(i.(*item)) {
241+
return true
242+
}
243+
it.current = i.(*item)
244+
it.valid = true
245+
return false
246+
})
247+
}
248+
if !it.valid {
249+
it.current = nil
250+
}
251+
}
252+
253+
func (it *memoryBTreeIterator) Valid() bool {
254+
return it.valid
255+
}
256+
257+
func (it *memoryBTreeIterator) Key() []byte {
258+
if !it.valid {
259+
return nil
260+
}
261+
return it.current.key
262+
}
263+
264+
func (it *memoryBTreeIterator) Value() *wal.ChunkPosition {
265+
if !it.valid {
266+
return nil
267+
}
268+
return it.current.pos
269+
}
270+
271+
func (it *memoryBTreeIterator) Close() {
272+
it.tree = nil
273+
it.current = nil
274+
it.valid = false
275+
}

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+
// Iterator returns an index iterator.
47+
Iterator(reverse bool) Iterator
4548
}
4649

4750
type IndexerType = byte
@@ -61,3 +64,27 @@ func NewIndexer() Indexer {
6164
panic("unexpected index type")
6265
}
6366
}
67+
68+
// Iterator represents a generic index iterator interface.
69+
type Iterator 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)