aboutsummaryrefslogtreecommitdiff
path: root/tx_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'tx_test.go')
-rw-r--r--tx_test.go413
1 files changed, 87 insertions, 326 deletions
diff --git a/tx_test.go b/tx_test.go
index 299cccc..036c0c5 100644
--- a/tx_test.go
+++ b/tx_test.go
@@ -5,27 +5,25 @@ import (
"fmt"
"math/rand"
"os"
- "sort"
"strconv"
"strings"
"testing"
- "testing/quick"
"github.com/stretchr/testify/assert"
)
// Ensure that committing a closed transaction returns an error.
-func TestTxCommitClosed(t *testing.T) {
+func TestTx_Commit_Closed(t *testing.T) {
withOpenDB(func(db *DB, path string) {
tx, _ := db.Begin(true)
- tx.CreateBucket("foo")
+ tx.CreateBucket([]byte("foo"))
assert.NoError(t, tx.Commit())
assert.Equal(t, tx.Commit(), ErrTxClosed)
})
}
// Ensure that rolling back a closed transaction returns an error.
-func TestTxRollbackClosed(t *testing.T) {
+func TestTx_Rollback_Closed(t *testing.T) {
withOpenDB(func(db *DB, path string) {
tx, _ := db.Begin(true)
assert.NoError(t, tx.Rollback())
@@ -34,114 +32,69 @@ func TestTxRollbackClosed(t *testing.T) {
}
// Ensure that committing a read-only transaction returns an error.
-func TestTxCommitReadOnly(t *testing.T) {
+func TestTx_Commit_ReadOnly(t *testing.T) {
withOpenDB(func(db *DB, path string) {
tx, _ := db.Begin(false)
assert.Equal(t, tx.Commit(), ErrTxNotWritable)
})
}
-// Ensure that the database can retrieve a list of buckets.
-func TestTxBuckets(t *testing.T) {
- withOpenDB(func(db *DB, path string) {
- db.Update(func(tx *Tx) error {
- tx.CreateBucket("foo")
- tx.CreateBucket("bar")
- tx.CreateBucket("baz")
- buckets := tx.Buckets()
- if assert.Equal(t, len(buckets), 3) {
- assert.Equal(t, buckets[0].Name(), "bar")
- assert.Equal(t, buckets[1].Name(), "baz")
- assert.Equal(t, buckets[2].Name(), "foo")
- }
- return nil
- })
- })
-}
-
// Ensure that creating a bucket with a read-only transaction returns an error.
-func TestTxCreateBucketReadOnly(t *testing.T) {
+func TestTx_CreateBucket_ReadOnly(t *testing.T) {
withOpenDB(func(db *DB, path string) {
db.View(func(tx *Tx) error {
- assert.Equal(t, tx.CreateBucket("foo"), ErrTxNotWritable)
+ assert.Equal(t, tx.CreateBucket([]byte("foo")), ErrTxNotWritable)
return nil
})
})
}
// Ensure that creating a bucket on a closed transaction returns an error.
-func TestTxCreateBucketClosed(t *testing.T) {
+func TestTx_CreateBucket_Closed(t *testing.T) {
withOpenDB(func(db *DB, path string) {
tx, _ := db.Begin(true)
tx.Commit()
- assert.Equal(t, tx.CreateBucket("foo"), ErrTxClosed)
+ assert.Equal(t, tx.CreateBucket([]byte("foo")), ErrTxClosed)
})
}
// Ensure that a Tx can retrieve a bucket.
-func TestTxBucket(t *testing.T) {
+func TestTx_Bucket(t *testing.T) {
withOpenDB(func(db *DB, path string) {
db.Update(func(tx *Tx) error {
- tx.CreateBucket("widgets")
- b := tx.Bucket("widgets")
- if assert.NotNil(t, b) {
- assert.Equal(t, "widgets", b.Name())
- }
+ tx.CreateBucket([]byte("widgets"))
+ b := tx.Bucket([]byte("widgets"))
+ assert.NotNil(t, b)
return nil
})
})
}
// Ensure that a Tx retrieving a non-existent key returns nil.
-func TestTxGetMissing(t *testing.T) {
+func TestTx_Get_Missing(t *testing.T) {
withOpenDB(func(db *DB, path string) {
db.Update(func(tx *Tx) error {
- tx.CreateBucket("widgets")
- tx.Bucket("widgets").Put([]byte("foo"), []byte("bar"))
- value := tx.Bucket("widgets").Get([]byte("no_such_key"))
+ tx.CreateBucket([]byte("widgets"))
+ tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
+ value := tx.Bucket([]byte("widgets")).Get([]byte("no_such_key"))
assert.Nil(t, value)
return nil
})
})
}
-// Ensure that retrieving all buckets returns writable buckets.
-func TestTxWritableBuckets(t *testing.T) {
- withOpenDB(func(db *DB, path string) {
- db.Update(func(tx *Tx) error {
- tx.CreateBucket("widgets")
- tx.CreateBucket("woojits")
- return nil
- })
- db.Update(func(tx *Tx) error {
- buckets := tx.Buckets()
- assert.Equal(t, len(buckets), 2)
- assert.Equal(t, buckets[0].Name(), "widgets")
- assert.Equal(t, buckets[1].Name(), "woojits")
- buckets[0].Put([]byte("foo"), []byte("0000"))
- buckets[1].Put([]byte("bar"), []byte("0001"))
- return nil
- })
- db.View(func(tx *Tx) error {
- assert.Equal(t, []byte("0000"), tx.Bucket("widgets").Get([]byte("foo")))
- assert.Equal(t, []byte("0001"), tx.Bucket("woojits").Get([]byte("bar")))
- return nil
- })
- })
-}
-
// Ensure that a bucket can be created and retrieved.
-func TestTxCreateBucket(t *testing.T) {
+func TestTx_CreateBucket(t *testing.T) {
withOpenDB(func(db *DB, path string) {
// Create a bucket.
db.Update(func(tx *Tx) error {
- assert.NoError(t, tx.CreateBucket("widgets"))
+ assert.NoError(t, tx.CreateBucket([]byte("widgets")))
return nil
})
// Read the bucket through a separate transaction.
db.View(func(tx *Tx) error {
- b := tx.Bucket("widgets")
+ b := tx.Bucket([]byte("widgets"))
assert.NotNil(t, b)
return nil
})
@@ -149,18 +102,19 @@ func TestTxCreateBucket(t *testing.T) {
}
// Ensure that a bucket can be created if it doesn't already exist.
-func TestTxCreateBucketIfNotExists(t *testing.T) {
+func TestTx_CreateBucketIfNotExists(t *testing.T) {
withOpenDB(func(db *DB, path string) {
db.Update(func(tx *Tx) error {
- assert.NoError(t, tx.CreateBucketIfNotExists("widgets"))
- assert.NoError(t, tx.CreateBucketIfNotExists("widgets"))
- assert.Equal(t, tx.CreateBucketIfNotExists(""), ErrBucketNameRequired)
+ assert.NoError(t, tx.CreateBucketIfNotExists([]byte("widgets")))
+ assert.NoError(t, tx.CreateBucketIfNotExists([]byte("widgets")))
+ assert.Equal(t, ErrBucketNameRequired, tx.CreateBucketIfNotExists([]byte{}))
+ assert.Equal(t, ErrBucketNameRequired, tx.CreateBucketIfNotExists(nil))
return nil
})
// Read the bucket through a separate transaction.
db.View(func(tx *Tx) error {
- b := tx.Bucket("widgets")
+ b := tx.Bucket([]byte("widgets"))
assert.NotNil(t, b)
return nil
})
@@ -168,64 +122,53 @@ func TestTxCreateBucketIfNotExists(t *testing.T) {
}
// Ensure that a bucket cannot be created twice.
-func TestTxRecreateBucket(t *testing.T) {
+func TestTx_CreateBucket_Exists(t *testing.T) {
withOpenDB(func(db *DB, path string) {
// Create a bucket.
db.Update(func(tx *Tx) error {
- assert.NoError(t, tx.CreateBucket("widgets"))
+ assert.NoError(t, tx.CreateBucket([]byte("widgets")))
return nil
})
// Create the same bucket again.
db.Update(func(tx *Tx) error {
- assert.Equal(t, ErrBucketExists, tx.CreateBucket("widgets"))
+ assert.Equal(t, ErrBucketExists, tx.CreateBucket([]byte("widgets")))
return nil
})
})
}
// Ensure that a bucket is created with a non-blank name.
-func TestTxCreateBucketWithoutName(t *testing.T) {
+func TestTx_CreateBucket_NameRequired(t *testing.T) {
withOpenDB(func(db *DB, path string) {
db.Update(func(tx *Tx) error {
- assert.Equal(t, ErrBucketNameRequired, tx.CreateBucket(""))
- return nil
- })
- })
-}
-
-// Ensure that a bucket name is not too long.
-func TestTxCreateBucketWithLongName(t *testing.T) {
- withOpenDB(func(db *DB, path string) {
- db.Update(func(tx *Tx) error {
- assert.NoError(t, tx.CreateBucket(strings.Repeat("X", 255)))
- assert.Equal(t, ErrBucketNameTooLarge, tx.CreateBucket(strings.Repeat("X", 256)))
+ assert.Equal(t, ErrBucketNameRequired, tx.CreateBucket(nil))
return nil
})
})
}
// Ensure that a bucket can be deleted.
-func TestTxDeleteBucket(t *testing.T) {
+func TestTx_DeleteBucket(t *testing.T) {
withOpenDB(func(db *DB, path string) {
// Create a bucket and add a value.
db.Update(func(tx *Tx) error {
- tx.CreateBucket("widgets")
- tx.Bucket("widgets").Put([]byte("foo"), []byte("bar"))
+ tx.CreateBucket([]byte("widgets"))
+ tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
return nil
})
// Save root page id.
var root pgid
db.View(func(tx *Tx) error {
- root = tx.Bucket("widgets").root
+ root = tx.Bucket([]byte("widgets")).root
return nil
})
// Delete the bucket and make sure we can't get the value.
db.Update(func(tx *Tx) error {
- assert.NoError(t, tx.DeleteBucket("widgets"))
- assert.Nil(t, tx.Bucket("widgets"))
+ assert.NoError(t, tx.DeleteBucket([]byte("widgets")))
+ assert.Nil(t, tx.Bucket([]byte("widgets")))
return nil
})
@@ -234,257 +177,42 @@ func TestTxDeleteBucket(t *testing.T) {
assert.Equal(t, []pgid{7, 6, root, 2}, db.freelist.all())
// Create the bucket again and make sure there's not a phantom value.
- assert.NoError(t, tx.CreateBucket("widgets"))
- assert.Nil(t, tx.Bucket("widgets").Get([]byte("foo")))
+ assert.NoError(t, tx.CreateBucket([]byte("widgets")))
+ assert.Nil(t, tx.Bucket([]byte("widgets")).Get([]byte("foo")))
return nil
})
})
}
// Ensure that deleting a bucket on a closed transaction returns an error.
-func TestTxDeleteBucketClosed(t *testing.T) {
+func TestTx_DeleteBucket_Closed(t *testing.T) {
withOpenDB(func(db *DB, path string) {
tx, _ := db.Begin(true)
tx.Commit()
- assert.Equal(t, tx.DeleteBucket("foo"), ErrTxClosed)
+ assert.Equal(t, tx.DeleteBucket([]byte("foo")), ErrTxClosed)
})
}
// Ensure that deleting a bucket with a read-only transaction returns an error.
-func TestTxDeleteBucketReadOnly(t *testing.T) {
+func TestTx_DeleteBucket_ReadOnly(t *testing.T) {
withOpenDB(func(db *DB, path string) {
db.View(func(tx *Tx) error {
- assert.Equal(t, tx.DeleteBucket("foo"), ErrTxNotWritable)
- return nil
- })
- })
-}
-
-// Ensure that an error is returned when deleting from a bucket that doesn't exist.
-func TestTxDeleteBucketNotFound(t *testing.T) {
- withOpenDB(func(db *DB, path string) {
- db.Update(func(tx *Tx) error {
- assert.Equal(t, ErrBucketNotFound, tx.DeleteBucket("widgets"))
+ assert.Equal(t, tx.DeleteBucket([]byte("foo")), ErrTxNotWritable)
return nil
})
})
}
-// Ensure that a Tx cursor can iterate over an empty bucket without error.
-func TestTxCursorEmptyBucket(t *testing.T) {
+// Ensure that nothing happens when deleting a bucket that doesn't exist.
+func TestTx_DeleteBucket_NotFound(t *testing.T) {
withOpenDB(func(db *DB, path string) {
db.Update(func(tx *Tx) error {
- return tx.CreateBucket("widgets")
- })
- db.View(func(tx *Tx) error {
- c := tx.Bucket("widgets").Cursor()
- k, v := c.First()
- assert.Nil(t, k)
- assert.Nil(t, v)
+ assert.Equal(t, ErrBucketNotFound, tx.DeleteBucket([]byte("widgets")))
return nil
})
})
}
-// Ensure that a Tx cursor can reverse iterate over an empty bucket without error.
-func TestCursorEmptyBucketReverse(t *testing.T) {
- withOpenDB(func(db *DB, path string) {
- db.Update(func(tx *Tx) error {
- return tx.CreateBucket("widgets")
- })
- db.View(func(tx *Tx) error {
- c := tx.Bucket("widgets").Cursor()
- k, v := c.Last()
- assert.Nil(t, k)
- assert.Nil(t, v)
- return nil
- })
- })
-}
-
-// Ensure that a Tx cursor can iterate over a single root with a couple elements.
-func TestTxCursorLeafRoot(t *testing.T) {
- withOpenDB(func(db *DB, path string) {
- db.Update(func(tx *Tx) error {
- tx.CreateBucket("widgets")
- tx.Bucket("widgets").Put([]byte("baz"), []byte{})
- tx.Bucket("widgets").Put([]byte("foo"), []byte{0})
- tx.Bucket("widgets").Put([]byte("bar"), []byte{1})
- return nil
- })
- tx, _ := db.Begin(false)
- c := tx.Bucket("widgets").Cursor()
-
- k, v := c.First()
- assert.Equal(t, string(k), "bar")
- assert.Equal(t, v, []byte{1})
-
- k, v = c.Next()
- assert.Equal(t, string(k), "baz")
- assert.Equal(t, v, []byte{})
-
- k, v = c.Next()
- assert.Equal(t, string(k), "foo")
- assert.Equal(t, v, []byte{0})
-
- k, v = c.Next()
- assert.Nil(t, k)
- assert.Nil(t, v)
-
- k, v = c.Next()
- assert.Nil(t, k)
- assert.Nil(t, v)
-
- tx.Rollback()
- })
-}
-
-// Ensure that a Tx cursor can iterate in reverse over a single root with a couple elements.
-func TestTxCursorLeafRootReverse(t *testing.T) {
- withOpenDB(func(db *DB, path string) {
- db.Update(func(tx *Tx) error {
- tx.CreateBucket("widgets")
- tx.Bucket("widgets").Put([]byte("baz"), []byte{})
- tx.Bucket("widgets").Put([]byte("foo"), []byte{0})
- tx.Bucket("widgets").Put([]byte("bar"), []byte{1})
- return nil
- })
- tx, _ := db.Begin(false)
- c := tx.Bucket("widgets").Cursor()
-
- k, v := c.Last()
- assert.Equal(t, string(k), "foo")
- assert.Equal(t, v, []byte{0})
-
- k, v = c.Prev()
- assert.Equal(t, string(k), "baz")
- assert.Equal(t, v, []byte{})
-
- k, v = c.Prev()
- assert.Equal(t, string(k), "bar")
- assert.Equal(t, v, []byte{1})
-
- k, v = c.Prev()
- assert.Nil(t, k)
- assert.Nil(t, v)
-
- k, v = c.Prev()
- assert.Nil(t, k)
- assert.Nil(t, v)
-
- tx.Rollback()
- })
-}
-
-// Ensure that a Tx cursor can restart from the beginning.
-func TestTxCursorRestart(t *testing.T) {
- withOpenDB(func(db *DB, path string) {
- db.Update(func(tx *Tx) error {
- tx.CreateBucket("widgets")
- tx.Bucket("widgets").Put([]byte("bar"), []byte{})
- tx.Bucket("widgets").Put([]byte("foo"), []byte{})
- return nil
- })
-
- tx, _ := db.Begin(false)
- c := tx.Bucket("widgets").Cursor()
-
- k, _ := c.First()
- assert.Equal(t, string(k), "bar")
-
- k, _ = c.Next()
- assert.Equal(t, string(k), "foo")
-
- k, _ = c.First()
- assert.Equal(t, string(k), "bar")
-
- k, _ = c.Next()
- assert.Equal(t, string(k), "foo")
-
- tx.Rollback()
- })
-}
-
-// Ensure that a Tx can iterate over all elements in a bucket.
-func TestTxCursorIterate(t *testing.T) {
- if testing.Short() {
- t.Skip("skipping test in short mode.")
- }
-
- f := func(items testdata) bool {
- withOpenDB(func(db *DB, path string) {
- // Bulk insert all values.
- tx, _ := db.Begin(true)
- tx.CreateBucket("widgets")
- b := tx.Bucket("widgets")
- for _, item := range items {
- assert.NoError(t, b.Put(item.Key, item.Value))
- }
- assert.NoError(t, tx.Commit())
-
- // Sort test data.
- sort.Sort(items)
-
- // Iterate over all items and check consistency.
- var index = 0
- tx, _ = db.Begin(false)
- c := tx.Bucket("widgets").Cursor()
- for k, v := c.First(); k != nil && index < len(items); k, v = c.Next() {
- assert.Equal(t, k, items[index].Key)
- assert.Equal(t, v, items[index].Value)
- index++
- }
- assert.Equal(t, len(items), index)
- tx.Rollback()
- })
- return true
- }
- if err := quick.Check(f, qconfig()); err != nil {
- t.Error(err)
- }
- fmt.Fprint(os.Stderr, "\n")
-}
-
-// Ensure that a transaction can iterate over all elements in a bucket in reverse.
-func TestTxCursorIterateReverse(t *testing.T) {
- if testing.Short() {
- t.Skip("skipping test in short mode.")
- }
-
- f := func(items testdata) bool {
- withOpenDB(func(db *DB, path string) {
- // Bulk insert all values.
- tx, _ := db.Begin(true)
- tx.CreateBucket("widgets")
- b := tx.Bucket("widgets")
- for _, item := range items {
- assert.NoError(t, b.Put(item.Key, item.Value))
- }
- assert.NoError(t, tx.Commit())
-
- // Sort test data.
- sort.Sort(revtestdata(items))
-
- // Iterate over all items and check consistency.
- var index = 0
- tx, _ = db.Begin(false)
- c := tx.Bucket("widgets").Cursor()
- for k, v := c.Last(); k != nil && index < len(items); k, v = c.Prev() {
- assert.Equal(t, k, items[index].Key)
- assert.Equal(t, v, items[index].Value)
- index++
- }
- assert.Equal(t, len(items), index)
- tx.Rollback()
- })
- return true
- }
- if err := quick.Check(f, qconfig()); err != nil {
- t.Error(err)
- }
- fmt.Fprint(os.Stderr, "\n")
-}
-
// Ensure that Tx commit handlers are called after a transaction successfully commits.
func TestTx_OnCommit(t *testing.T) {
var x int
@@ -492,7 +220,7 @@ func TestTx_OnCommit(t *testing.T) {
db.Update(func(tx *Tx) error {
tx.OnCommit(func() { x += 1 })
tx.OnCommit(func() { x += 2 })
- return tx.CreateBucket("widgets")
+ return tx.CreateBucket([]byte("widgets"))
})
})
assert.Equal(t, 3, x)
@@ -505,7 +233,7 @@ func TestTx_OnCommit_Rollback(t *testing.T) {
db.Update(func(tx *Tx) error {
tx.OnCommit(func() { x += 1 })
tx.OnCommit(func() { x += 2 })
- tx.CreateBucket("widgets")
+ tx.CreateBucket([]byte("widgets"))
return errors.New("rollback this commit")
})
})
@@ -526,8 +254,8 @@ func benchmarkTxCursor(b *testing.B, total int) {
withOpenDB(func(db *DB, path string) {
// Write data to bucket.
db.Update(func(tx *Tx) error {
- tx.CreateBucket("widgets")
- bucket := tx.Bucket("widgets")
+ tx.CreateBucket([]byte("widgets"))
+ bucket := tx.Bucket([]byte("widgets"))
for i := 0; i < total; i++ {
bucket.Put([]byte(fmt.Sprintf("%016d", indexes[i])), value)
}
@@ -539,7 +267,7 @@ func benchmarkTxCursor(b *testing.B, total int) {
for i := 0; i < b.N; i++ {
db.View(func(tx *Tx) error {
count := 0
- c := tx.Bucket("widgets").Cursor()
+ c := tx.Bucket([]byte("widgets")).Cursor()
for k, _ := c.First(); k != nil; k, _ = c.Next() {
count++
}
@@ -564,7 +292,7 @@ func benchmarkTxPutRandom(b *testing.B, total int) {
value := []byte(strings.Repeat("0", 64))
withOpenDB(func(db *DB, path string) {
db.Update(func(tx *Tx) error {
- return tx.CreateBucket("widgets")
+ return tx.CreateBucket([]byte("widgets"))
})
var tx *Tx
var bucket *Bucket
@@ -575,7 +303,7 @@ func benchmarkTxPutRandom(b *testing.B, total int) {
tx.Commit()
}
tx, _ = db.Begin(true)
- bucket = tx.Bucket("widgets")
+ bucket = tx.Bucket([]byte("widgets"))
}
bucket.Put([]byte(strconv.Itoa(indexes[i])), value)
}
@@ -595,10 +323,10 @@ func benchmarkTxPutSequential(b *testing.B, total int) {
value := []byte(strings.Repeat("0", 64))
withOpenDB(func(db *DB, path string) {
db.Update(func(tx *Tx) error {
- return tx.CreateBucket("widgets")
+ return tx.CreateBucket([]byte("widgets"))
})
db.Update(func(tx *Tx) error {
- bucket := tx.Bucket("widgets")
+ bucket := tx.Bucket([]byte("widgets"))
for j := 0; j < b.N; j++ {
for i := 0; i < total; i++ {
bucket.Put([]byte(strconv.Itoa(i)), value)
@@ -608,3 +336,36 @@ func benchmarkTxPutSequential(b *testing.B, total int) {
})
})
}
+
+func ExampleTx_Rollback() {
+ // Open the database.
+ db, _ := Open(tempfile(), 0666)
+ defer os.Remove(db.Path())
+ defer db.Close()
+
+ // Create a bucket.
+ db.Update(func(tx *Tx) error {
+ return tx.CreateBucket([]byte("widgets"))
+ })
+
+ // Set a value for a key.
+ db.Update(func(tx *Tx) error {
+ return tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
+ })
+
+ // Update the key but rollback the transaction so it never saves.
+ tx, _ := db.Begin(true)
+ b := tx.Bucket([]byte("widgets"))
+ b.Put([]byte("foo"), []byte("baz"))
+ tx.Rollback()
+
+ // Ensure that our original value is still set.
+ db.View(func(tx *Tx) error {
+ value := tx.Bucket([]byte("widgets")).Get([]byte("foo"))
+ fmt.Printf("The value for 'foo' is still: %s\n", string(value))
+ return nil
+ })
+
+ // Output:
+ // The value for 'foo' is still: bar
+}