diff --git a/bucket.go b/bucket.go index 0a9f9e333..8c5f7d763 100644 --- a/bucket.go +++ b/bucket.go @@ -742,10 +742,6 @@ func (b *Bucket) write() []byte { return value } -func (b *Bucket) Write() []byte { - return b.write() -} - // rebalance attempts to balance all nodes. func (b *Bucket) rebalance() { for _, n := range b.nodes { diff --git a/movebucket_test.go b/movebucket_test.go index 664dfda92..0df12648a 100644 --- a/movebucket_test.go +++ b/movebucket_test.go @@ -1,6 +1,8 @@ package bbolt_test import ( + "bytes" + berrors "go.etcd.io/bbolt/errors" "math/rand" "os" "strings" @@ -12,6 +14,283 @@ import ( "go.etcd.io/bbolt/internal/btesting" ) +func TestTx_MoveBucket(t *testing.T) { + testCases := []struct { + name string + srcBucketName string + subBucketName string + subBucketKey string + subBucketValue string + dstBucketName string + subBucketExistSrcBucket bool + subBucketExistDstBucket bool + keyNoSubBucketDstBucket bool + srcBucketIsRootBucket bool + dstBucketIsRootBucket bool + expErr error + }{ + { + "happy path", + "srcBucket", + "subBucket", + "this is subBucket key", + "this is subBucket value", + "dstBucket", + true, + false, + false, + false, + false, + nil, + }, + { + "subBucket not exist in src bucket", + "srcBucket", + "subBucket", + "this is subBucket key", + "this is subBucket value", + "dstBucket", + false, + false, + false, + false, + false, + berrors.ErrBucketNotFound, + }, + { + "suBucket exist in dstBucket", + "srcBucket", + "subBucket", + "this is subBucket key", + "this is subBucket value", + "dstBucket", + true, + true, + false, + false, + false, + berrors.ErrBucketExists, + }, + { + "subBucket key exist in dstBucket, but no subBucket value", + "srcBucket", + "subBucket", + "this is subBucket key", + "this is subBucket value", + "dstBucket", + true, + false, + true, + false, + false, + berrors.ErrIncompatibleValue, + }, + { + "srcBucket is RootBucket", + "srcBucket", + "subBucket", + "this is subBucket key", + "this is subBucket value", + "dstBucket", + true, + false, + false, + true, + false, + nil, + }, + { + "dstBucket is RootBucket", + "srcBucket", + "subBucket", + "this is subBucket key", + "this is subBucket value", + "dstBucket", + true, + false, + false, + false, + true, + nil, + }, + { + "srcBucket is RootBucket, and dstBucket is RootBucket, corner case", + "srcBucket", + "subBucket", + "this is subBucket key", + "this is subBucket value", + "dstBucket", + true, + true, + false, + true, + true, + nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + db := btesting.MustCreateDB(t) + + // arrange + if err := db.Update(func(tx *bolt.Tx) error { + var srcBucket *bolt.Bucket = nil + var bErr error = nil + var subBucket *bolt.Bucket = nil + var sErr error = nil + + if !tc.srcBucketIsRootBucket { + // create src bucket + srcBucket, bErr = tx.CreateBucketIfNotExists([]byte(tc.srcBucketName)) + if bErr != nil { + t.Fatalf("error creating src bucket %s: %v", tc.srcBucketName, bErr) + } + // insert K/V pair into src bucket + if pErr := srcBucket.Put([]byte("foo"), []byte("0000")); pErr != nil { + t.Fatal(pErr) + } + } + + if tc.subBucketExistSrcBucket { + if tc.srcBucketIsRootBucket { + // create subBucket within root bucket + subBucket, sErr = tx.CreateBucket([]byte(tc.subBucketName)) + if sErr != nil { + t.Fatalf("error creating subBucket %s within root bucket: %v", tc.subBucketName, sErr) + } + } else { + // create subBucket within srcBucket + subBucket, sErr = srcBucket.CreateBucket([]byte(tc.subBucketName)) + if sErr != nil { + t.Fatalf("error creating subBucket %s within srcBucket %s: %v", tc.subBucketName, tc.srcBucketName, sErr) + } + } + // insert K/V pair into subBucket + if pErr := subBucket.Put([]byte(tc.subBucketKey), []byte(tc.subBucketValue)); pErr != nil { + t.Fatal(pErr) + } + } + + // create dst bucket + if !tc.dstBucketIsRootBucket { + dstBucket, bErr := tx.CreateBucketIfNotExists([]byte(tc.dstBucketName)) + if bErr != nil { + t.Fatalf("error creating dst bucket %s: %v", tc.dstBucketName, bErr) + } + // insert K/V pair into dst bucket + if pErr := dstBucket.Put([]byte("bar"), []byte("0000")); pErr != nil { + t.Fatal(pErr) + } + + if tc.subBucketExistDstBucket { + // create subBucket within dstBucket + _, sErr := dstBucket.CreateBucket([]byte(tc.subBucketName)) + if sErr != nil { + t.Fatal(sErr) + } + } + + if tc.keyNoSubBucketDstBucket { + if pErr := dstBucket.Put([]byte(tc.subBucketName), []byte(tc.subBucketValue)); pErr != nil { + t.Fatal(pErr) + } + } + } + + return nil + }); err != nil { + t.Fatal(err) + } + db.MustCheck() + + // act: move the subBucket from srcBucket to dstBucket + if err := db.Update(func(tx *bolt.Tx) error { + srcBucket := tx.Bucket([]byte(tc.srcBucketName)) + if srcBucket == nil && !tc.srcBucketIsRootBucket { + t.Fatalf("src bucket %s does not exist: %v", tc.srcBucketName, berrors.ErrBucketNotFound) + } + + dstBucket := tx.Bucket([]byte(tc.dstBucketName)) + if dstBucket == nil && !tc.dstBucketIsRootBucket { + t.Fatalf("dst bucket %s does not exist: %v", tc.dstBucketName, berrors.ErrBucketNotFound) + } + + mvErr := tx.MoveBucket([]byte(tc.subBucketName), srcBucket, dstBucket) + if !tc.subBucketExistSrcBucket || tc.subBucketExistDstBucket || tc.keyNoSubBucketDstBucket { + require.ErrorIs(t, mvErr, tc.expErr) + } else if mvErr != nil { + t.Fatalf("failed to move subBucket '%v' from root bucket to dstBucket '%v': %v", tc.subBucketName, tc.dstBucketName, mvErr) + } + + return nil + + }); err != nil { + t.Fatal(err) + } + db.MustCheck() + + // skip assertion in these cases + if !tc.subBucketExistSrcBucket || tc.subBucketExistDstBucket || tc.keyNoSubBucketDstBucket { + return + } + + // assert: check subBucket has been deleted from srcBucket, and exists in dstBucket + if err := db.View(func(tx *bolt.Tx) error { + if !tc.srcBucketIsRootBucket { + srcBucket := tx.Bucket([]byte(tc.srcBucketName)) + if srcBucket == nil { + t.Fatalf("src bucket %s does not exist: %v", tc.srcBucketName, berrors.ErrBucketNotFound) + } + + srcCur := srcBucket.Cursor() + k, _ := srcCur.Seek([]byte(tc.subBucketName)) + if bytes.Equal([]byte(tc.subBucketName), k) { + t.Fatalf("key %q still exists in the srcBucket %s : %v", tc.subBucketName, tc.srcBucketName, berrors.ErrIncompatibleValue) + } + } else { + subBucket := tx.Bucket([]byte(tc.subBucketName)) + if subBucket != nil { + t.Fatalf("expected subBucket '%s' to be moved, but still exist in rootBucket", tc.subBucketName) + } + } + + if !tc.dstBucketIsRootBucket { + dstBucket := tx.Bucket([]byte(tc.dstBucketName)) + if dstBucket == nil { + t.Fatalf("dst bucket %s does not exist: %v", tc.dstBucketName, berrors.ErrBucketNotFound) + } + + v := dstBucket.Get([]byte(tc.subBucketName)) + if v != nil { + t.Fatalf("expected nil value, as the key is nested bucket, got %v instead", v) + } + subBucket := dstBucket.Bucket([]byte(tc.subBucketName)) + v = subBucket.Get([]byte(tc.subBucketKey)) + if v == nil { + t.Fatalf("expected value %v, but got %v instead", tc.subBucketValue, v) + } + } else { + subBucket := tx.Bucket([]byte(tc.subBucketName)) + if subBucket == nil { + t.Fatalf("dst bucket %s does not exist in rootBucket: %v", tc.subBucketName, berrors.ErrBucketNotFound) + } + + v := subBucket.Get([]byte(tc.subBucketKey)) + if v == nil { + t.Fatalf("expected value %v, but got %v instead", tc.subBucketValue, v) + } + } + + return nil + }); err != nil { + t.Fatal(err) + } + db.MustCheck() + }) + } +} + func TestBucket_MoveBucket(t *testing.T) { testCases := []struct { name string @@ -30,8 +309,10 @@ func TestBucket_MoveBucket(t *testing.T) { } for _, tc := range testCases { + t.Run(tc.name, func(*testing.T) { db := btesting.MustCreateDB(t) + var tmpFile string // arrange if err := db.Update(func(tx *bolt.Tx) error { @@ -46,15 +327,13 @@ func TestBucket_MoveBucket(t *testing.T) { db.MustCheck() // act - //var tmpFile string if err := db.Update(func(tx *bolt.Tx) error { srcBucket := retrieveParentBucket(t, tx, tc.srcBucketPath...) dstBucket := retrieveChildBucket(t, tx, tc.dstBucketPath...) bucketToMove := tc.srcBucketPath[len(tc.srcBucketPath)-1] - //// dump bucketToMove to bbolt file for assertion - //bk := srcBucket.Bucket([]byte(bucketToMove)) - //tmpFile = dumpBucketToFile(t, bk) + // dump db before moving the bucket for verification later + tmpFile = dumpDBToFile(t, tx) mErr := srcBucket.MoveBucket([]byte(bucketToMove), dstBucket) require.Equal(t, tc.expErr, mErr) @@ -81,14 +360,11 @@ func TestBucket_MoveBucket(t *testing.T) { childBucket := dstBucket.Bucket([]byte(bucketToMove)) if childBucket == nil { - t.Fatalf("expected subBucket %v to exist within dstBucket %v", bucketToMove, dstBucket) + t.Fatalf("expected childBucket %v to exist within dstBucket %v", bucketToMove, dstBucket) } - //bucketOnDisk, err := os.ReadFile(tmpFile) - //if err != nil { - // t.Fatalf("error reading tmp file %v", tmpFile) - //} - //require.Equal(t, bucketOnDisk, childBucket.Write()) + dbAsBytes := readDBFromFile(t, tmpFile) + require.Equal(t, dbAsBytes, encodeDBIntoBytes(t, tx)) return nil }); err != nil { @@ -98,6 +374,7 @@ func TestBucket_MoveBucket(t *testing.T) { }) } } + func createBucketIfNotExist(t testing.TB, tx *bolt.Tx, paths ...string) *bolt.Bucket { t.Helper() @@ -135,28 +412,47 @@ func retrieveChildBucket(t testing.TB, tx *bolt.Tx, paths ...string) *bolt.Bucke t.Fatalf("error retrieving bucket %v within paths %v", path, strings.TrimSuffix(strings.Join(paths, "->"), "->")) } } + return bk } -func dumpBucketToFile(t testing.TB, bk *bolt.Bucket) string { +func dumpDBToFile(t testing.TB, tx *bolt.Tx) string { tmpFile := tempfile() f, err := os.Create(tmpFile) if err != nil { - t.Fatalf("error creating tmp file %v: %v", tmpFile, err) + t.Fatalf("error creating file %v: %v", tmpFile, err) } defer f.Close() - data := bk.Write() - _, err = f.Write(data) + // dump to file + _, err = tx.WriteTo(f) if err != nil { - t.Fatalf("error writing bucket %v to file %v tmpFile", bk, tmpFile) + t.Fatalf("error writing db %v to file %v: %v", tx.DB(), tmpFile, err) } return tmpFile } -func insertRandKeysValuesBucket(t testing.TB, bk *bolt.Bucket, n int) { +func readDBFromFile(t testing.TB, tmpFile string) []byte { + data, err := os.ReadFile(tmpFile) + if err != nil { + t.Fatalf("error reading temp file %v", tmpFile) + } + + return data +} + +func encodeDBIntoBytes(t testing.TB, tx *bolt.Tx) []byte { + var data bytes.Buffer + _, err := tx.WriteTo(&data) + if err != nil { + t.Fatalf("error writing db to buffer: %v", err) + } + + return data.Bytes() +} +func insertRandKeysValuesBucket(t testing.TB, bk *bolt.Bucket, n int) { var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") var min, max = 1, 1024 @@ -181,5 +477,4 @@ func insertRandKeysValuesBucket(t testing.TB, bk *bolt.Bucket, n int) { t.Fatalf("error inserting key %v and value %v in bucket %v: %v", string(keyData), string(valData), bk.String(), pErr) } } - } diff --git a/tx_test.go b/tx_test.go index 52cbb02f5..cc59804b2 100644 --- a/tx_test.go +++ b/tx_test.go @@ -433,283 +433,6 @@ func TestTx_DeleteBucket_NotFound(t *testing.T) { } } -func TestTx_MoveBucket(t *testing.T) { - testCases := []struct { - name string - srcBucketName string - subBucketName string - subBucketKey string - subBucketValue string - dstBucketName string - subBucketExistSrcBucket bool - subBucketExistDstBucket bool - keyNoSubBucketDstBucket bool - srcBucketIsRootBucket bool - dstBucketIsRootBucket bool - expErr error - }{ - { - "happy path", - "srcBucket", - "subBucket", - "this is subBucket key", - "this is subBucket value", - "dstBucket", - true, - false, - false, - false, - false, - nil, - }, - { - "subBucket not exist in src bucket", - "srcBucket", - "subBucket", - "this is subBucket key", - "this is subBucket value", - "dstBucket", - false, - false, - false, - false, - false, - berrors.ErrBucketNotFound, - }, - { - "suBucket exist in dstBucket", - "srcBucket", - "subBucket", - "this is subBucket key", - "this is subBucket value", - "dstBucket", - true, - true, - false, - false, - false, - berrors.ErrBucketExists, - }, - { - "subBucket key exist in dstBucket, but no subBucket value", - "srcBucket", - "subBucket", - "this is subBucket key", - "this is subBucket value", - "dstBucket", - true, - false, - true, - false, - false, - berrors.ErrIncompatibleValue, - }, - { - "srcBucket is RootBucket", - "srcBucket", - "subBucket", - "this is subBucket key", - "this is subBucket value", - "dstBucket", - true, - false, - false, - true, - false, - nil, - }, - { - "dstBucket is RootBucket", - "srcBucket", - "subBucket", - "this is subBucket key", - "this is subBucket value", - "dstBucket", - true, - false, - false, - false, - true, - nil, - }, - { - "srcBucket is RootBucket, and dstBucket is RootBucket, corner case", - "srcBucket", - "subBucket", - "this is subBucket key", - "this is subBucket value", - "dstBucket", - true, - true, - false, - true, - true, - nil, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - db := btesting.MustCreateDB(t) - - // arrange - if err := db.Update(func(tx *bolt.Tx) error { - var srcBucket *bolt.Bucket = nil - var bErr error = nil - var subBucket *bolt.Bucket = nil - var sErr error = nil - - if !tc.srcBucketIsRootBucket { - // create src bucket - srcBucket, bErr = tx.CreateBucketIfNotExists([]byte(tc.srcBucketName)) - if bErr != nil { - t.Fatalf("error creating src bucket %s: %v", tc.srcBucketName, bErr) - } - // insert K/V pair into src bucket - if pErr := srcBucket.Put([]byte("foo"), []byte("0000")); pErr != nil { - t.Fatal(pErr) - } - } - - if tc.subBucketExistSrcBucket { - if tc.srcBucketIsRootBucket { - // create subBucket within root bucket - subBucket, sErr = tx.CreateBucket([]byte(tc.subBucketName)) - if sErr != nil { - t.Fatalf("error creating subBucket %s within root bucket: %v", tc.subBucketName, sErr) - } - } else { - // create subBucket within srcBucket - subBucket, sErr = srcBucket.CreateBucket([]byte(tc.subBucketName)) - if sErr != nil { - t.Fatalf("error creating subBucket %s within srcBucket %s: %v", tc.subBucketName, tc.srcBucketName, sErr) - } - } - // insert K/V pair into subBucket - if pErr := subBucket.Put([]byte(tc.subBucketKey), []byte(tc.subBucketValue)); pErr != nil { - t.Fatal(pErr) - } - } - - // create dst bucket - if !tc.dstBucketIsRootBucket { - dstBucket, bErr := tx.CreateBucketIfNotExists([]byte(tc.dstBucketName)) - if bErr != nil { - t.Fatalf("error creating dst bucket %s: %v", tc.dstBucketName, bErr) - } - // insert K/V pair into dst bucket - if pErr := dstBucket.Put([]byte("bar"), []byte("0000")); pErr != nil { - t.Fatal(pErr) - } - - if tc.subBucketExistDstBucket { - // create subBucket within dstBucket - _, sErr := dstBucket.CreateBucket([]byte(tc.subBucketName)) - if sErr != nil { - t.Fatal(sErr) - } - } - - if tc.keyNoSubBucketDstBucket { - if pErr := dstBucket.Put([]byte(tc.subBucketName), []byte(tc.subBucketValue)); pErr != nil { - t.Fatal(pErr) - } - } - } - - return nil - }); err != nil { - t.Fatal(err) - } - db.MustCheck() - - // act: move the subBucket from srcBucket to dstBucket - if err := db.Update(func(tx *bolt.Tx) error { - srcBucket := tx.Bucket([]byte(tc.srcBucketName)) - if srcBucket == nil && !tc.srcBucketIsRootBucket { - t.Fatalf("src bucket %s does not exist: %v", tc.srcBucketName, berrors.ErrBucketNotFound) - } - - dstBucket := tx.Bucket([]byte(tc.dstBucketName)) - if dstBucket == nil && !tc.dstBucketIsRootBucket { - t.Fatalf("dst bucket %s does not exist: %v", tc.dstBucketName, berrors.ErrBucketNotFound) - } - - mvErr := tx.MoveBucket([]byte(tc.subBucketName), srcBucket, dstBucket) - if !tc.subBucketExistSrcBucket || tc.subBucketExistDstBucket || tc.keyNoSubBucketDstBucket { - require.ErrorIs(t, mvErr, tc.expErr) - } else if mvErr != nil { - t.Fatalf("failed to move subBucket '%v' from root bucket to dstBucket '%v': %v", tc.subBucketName, tc.dstBucketName, mvErr) - } - - return nil - - }); err != nil { - t.Fatal(err) - } - db.MustCheck() - - // skip assertion in these cases - if !tc.subBucketExistSrcBucket || tc.subBucketExistDstBucket || tc.keyNoSubBucketDstBucket { - return - } - - // assert: check subBucket has been deleted from srcBucket, and exists in dstBucket - if err := db.View(func(tx *bolt.Tx) error { - if !tc.srcBucketIsRootBucket { - srcBucket := tx.Bucket([]byte(tc.srcBucketName)) - if srcBucket == nil { - t.Fatalf("src bucket %s does not exist: %v", tc.srcBucketName, berrors.ErrBucketNotFound) - } - - srcCur := srcBucket.Cursor() - k, _ := srcCur.Seek([]byte(tc.subBucketName)) - if bytes.Equal([]byte(tc.subBucketName), k) { - t.Fatalf("key %q still exists in the srcBucket %s : %v", tc.subBucketName, tc.srcBucketName, berrors.ErrIncompatibleValue) - } - } else { - subBucket := tx.Bucket([]byte(tc.subBucketName)) - if subBucket != nil { - t.Fatalf("expected subBucket '%s' to be moved, but still exist in rootBucket", tc.subBucketName) - } - } - - if !tc.dstBucketIsRootBucket { - dstBucket := tx.Bucket([]byte(tc.dstBucketName)) - if dstBucket == nil { - t.Fatalf("dst bucket %s does not exist: %v", tc.dstBucketName, berrors.ErrBucketNotFound) - } - - v := dstBucket.Get([]byte(tc.subBucketName)) - if v != nil { - t.Fatalf("expected nil value, as the key is nested bucket, got %v instead", v) - } - subBucket := dstBucket.Bucket([]byte(tc.subBucketName)) - v = subBucket.Get([]byte(tc.subBucketKey)) - if v == nil { - t.Fatalf("expected value %v, but got %v instead", tc.subBucketValue, v) - } - } else { - subBucket := tx.Bucket([]byte(tc.subBucketName)) - if subBucket == nil { - t.Fatalf("dst bucket %s does not exist in rootBucket: %v", tc.subBucketName, berrors.ErrBucketNotFound) - } - - v := subBucket.Get([]byte(tc.subBucketKey)) - if v == nil { - t.Fatalf("expected value %v, but got %v instead", tc.subBucketValue, v) - } - } - - return nil - }); err != nil { - t.Fatal(err) - } - db.MustCheck() - }) - } -} - // Ensure that no error is returned when a tx.ForEach function does not return // an error. func TestTx_ForEach_NoError(t *testing.T) {