Skip to content

Commit e0f7a9b

Browse files
committed
feat(kv): update tests to capture forward cursor
1 parent d0ac4c8 commit e0f7a9b

File tree

2 files changed

+263
-6
lines changed

2 files changed

+263
-6
lines changed

bolt/kv.go

+26-6
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,15 @@ func (b *Bucket) Delete(key []byte) error {
194194
// ForwardCursor retrieves a cursor for iterating through the entries
195195
// in the key value store in a given direction (ascending / descending).
196196
func (b *Bucket) ForwardCursor(seek []byte, opts ...kv.CursorOption) (kv.ForwardCursor, error) {
197-
cursor := b.bucket.Cursor()
198-
cursor.Seek(seek)
197+
var (
198+
cursor = b.bucket.Cursor()
199+
key, value = cursor.Seek(seek)
200+
)
201+
199202
return &Cursor{
200203
cursor: cursor,
204+
key: key,
205+
value: value,
201206
config: kv.NewCursorConfig(opts...),
202207
}, nil
203208
}
@@ -215,6 +220,9 @@ func (b *Bucket) Cursor(opts ...kv.CursorHint) (kv.Cursor, error) {
215220
type Cursor struct {
216221
cursor *bolt.Cursor
217222

223+
// previously seeked key/value
224+
key, value []byte
225+
218226
config kv.CursorConfig
219227
}
220228

@@ -246,27 +254,39 @@ func (c *Cursor) Last() ([]byte, []byte) {
246254
}
247255

248256
// Next retrieves the next key in the bucket.
249-
func (c *Cursor) Next() ([]byte, []byte) {
257+
func (c *Cursor) Next() (k []byte, v []byte) {
258+
// get and unset previously seeked values if they exist
259+
k, v, c.key, c.value = c.key, c.value, nil, nil
260+
if len(k) > 0 && len(v) > 0 {
261+
return
262+
}
263+
250264
next := c.cursor.Next
251265
if c.config.Direction == kv.CursorDescending {
252266
next = c.cursor.Prev
253267
}
254268

255-
k, v := next()
269+
k, v = next()
256270
if len(k) == 0 && len(v) == 0 {
257271
return nil, nil
258272
}
259273
return k, v
260274
}
261275

262276
// Prev retrieves the previous key in the bucket.
263-
func (c *Cursor) Prev() ([]byte, []byte) {
277+
func (c *Cursor) Prev() (k []byte, v []byte) {
278+
// get and unset previously seeked values if they exist
279+
k, v, c.key, c.value = c.key, c.value, nil, nil
280+
if len(k) > 0 && len(v) > 0 {
281+
return
282+
}
283+
264284
prev := c.cursor.Prev
265285
if c.config.Direction == kv.CursorDescending {
266286
prev = c.cursor.Next
267287
}
268288

269-
k, v := prev()
289+
k, v = prev()
270290
if len(k) == 0 && len(v) == 0 {
271291
return nil, nil
272292
}

testing/kv.go

+237
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ func KVStore(
5050
name: "CursorWithHints",
5151
fn: KVCursorWithHints,
5252
},
53+
{
54+
name: "ForwardCursor",
55+
fn: KVForwardCursor,
56+
},
5357
{
5458
name: "View",
5559
fn: KVView,
@@ -672,6 +676,239 @@ func KVCursorWithHints(
672676
}
673677
}
674678

679+
// KVForwardCursor tests the forward cursor contract for the key value store.
680+
func KVForwardCursor(
681+
init func(KVStoreFields, *testing.T) (kv.Store, func()),
682+
t *testing.T,
683+
) {
684+
type args struct {
685+
seek string
686+
direction kv.CursorDirection
687+
until string
688+
hints []kv.CursorHint
689+
}
690+
691+
pairs := func(keys ...string) []kv.Pair {
692+
p := make([]kv.Pair, len(keys))
693+
for i, k := range keys {
694+
p[i].Key = []byte(k)
695+
p[i].Value = []byte("val:" + k)
696+
}
697+
return p
698+
}
699+
700+
tests := []struct {
701+
name string
702+
fields KVStoreFields
703+
args args
704+
exp []string
705+
}{
706+
{
707+
name: "no hints",
708+
fields: KVStoreFields{
709+
Bucket: []byte("bucket"),
710+
Pairs: pairs(
711+
"aa/00", "aa/01",
712+
"aaa/00", "aaa/01", "aaa/02", "aaa/03",
713+
"bbb/00", "bbb/01", "bbb/02"),
714+
},
715+
args: args{
716+
seek: "aaa",
717+
until: "bbb/00",
718+
},
719+
exp: []string{"aaa/00", "aaa/01", "aaa/02", "aaa/03", "bbb/00"},
720+
},
721+
{
722+
name: "prefix hint",
723+
fields: KVStoreFields{
724+
Bucket: []byte("bucket"),
725+
Pairs: pairs(
726+
"aa/00", "aa/01",
727+
"aaa/00", "aaa/01", "aaa/02", "aaa/03",
728+
"bbb/00", "bbb/01", "bbb/02"),
729+
},
730+
args: args{
731+
seek: "aaa",
732+
until: "aaa/03",
733+
hints: []kv.CursorHint{kv.WithCursorHintPrefix("aaa/")},
734+
},
735+
exp: []string{"aaa/00", "aaa/01", "aaa/02", "aaa/03"},
736+
},
737+
{
738+
name: "start hint",
739+
fields: KVStoreFields{
740+
Bucket: []byte("bucket"),
741+
Pairs: pairs(
742+
"aa/00", "aa/01",
743+
"aaa/00", "aaa/01", "aaa/02", "aaa/03",
744+
"bbb/00", "bbb/01", "bbb/02"),
745+
},
746+
args: args{
747+
seek: "aaa",
748+
until: "bbb/00",
749+
hints: []kv.CursorHint{kv.WithCursorHintKeyStart("aaa/")},
750+
},
751+
exp: []string{"aaa/00", "aaa/01", "aaa/02", "aaa/03", "bbb/00"},
752+
},
753+
{
754+
name: "predicate for key",
755+
fields: KVStoreFields{
756+
Bucket: []byte("bucket"),
757+
Pairs: pairs(
758+
"aa/00", "aa/01",
759+
"aaa/00", "aaa/01", "aaa/02", "aaa/03",
760+
"bbb/00", "bbb/01", "bbb/02"),
761+
},
762+
args: args{
763+
seek: "aaa",
764+
until: "aaa/03",
765+
hints: []kv.CursorHint{
766+
kv.WithCursorHintPredicate(func(key, _ []byte) bool {
767+
return len(key) < 3 || string(key[:3]) == "aaa"
768+
})},
769+
},
770+
exp: []string{"aaa/00", "aaa/01", "aaa/02", "aaa/03"},
771+
},
772+
{
773+
name: "predicate for value",
774+
fields: KVStoreFields{
775+
Bucket: []byte("bucket"),
776+
Pairs: pairs(
777+
"aa/00", "aa/01",
778+
"aaa/00", "aaa/01", "aaa/02", "aaa/03",
779+
"bbb/00", "bbb/01", "bbb/02"),
780+
},
781+
args: args{
782+
seek: "",
783+
until: "aa/01",
784+
hints: []kv.CursorHint{
785+
kv.WithCursorHintPredicate(func(_, val []byte) bool {
786+
return len(val) < 7 || string(val[:7]) == "val:aa/"
787+
})},
788+
},
789+
exp: []string{"aa/00", "aa/01"},
790+
},
791+
{
792+
name: "no hints - descending",
793+
fields: KVStoreFields{
794+
Bucket: []byte("bucket"),
795+
Pairs: pairs(
796+
"aa/00", "aa/01",
797+
"aaa/00", "aaa/01", "aaa/02", "aaa/03",
798+
"bbb/00", "bbb/01", "bbb/02"),
799+
},
800+
args: args{
801+
seek: "bbb/00",
802+
until: "aaa/00",
803+
direction: kv.CursorDescending,
804+
},
805+
exp: []string{"bbb/00", "aaa/03", "aaa/02", "aaa/01", "aaa/00"},
806+
},
807+
{
808+
name: "start hint - descending",
809+
fields: KVStoreFields{
810+
Bucket: []byte("bucket"),
811+
Pairs: pairs(
812+
"aa/00", "aa/01",
813+
"aaa/00", "aaa/01", "aaa/02", "aaa/03",
814+
"bbb/00", "bbb/01", "bbb/02"),
815+
},
816+
args: args{
817+
seek: "bbb/00",
818+
until: "aaa/00",
819+
direction: kv.CursorDescending,
820+
hints: []kv.CursorHint{kv.WithCursorHintKeyStart("aaa/")},
821+
},
822+
exp: []string{"bbb/00", "aaa/03", "aaa/02", "aaa/01", "aaa/00"},
823+
},
824+
{
825+
name: "predicate for key - descending",
826+
fields: KVStoreFields{
827+
Bucket: []byte("bucket"),
828+
Pairs: pairs(
829+
"aa/00", "aa/01",
830+
"aaa/00", "aaa/01", "aaa/02", "aaa/03",
831+
"bbb/00", "bbb/01", "bbb/02"),
832+
},
833+
args: args{
834+
seek: "aaa/03",
835+
until: "aaa/00",
836+
direction: kv.CursorDescending,
837+
hints: []kv.CursorHint{
838+
kv.WithCursorHintPredicate(func(key, _ []byte) bool {
839+
return len(key) < 3 || string(key[:3]) == "aaa"
840+
})},
841+
},
842+
exp: []string{"aaa/03", "aaa/02", "aaa/01", "aaa/00"},
843+
},
844+
{
845+
name: "predicate for value - descending",
846+
fields: KVStoreFields{
847+
Bucket: []byte("bucket"),
848+
Pairs: pairs(
849+
"aa/00", "aa/01",
850+
"aaa/00", "aaa/01", "aaa/02", "aaa/03",
851+
"bbb/00", "bbb/01", "bbb/02"),
852+
},
853+
args: args{
854+
seek: "aa/01",
855+
until: "aa/00",
856+
direction: kv.CursorDescending,
857+
hints: []kv.CursorHint{
858+
kv.WithCursorHintPredicate(func(_, val []byte) bool {
859+
return len(val) >= 7 && string(val[:7]) == "val:aa/"
860+
})},
861+
},
862+
exp: []string{"aa/01", "aa/00"},
863+
},
864+
}
865+
866+
for _, tt := range tests {
867+
t.Run(tt.name, func(t *testing.T) {
868+
s, fin := init(tt.fields, t)
869+
defer fin()
870+
871+
err := s.View(context.Background(), func(tx kv.Tx) error {
872+
b, err := tx.Bucket([]byte("bucket"))
873+
if err != nil {
874+
t.Errorf("unexpected error retrieving bucket: %v", err)
875+
return err
876+
}
877+
878+
cur, err := b.ForwardCursor([]byte(tt.args.seek),
879+
kv.WithCursorDirection(tt.args.direction),
880+
kv.WithCursorHints(tt.args.hints...))
881+
if err != nil {
882+
t.Errorf("unexpected error: %v", err)
883+
return err
884+
}
885+
886+
var got []string
887+
888+
k, _ := cur.Next()
889+
for len(k) > 0 {
890+
got = append(got, string(k))
891+
if string(k) == tt.args.until {
892+
break
893+
}
894+
895+
k, _ = cur.Next()
896+
}
897+
898+
if exp := tt.exp; !cmp.Equal(got, exp) {
899+
t.Errorf("unexpected cursor values: -got/+exp\n%v", cmp.Diff(got, exp))
900+
}
901+
902+
return nil
903+
})
904+
905+
if err != nil {
906+
t.Fatalf("error during view transaction: %v", err)
907+
}
908+
})
909+
}
910+
}
911+
675912
// KVView tests the view method contract for the key value store.
676913
func KVView(
677914
init func(KVStoreFields, *testing.T) (kv.Store, func()),

0 commit comments

Comments
 (0)