Skip to content

Commit 01d1310

Browse files
committed
Add key-only scan interface
1 parent 723b1ba commit 01d1310

File tree

5 files changed

+138
-0
lines changed

5 files changed

+138
-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: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,36 @@ 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+
s.tree.Each(func(key interface{}, _ interface{}) {
136+
k, ok := key.([]byte)
137+
if !ok {
138+
return
139+
}
140+
141+
if start != nil && bytes.Compare(k, start) < 0 {
142+
return
143+
}
144+
145+
if end != nil && bytes.Compare(k, end) > 0 {
146+
return
147+
}
148+
149+
if len(result) >= limit {
150+
return
151+
}
152+
153+
result = append(result, k)
154+
155+
})
156+
return result, nil
157+
}
158+
129159
func (s *rbMemoryStore) Put(ctx context.Context, key []byte, value []byte) error {
130160
s.mtx.Lock()
131161
defer s.mtx.Unlock()

store/rb_memory_store_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,45 @@ 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+
88127
func TestRbMemoryStore_Txn(t *testing.T) {
89128
t.Parallel()
90129
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)