bbolt/cursor_test.go
Josh Rickmar 7a957f94b2 Introduce errors package and restore API compatibility
This moves the error variables that had been moved to the
internal/common package during recent refactoring to a non-internal
errors package, once again allowing consumers to test for particular
error conditions.

To preserve API compatibility with bbolt v1.3, these error variables
are also redefined in the bbolt package, with deprecation notice to
migrate to bbolt/errors.

Signed-off-by: Josh Rickmar <jrick@zettaport.com>
2023-04-06 13:26:30 +00:00

854 lines
19 KiB
Go

package bbolt_test
import (
"bytes"
"encoding/binary"
"fmt"
"log"
"os"
"reflect"
"sort"
"testing"
"testing/quick"
bolt "go.etcd.io/bbolt"
"go.etcd.io/bbolt/errors"
"go.etcd.io/bbolt/internal/btesting"
)
// Ensure that a cursor can return a reference to the bucket that created it.
func TestCursor_Bucket(t *testing.T) {
db := btesting.MustCreateDB(t)
if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets"))
if err != nil {
t.Fatal(err)
}
if cb := b.Cursor().Bucket(); !reflect.DeepEqual(cb, b) {
t.Fatal("cursor bucket mismatch")
}
return nil
}); err != nil {
t.Fatal(err)
}
}
// Ensure that a Tx cursor can seek to the appropriate keys.
func TestCursor_Seek(t *testing.T) {
db := btesting.MustCreateDB(t)
if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets"))
if err != nil {
t.Fatal(err)
}
if err := b.Put([]byte("foo"), []byte("0001")); err != nil {
t.Fatal(err)
}
if err := b.Put([]byte("bar"), []byte("0002")); err != nil {
t.Fatal(err)
}
if err := b.Put([]byte("baz"), []byte("0003")); err != nil {
t.Fatal(err)
}
if _, err := b.CreateBucket([]byte("bkt")); err != nil {
t.Fatal(err)
}
return nil
}); err != nil {
t.Fatal(err)
}
if err := db.View(func(tx *bolt.Tx) error {
c := tx.Bucket([]byte("widgets")).Cursor()
// Exact match should go to the key.
if k, v := c.Seek([]byte("bar")); !bytes.Equal(k, []byte("bar")) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, []byte("0002")) {
t.Fatalf("unexpected value: %v", v)
}
// Inexact match should go to the next key.
if k, v := c.Seek([]byte("bas")); !bytes.Equal(k, []byte("baz")) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, []byte("0003")) {
t.Fatalf("unexpected value: %v", v)
}
// Low key should go to the first key.
if k, v := c.Seek([]byte("")); !bytes.Equal(k, []byte("bar")) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, []byte("0002")) {
t.Fatalf("unexpected value: %v", v)
}
// High key should return no key.
if k, v := c.Seek([]byte("zzz")); k != nil {
t.Fatalf("expected nil key: %v", k)
} else if v != nil {
t.Fatalf("expected nil value: %v", v)
}
// Buckets should return their key but no value.
if k, v := c.Seek([]byte("bkt")); !bytes.Equal(k, []byte("bkt")) {
t.Fatalf("unexpected key: %v", k)
} else if v != nil {
t.Fatalf("expected nil value: %v", v)
}
return nil
}); err != nil {
t.Fatal(err)
}
}
func TestCursor_Delete(t *testing.T) {
db := btesting.MustCreateDB(t)
const count = 1000
// Insert every other key between 0 and $count.
if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets"))
if err != nil {
t.Fatal(err)
}
for i := 0; i < count; i += 1 {
k := make([]byte, 8)
binary.BigEndian.PutUint64(k, uint64(i))
if err := b.Put(k, make([]byte, 100)); err != nil {
t.Fatal(err)
}
}
if _, err := b.CreateBucket([]byte("sub")); err != nil {
t.Fatal(err)
}
return nil
}); err != nil {
t.Fatal(err)
}
if err := db.Update(func(tx *bolt.Tx) error {
c := tx.Bucket([]byte("widgets")).Cursor()
bound := make([]byte, 8)
binary.BigEndian.PutUint64(bound, uint64(count/2))
for key, _ := c.First(); bytes.Compare(key, bound) < 0; key, _ = c.Next() {
if err := c.Delete(); err != nil {
t.Fatal(err)
}
}
c.Seek([]byte("sub"))
if err := c.Delete(); err != errors.ErrIncompatibleValue {
t.Fatalf("unexpected error: %s", err)
}
return nil
}); err != nil {
t.Fatal(err)
}
if err := db.View(func(tx *bolt.Tx) error {
stats := tx.Bucket([]byte("widgets")).Stats()
if stats.KeyN != count/2+1 {
t.Fatalf("unexpected KeyN: %d", stats.KeyN)
}
return nil
}); err != nil {
t.Fatal(err)
}
}
// Ensure that a Tx cursor can seek to the appropriate keys when there are a
// large number of keys. This test also checks that seek will always move
// forward to the next key.
//
// Related: https://github.com/boltdb/bolt/pull/187
func TestCursor_Seek_Large(t *testing.T) {
db := btesting.MustCreateDB(t)
var count = 10000
// Insert every other key between 0 and $count.
if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets"))
if err != nil {
t.Fatal(err)
}
for i := 0; i < count; i += 100 {
for j := i; j < i+100; j += 2 {
k := make([]byte, 8)
binary.BigEndian.PutUint64(k, uint64(j))
if err := b.Put(k, make([]byte, 100)); err != nil {
t.Fatal(err)
}
}
}
return nil
}); err != nil {
t.Fatal(err)
}
if err := db.View(func(tx *bolt.Tx) error {
c := tx.Bucket([]byte("widgets")).Cursor()
for i := 0; i < count; i++ {
seek := make([]byte, 8)
binary.BigEndian.PutUint64(seek, uint64(i))
k, _ := c.Seek(seek)
// The last seek is beyond the end of the the range so
// it should return nil.
if i == count-1 {
if k != nil {
t.Fatal("expected nil key")
}
continue
}
// Otherwise we should seek to the exact key or the next key.
num := binary.BigEndian.Uint64(k)
if i%2 == 0 {
if num != uint64(i) {
t.Fatalf("unexpected num: %d", num)
}
} else {
if num != uint64(i+1) {
t.Fatalf("unexpected num: %d", num)
}
}
}
return nil
}); err != nil {
t.Fatal(err)
}
}
// Ensure that a cursor can iterate over an empty bucket without error.
func TestCursor_EmptyBucket(t *testing.T) {
db := btesting.MustCreateDB(t)
if err := db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucket([]byte("widgets"))
return err
}); err != nil {
t.Fatal(err)
}
if err := db.View(func(tx *bolt.Tx) error {
c := tx.Bucket([]byte("widgets")).Cursor()
k, v := c.First()
if k != nil {
t.Fatalf("unexpected key: %v", k)
} else if v != nil {
t.Fatalf("unexpected value: %v", v)
}
return nil
}); err != nil {
t.Fatal(err)
}
}
// Ensure that a Tx cursor can reverse iterate over an empty bucket without error.
func TestCursor_EmptyBucketReverse(t *testing.T) {
db := btesting.MustCreateDB(t)
if err := db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucket([]byte("widgets"))
return err
}); err != nil {
t.Fatal(err)
}
if err := db.View(func(tx *bolt.Tx) error {
c := tx.Bucket([]byte("widgets")).Cursor()
k, v := c.Last()
if k != nil {
t.Fatalf("unexpected key: %v", k)
} else if v != nil {
t.Fatalf("unexpected value: %v", v)
}
return nil
}); err != nil {
t.Fatal(err)
}
}
// Ensure that a Tx cursor can iterate over a single root with a couple elements.
func TestCursor_Iterate_Leaf(t *testing.T) {
db := btesting.MustCreateDB(t)
if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets"))
if err != nil {
t.Fatal(err)
}
if err := b.Put([]byte("baz"), []byte{}); err != nil {
t.Fatal(err)
}
if err := b.Put([]byte("foo"), []byte{0}); err != nil {
t.Fatal(err)
}
if err := b.Put([]byte("bar"), []byte{1}); err != nil {
t.Fatal(err)
}
return nil
}); err != nil {
t.Fatal(err)
}
tx, err := db.Begin(false)
if err != nil {
t.Fatal(err)
}
defer func() { _ = tx.Rollback() }()
c := tx.Bucket([]byte("widgets")).Cursor()
k, v := c.First()
if !bytes.Equal(k, []byte("bar")) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, []byte{1}) {
t.Fatalf("unexpected value: %v", v)
}
k, v = c.Next()
if !bytes.Equal(k, []byte("baz")) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, []byte{}) {
t.Fatalf("unexpected value: %v", v)
}
k, v = c.Next()
if !bytes.Equal(k, []byte("foo")) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, []byte{0}) {
t.Fatalf("unexpected value: %v", v)
}
k, v = c.Next()
if k != nil {
t.Fatalf("expected nil key: %v", k)
} else if v != nil {
t.Fatalf("expected nil value: %v", v)
}
k, v = c.Next()
if k != nil {
t.Fatalf("expected nil key: %v", k)
} else if v != nil {
t.Fatalf("expected nil value: %v", v)
}
if err := tx.Rollback(); err != nil {
t.Fatal(err)
}
}
// Ensure that a Tx cursor can iterate in reverse over a single root with a couple elements.
func TestCursor_LeafRootReverse(t *testing.T) {
db := btesting.MustCreateDB(t)
if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets"))
if err != nil {
t.Fatal(err)
}
if err := b.Put([]byte("baz"), []byte{}); err != nil {
t.Fatal(err)
}
if err := b.Put([]byte("foo"), []byte{0}); err != nil {
t.Fatal(err)
}
if err := b.Put([]byte("bar"), []byte{1}); err != nil {
t.Fatal(err)
}
return nil
}); err != nil {
t.Fatal(err)
}
tx, err := db.Begin(false)
if err != nil {
t.Fatal(err)
}
c := tx.Bucket([]byte("widgets")).Cursor()
if k, v := c.Last(); !bytes.Equal(k, []byte("foo")) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, []byte{0}) {
t.Fatalf("unexpected value: %v", v)
}
if k, v := c.Prev(); !bytes.Equal(k, []byte("baz")) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, []byte{}) {
t.Fatalf("unexpected value: %v", v)
}
if k, v := c.Prev(); !bytes.Equal(k, []byte("bar")) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, []byte{1}) {
t.Fatalf("unexpected value: %v", v)
}
if k, v := c.Prev(); k != nil {
t.Fatalf("expected nil key: %v", k)
} else if v != nil {
t.Fatalf("expected nil value: %v", v)
}
if k, v := c.Prev(); k != nil {
t.Fatalf("expected nil key: %v", k)
} else if v != nil {
t.Fatalf("expected nil value: %v", v)
}
if err := tx.Rollback(); err != nil {
t.Fatal(err)
}
}
// Ensure that a Tx cursor can restart from the beginning.
func TestCursor_Restart(t *testing.T) {
db := btesting.MustCreateDB(t)
if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets"))
if err != nil {
t.Fatal(err)
}
if err := b.Put([]byte("bar"), []byte{}); err != nil {
t.Fatal(err)
}
if err := b.Put([]byte("foo"), []byte{}); err != nil {
t.Fatal(err)
}
return nil
}); err != nil {
t.Fatal(err)
}
tx, err := db.Begin(false)
if err != nil {
t.Fatal(err)
}
c := tx.Bucket([]byte("widgets")).Cursor()
if k, _ := c.First(); !bytes.Equal(k, []byte("bar")) {
t.Fatalf("unexpected key: %v", k)
}
if k, _ := c.Next(); !bytes.Equal(k, []byte("foo")) {
t.Fatalf("unexpected key: %v", k)
}
if k, _ := c.First(); !bytes.Equal(k, []byte("bar")) {
t.Fatalf("unexpected key: %v", k)
}
if k, _ := c.Next(); !bytes.Equal(k, []byte("foo")) {
t.Fatalf("unexpected key: %v", k)
}
if err := tx.Rollback(); err != nil {
t.Fatal(err)
}
}
// Ensure that a cursor can skip over empty pages that have been deleted.
func TestCursor_First_EmptyPages(t *testing.T) {
db := btesting.MustCreateDB(t)
// Create 1000 keys in the "widgets" bucket.
if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets"))
if err != nil {
t.Fatal(err)
}
for i := 0; i < 1000; i++ {
if err := b.Put(u64tob(uint64(i)), []byte{}); err != nil {
t.Fatal(err)
}
}
return nil
}); err != nil {
t.Fatal(err)
}
// Delete half the keys and then try to iterate.
if err := db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("widgets"))
for i := 0; i < 600; i++ {
if err := b.Delete(u64tob(uint64(i))); err != nil {
t.Fatal(err)
}
}
c := b.Cursor()
var n int
for k, _ := c.First(); k != nil; k, _ = c.Next() {
n++
}
if n != 400 {
t.Fatalf("unexpected key count: %d", n)
}
return nil
}); err != nil {
t.Fatal(err)
}
}
// Ensure that a cursor can skip over empty pages that have been deleted.
func TestCursor_Last_EmptyPages(t *testing.T) {
db := btesting.MustCreateDB(t)
// Create 1000 keys in the "widgets" bucket.
if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets"))
if err != nil {
t.Fatal(err)
}
for i := 0; i < 1000; i++ {
if err := b.Put(u64tob(uint64(i)), []byte{}); err != nil {
t.Fatal(err)
}
}
return nil
}); err != nil {
t.Fatal(err)
}
// Delete last 800 elements to ensure last page is empty
if err := db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("widgets"))
for i := 200; i < 1000; i++ {
if err := b.Delete(u64tob(uint64(i))); err != nil {
t.Fatal(err)
}
}
c := b.Cursor()
var n int
for k, _ := c.Last(); k != nil; k, _ = c.Prev() {
n++
}
if n != 200 {
t.Fatalf("unexpected key count: %d", n)
}
return nil
}); err != nil {
t.Fatal(err)
}
}
// Ensure that a Tx can iterate over all elements in a bucket.
func TestCursor_QuickCheck(t *testing.T) {
f := func(items testdata) bool {
db := btesting.MustCreateDB(t)
defer db.MustClose()
// Bulk insert all values.
tx, err := db.Begin(true)
if err != nil {
t.Fatal(err)
}
b, err := tx.CreateBucket([]byte("widgets"))
if err != nil {
t.Fatal(err)
}
for _, item := range items {
if err := b.Put(item.Key, item.Value); err != nil {
t.Fatal(err)
}
}
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
// Sort test data.
sort.Sort(items)
// Iterate over all items and check consistency.
var index = 0
tx, err = db.Begin(false)
if err != nil {
t.Fatal(err)
}
c := tx.Bucket([]byte("widgets")).Cursor()
for k, v := c.First(); k != nil && index < len(items); k, v = c.Next() {
if !bytes.Equal(k, items[index].Key) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, items[index].Value) {
t.Fatalf("unexpected value: %v", v)
}
index++
}
if len(items) != index {
t.Fatalf("unexpected item count: %v, expected %v", len(items), index)
}
if err := tx.Rollback(); err != nil {
t.Fatal(err)
}
return true
}
if err := quick.Check(f, qconfig()); err != nil {
t.Error(err)
}
}
// Ensure that a transaction can iterate over all elements in a bucket in reverse.
func TestCursor_QuickCheck_Reverse(t *testing.T) {
f := func(items testdata) bool {
db := btesting.MustCreateDB(t)
defer db.MustClose()
// Bulk insert all values.
tx, err := db.Begin(true)
if err != nil {
t.Fatal(err)
}
b, err := tx.CreateBucket([]byte("widgets"))
if err != nil {
t.Fatal(err)
}
for _, item := range items {
if err := b.Put(item.Key, item.Value); err != nil {
t.Fatal(err)
}
}
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
// Sort test data.
sort.Sort(revtestdata(items))
// Iterate over all items and check consistency.
var index = 0
tx, err = db.Begin(false)
if err != nil {
t.Fatal(err)
}
c := tx.Bucket([]byte("widgets")).Cursor()
for k, v := c.Last(); k != nil && index < len(items); k, v = c.Prev() {
if !bytes.Equal(k, items[index].Key) {
t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, items[index].Value) {
t.Fatalf("unexpected value: %v", v)
}
index++
}
if len(items) != index {
t.Fatalf("unexpected item count: %v, expected %v", len(items), index)
}
if err := tx.Rollback(); err != nil {
t.Fatal(err)
}
return true
}
if err := quick.Check(f, qconfig()); err != nil {
t.Error(err)
}
}
// Ensure that a Tx cursor can iterate over subbuckets.
func TestCursor_QuickCheck_BucketsOnly(t *testing.T) {
db := btesting.MustCreateDB(t)
if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets"))
if err != nil {
t.Fatal(err)
}
if _, err := b.CreateBucket([]byte("foo")); err != nil {
t.Fatal(err)
}
if _, err := b.CreateBucket([]byte("bar")); err != nil {
t.Fatal(err)
}
if _, err := b.CreateBucket([]byte("baz")); err != nil {
t.Fatal(err)
}
return nil
}); err != nil {
t.Fatal(err)
}
if err := db.View(func(tx *bolt.Tx) error {
var names []string
c := tx.Bucket([]byte("widgets")).Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
names = append(names, string(k))
if v != nil {
t.Fatalf("unexpected value: %v", v)
}
}
if !reflect.DeepEqual(names, []string{"bar", "baz", "foo"}) {
t.Fatalf("unexpected names: %+v", names)
}
return nil
}); err != nil {
t.Fatal(err)
}
}
// Ensure that a Tx cursor can reverse iterate over subbuckets.
func TestCursor_QuickCheck_BucketsOnly_Reverse(t *testing.T) {
db := btesting.MustCreateDB(t)
if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets"))
if err != nil {
t.Fatal(err)
}
if _, err := b.CreateBucket([]byte("foo")); err != nil {
t.Fatal(err)
}
if _, err := b.CreateBucket([]byte("bar")); err != nil {
t.Fatal(err)
}
if _, err := b.CreateBucket([]byte("baz")); err != nil {
t.Fatal(err)
}
return nil
}); err != nil {
t.Fatal(err)
}
if err := db.View(func(tx *bolt.Tx) error {
var names []string
c := tx.Bucket([]byte("widgets")).Cursor()
for k, v := c.Last(); k != nil; k, v = c.Prev() {
names = append(names, string(k))
if v != nil {
t.Fatalf("unexpected value: %v", v)
}
}
if !reflect.DeepEqual(names, []string{"foo", "baz", "bar"}) {
t.Fatalf("unexpected names: %+v", names)
}
return nil
}); err != nil {
t.Fatal(err)
}
}
func ExampleCursor() {
// Open the database.
db, err := bolt.Open(tempfile(), 0666, nil)
if err != nil {
log.Fatal(err)
}
defer os.Remove(db.Path())
// Start a read-write transaction.
if err := db.Update(func(tx *bolt.Tx) error {
// Create a new bucket.
b, err := tx.CreateBucket([]byte("animals"))
if err != nil {
return err
}
// Insert data into a bucket.
if err := b.Put([]byte("dog"), []byte("fun")); err != nil {
log.Fatal(err)
}
if err := b.Put([]byte("cat"), []byte("lame")); err != nil {
log.Fatal(err)
}
if err := b.Put([]byte("liger"), []byte("awesome")); err != nil {
log.Fatal(err)
}
// Create a cursor for iteration.
c := b.Cursor()
// Iterate over items in sorted key order. This starts from the
// first key/value pair and updates the k/v variables to the
// next key/value on each iteration.
//
// The loop finishes at the end of the cursor when a nil key is returned.
for k, v := c.First(); k != nil; k, v = c.Next() {
fmt.Printf("A %s is %s.\n", k, v)
}
return nil
}); err != nil {
log.Fatal(err)
}
if err := db.Close(); err != nil {
log.Fatal(err)
}
// Output:
// A cat is lame.
// A dog is fun.
// A liger is awesome.
}
func ExampleCursor_reverse() {
// Open the database.
db, err := bolt.Open(tempfile(), 0666, nil)
if err != nil {
log.Fatal(err)
}
defer os.Remove(db.Path())
// Start a read-write transaction.
if err := db.Update(func(tx *bolt.Tx) error {
// Create a new bucket.
b, err := tx.CreateBucket([]byte("animals"))
if err != nil {
return err
}
// Insert data into a bucket.
if err := b.Put([]byte("dog"), []byte("fun")); err != nil {
log.Fatal(err)
}
if err := b.Put([]byte("cat"), []byte("lame")); err != nil {
log.Fatal(err)
}
if err := b.Put([]byte("liger"), []byte("awesome")); err != nil {
log.Fatal(err)
}
// Create a cursor for iteration.
c := b.Cursor()
// Iterate over items in reverse sorted key order. This starts
// from the last key/value pair and updates the k/v variables to
// the previous key/value on each iteration.
//
// The loop finishes at the beginning of the cursor when a nil key
// is returned.
for k, v := c.Last(); k != nil; k, v = c.Prev() {
fmt.Printf("A %s is %s.\n", k, v)
}
return nil
}); err != nil {
log.Fatal(err)
}
// Close the database to release the file lock.
if err := db.Close(); err != nil {
log.Fatal(err)
}
// Output:
// A liger is awesome.
// A dog is fun.
// A cat is lame.
}