Skip to content

Commit 5dc7ec2

Browse files
committed
Refactor memory store ScanKeys implementation
1 parent 723b1ba commit 5dc7ec2

File tree

5 files changed

+161
-0
lines changed

5 files changed

+161
-0
lines changed

store/bolt_store.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,34 @@ func (s *boltStore) Scan(ctx context.Context, start []byte, end []byte, limit in
9595
return res, errors.WithStack(err)
9696
}
9797

98+
func (s *boltStore) ScanKeys(ctx context.Context, start []byte, end []byte, limit int) ([][]byte, error) {
99+
s.log.InfoContext(ctx, "ScanKeys",
100+
slog.String("start", string(start)),
101+
slog.String("end", string(end)),
102+
slog.Int("limit", limit),
103+
)
104+
105+
var res [][]byte
106+
107+
err := s.bbolt.View(func(tx *bbolt.Tx) error {
108+
b := tx.Bucket(defaultBucket)
109+
if b == nil {
110+
return nil
111+
}
112+
113+
c := b.Cursor()
114+
for k, _ := c.Seek(start); k != nil && (end == nil || bytes.Compare(k, end) < 0); k, _ = c.Next() {
115+
res = append(res, k)
116+
if len(res) >= limit {
117+
break
118+
}
119+
}
120+
return nil
121+
})
122+
123+
return res, errors.WithStack(err)
124+
}
125+
98126
func (s *boltStore) Put(ctx context.Context, key []byte, value []byte) error {
99127
s.log.InfoContext(ctx, "put",
100128
slog.String("key", string(key)),

store/bolt_store_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,45 @@ func TestBoltStore_Scan(t *testing.T) {
8383
assert.Equal(t, 100, cnt)
8484
}
8585

86+
func TestBoltStore_ScanKeys(t *testing.T) {
87+
ctx := context.Background()
88+
t.Parallel()
89+
st := mustStore(NewBoltStore(t.TempDir() + "/bolt.db"))
90+
91+
for i := 0; i < 999; i++ {
92+
keyStr := "prefix " + strconv.Itoa(i) + "foo"
93+
key := []byte(keyStr)
94+
b := make([]byte, 8)
95+
binary.PutVarint(b, int64(i))
96+
err := st.Put(ctx, key, b)
97+
assert.NoError(t, err)
98+
}
99+
100+
res, err := st.ScanKeys(ctx, []byte("prefix"), []byte("z"), 100)
101+
assert.NoError(t, err)
102+
assert.Equal(t, 100, len(res))
103+
104+
sortedKeys := make([][]byte, 999)
105+
106+
for _, k := range res {
107+
str := string(k)
108+
i, err := strconv.Atoi(str[7 : len(str)-3])
109+
assert.NoError(t, err)
110+
sortedKeys[i] = k
111+
}
112+
113+
cnt := 0
114+
for i, k := range sortedKeys {
115+
if k == nil {
116+
continue
117+
}
118+
cnt++
119+
assert.Equal(t, []byte("prefix "+strconv.Itoa(i)+"foo"), k)
120+
}
121+
122+
assert.Equal(t, 100, cnt)
123+
}
124+
86125
func TestBoltStore_Txn(t *testing.T) {
87126
t.Parallel()
88127
t.Run("success", func(t *testing.T) {

store/rb_memory_store.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,40 @@ func (s *rbMemoryStore) Scan(ctx context.Context, start []byte, end []byte, limi
126126
return result, nil
127127
}
128128

129+
func (s *rbMemoryStore) ScanKeys(ctx context.Context, start []byte, end []byte, limit int) ([][]byte, error) {
130+
s.mtx.RLock()
131+
defer s.mtx.RUnlock()
132+
133+
var result [][]byte
134+
135+
it := s.tree.Iterator()
136+
137+
var ok bool
138+
if start != nil {
139+
it.Begin()
140+
ok = it.NextTo(func(key, _ interface{}) bool {
141+
k, _ := key.([]byte)
142+
return bytes.Compare(k, start) >= 0
143+
})
144+
} else {
145+
ok = it.First()
146+
}
147+
148+
for ; ok && len(result) < limit; ok = it.Next() {
149+
k, _ := it.Key().([]byte)
150+
151+
if end != nil && bytes.Compare(k, end) > 0 {
152+
break
153+
}
154+
155+
keyCopy := make([]byte, len(k))
156+
copy(keyCopy, k)
157+
result = append(result, keyCopy)
158+
}
159+
160+
return result, nil
161+
}
162+
129163
func (s *rbMemoryStore) Put(ctx context.Context, key []byte, value []byte) error {
130164
s.mtx.Lock()
131165
defer s.mtx.Unlock()

store/rb_memory_store_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,64 @@ func TestRbMemoryStore_Scan(t *testing.T) {
8585
assert.Equal(t, 100, cnt)
8686
}
8787

88+
func TestRbMemoryStore_ScanKeys(t *testing.T) {
89+
ctx := context.Background()
90+
t.Parallel()
91+
st := NewRbMemoryStore()
92+
93+
for i := 0; i < 9999; i++ {
94+
keyStr := "prefix " + strconv.Itoa(i) + "foo"
95+
key := []byte(keyStr)
96+
b := make([]byte, 8)
97+
binary.PutVarint(b, int64(i))
98+
err := st.Put(ctx, key, b)
99+
assert.NoError(t, err)
100+
}
101+
102+
res, err := st.ScanKeys(ctx, []byte("prefix"), []byte("z"), 100)
103+
assert.NoError(t, err)
104+
assert.Equal(t, 100, len(res))
105+
106+
sortedKeys := make([][]byte, 9999)
107+
108+
for _, k := range res {
109+
str := string(k)
110+
i, err := strconv.Atoi(str[7 : len(str)-3])
111+
assert.NoError(t, err)
112+
sortedKeys[i] = k
113+
}
114+
115+
cnt := 0
116+
for i, k := range sortedKeys {
117+
if k == nil {
118+
continue
119+
}
120+
cnt++
121+
assert.Equal(t, []byte("prefix "+strconv.Itoa(i)+"foo"), k)
122+
}
123+
124+
assert.Equal(t, 100, cnt)
125+
}
126+
127+
func TestRbMemoryStore_ScanKeysReturnsCopies(t *testing.T) {
128+
ctx := context.Background()
129+
st := NewRbMemoryStore()
130+
131+
assert.NoError(t, st.Put(ctx, []byte("foo"), []byte("bar")))
132+
133+
res, err := st.ScanKeys(ctx, nil, nil, 10)
134+
assert.NoError(t, err)
135+
if len(res) == 0 {
136+
t.Fatalf("expected keys, got none")
137+
}
138+
139+
res[0][0] = 'x'
140+
141+
res2, err := st.ScanKeys(ctx, nil, nil, 10)
142+
assert.NoError(t, err)
143+
assert.Equal(t, []byte("foo"), res2[0])
144+
}
145+
88146
func TestRbMemoryStore_Txn(t *testing.T) {
89147
t.Parallel()
90148
t.Run("success", func(t *testing.T) {

store/store.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type Store interface {
3232
type ScanStore interface {
3333
Store
3434
Scan(ctx context.Context, start []byte, end []byte, limit int) ([]*KVPair, error)
35+
ScanKeys(ctx context.Context, start []byte, end []byte, limit int) ([][]byte, error)
3536
}
3637

3738
type TTLStore interface {
@@ -56,6 +57,7 @@ type Txn interface {
5657
type ScanTxn interface {
5758
Txn
5859
Scan(ctx context.Context, start []byte, end []byte, limit int) ([]*KVPair, error)
60+
ScanKeys(ctx context.Context, start []byte, end []byte, limit int) ([][]byte, error)
5961
}
6062

6163
type TTLTxn interface {

0 commit comments

Comments
 (0)