package bolt_test import ( "bytes" "errors" "fmt" "log" "os" "testing" "github.com/boltdb/bolt" ) // Ensure that committing a closed transaction returns an error. func TestTx_Commit_ErrTxClosed(t *testing.T) { db := MustOpenDB() defer db.MustClose() tx, err := db.Begin(true) if err != nil { t.Fatal(err) } if _, err := tx.CreateBucket([]byte("foo")); err != nil { t.Fatal(err) } if err := tx.Commit(); err != nil { t.Fatal(err) } if err := tx.Commit(); err != bolt.ErrTxClosed { t.Fatalf("unexpected error: %s", err) } } // Ensure that rolling back a closed transaction returns an error. func TestTx_Rollback_ErrTxClosed(t *testing.T) { db := MustOpenDB() defer db.MustClose() tx, err := db.Begin(true) if err != nil { t.Fatal(err) } if err := tx.Rollback(); err != nil { t.Fatal(err) } if err := tx.Rollback(); err != bolt.ErrTxClosed { t.Fatalf("unexpected error: %s", err) } } // Ensure that committing a read-only transaction returns an error. func TestTx_Commit_ErrTxNotWritable(t *testing.T) { db := MustOpenDB() defer db.MustClose() tx, err := db.Begin(false) if err != nil { t.Fatal(err) } if err := tx.Commit(); err != bolt.ErrTxNotWritable { t.Fatal(err) } } // Ensure that a transaction can retrieve a cursor on the root bucket. func TestTx_Cursor(t *testing.T) { db := MustOpenDB() defer db.MustClose() if err := db.Update(func(tx *bolt.Tx) error { if _, err := tx.CreateBucket([]byte("widgets")); err != nil { t.Fatal(err) } if _, err := tx.CreateBucket([]byte("woojits")); err != nil { t.Fatal(err) } c := tx.Cursor() if k, v := c.First(); !bytes.Equal(k, []byte("widgets")) { t.Fatalf("unexpected key: %v", k) } else if v != nil { t.Fatalf("unexpected value: %v", v) } if k, v := c.Next(); !bytes.Equal(k, []byte("woojits")) { t.Fatalf("unexpected key: %v", k) } else if v != nil { t.Fatalf("unexpected value: %v", v) } if k, v := c.Next(); k != nil { t.Fatalf("unexpected key: %v", k) } else if v != nil { t.Fatalf("unexpected value: %v", k) } return nil }); err != nil { t.Fatal(err) } } // Ensure that creating a bucket with a read-only transaction returns an error. func TestTx_CreateBucket_ErrTxNotWritable(t *testing.T) { db := MustOpenDB() defer db.MustClose() if err := db.View(func(tx *bolt.Tx) error { _, err := tx.CreateBucket([]byte("foo")) if err != bolt.ErrTxNotWritable { t.Fatalf("unexpected error: %s", err) } return nil }); err != nil { t.Fatal(err) } } // Ensure that creating a bucket on a closed transaction returns an error. func TestTx_CreateBucket_ErrTxClosed(t *testing.T) { db := MustOpenDB() defer db.MustClose() tx, err := db.Begin(true) if err != nil { t.Fatal(err) } if err := tx.Commit(); err != nil { t.Fatal(err) } if _, err := tx.CreateBucket([]byte("foo")); err != bolt.ErrTxClosed { t.Fatalf("unexpected error: %s", err) } } // Ensure that a Tx can retrieve a bucket. func TestTx_Bucket(t *testing.T) { db := MustOpenDB() defer db.MustClose() if err := db.Update(func(tx *bolt.Tx) error { if _, err := tx.CreateBucket([]byte("widgets")); err != nil { t.Fatal(err) } if tx.Bucket([]byte("widgets")) == nil { t.Fatal("expected bucket") } return nil }); err != nil { t.Fatal(err) } } // Ensure that a Tx retrieving a non-existent key returns nil. func TestTx_Get_NotFound(t *testing.T) { db := MustOpenDB() defer db.MustClose() 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("bar")); err != nil { t.Fatal(err) } if b.Get([]byte("no_such_key")) != nil { t.Fatal("expected nil value") } return nil }); err != nil { t.Fatal(err) } } // Ensure that a bucket can be created and retrieved. func TestTx_CreateBucket(t *testing.T) { db := MustOpenDB() defer db.MustClose() // Create a bucket. if err := db.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucket([]byte("widgets")) if err != nil { t.Fatal(err) } else if b == nil { t.Fatal("expected bucket") } return nil }); err != nil { t.Fatal(err) } // Read the bucket through a separate transaction. if err := db.View(func(tx *bolt.Tx) error { if tx.Bucket([]byte("widgets")) == nil { t.Fatal("expected bucket") } return nil }); err != nil { t.Fatal(err) } } // Ensure that a bucket can be created if it doesn't already exist. func TestTx_CreateBucketIfNotExists(t *testing.T) { db := MustOpenDB() defer db.MustClose() if err := db.Update(func(tx *bolt.Tx) error { // Create bucket. if b, err := tx.CreateBucketIfNotExists([]byte("widgets")); err != nil { t.Fatal(err) } else if b == nil { t.Fatal("expected bucket") } // Create bucket again. if b, err := tx.CreateBucketIfNotExists([]byte("widgets")); err != nil { t.Fatal(err) } else if b == nil { t.Fatal("expected bucket") } return nil }); err != nil { t.Fatal(err) } // Read the bucket through a separate transaction. if err := db.View(func(tx *bolt.Tx) error { if tx.Bucket([]byte("widgets")) == nil { t.Fatal("expected bucket") } return nil }); err != nil { t.Fatal(err) } } // Ensure transaction returns an error if creating an unnamed bucket. func TestTx_CreateBucketIfNotExists_ErrBucketNameRequired(t *testing.T) { db := MustOpenDB() defer db.MustClose() if err := db.Update(func(tx *bolt.Tx) error { if _, err := tx.CreateBucketIfNotExists([]byte{}); err != bolt.ErrBucketNameRequired { t.Fatalf("unexpected error: %s", err) } if _, err := tx.CreateBucketIfNotExists(nil); err != bolt.ErrBucketNameRequired { t.Fatalf("unexpected error: %s", err) } return nil }); err != nil { t.Fatal(err) } } // Ensure that a bucket cannot be created twice. func TestTx_CreateBucket_ErrBucketExists(t *testing.T) { db := MustOpenDB() defer db.MustClose() // Create a bucket. if err := db.Update(func(tx *bolt.Tx) error { if _, err := tx.CreateBucket([]byte("widgets")); err != nil { t.Fatal(err) } return nil }); err != nil { t.Fatal(err) } // Create the same bucket again. if err := db.Update(func(tx *bolt.Tx) error { if _, err := tx.CreateBucket([]byte("widgets")); err != bolt.ErrBucketExists { t.Fatalf("unexpected error: %s", err) } return nil }); err != nil { t.Fatal(err) } } // Ensure that a bucket is created with a non-blank name. func TestTx_CreateBucket_ErrBucketNameRequired(t *testing.T) { db := MustOpenDB() defer db.MustClose() if err := db.Update(func(tx *bolt.Tx) error { if _, err := tx.CreateBucket(nil); err != bolt.ErrBucketNameRequired { t.Fatalf("unexpected error: %s", err) } return nil }); err != nil { t.Fatal(err) } } // Ensure that a bucket can be deleted. func TestTx_DeleteBucket(t *testing.T) { db := MustOpenDB() defer db.MustClose() // Create a bucket and add a value. 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("bar")); err != nil { t.Fatal(err) } return nil }); err != nil { t.Fatal(err) } // Delete the bucket and make sure we can't get the value. if err := db.Update(func(tx *bolt.Tx) error { if err := tx.DeleteBucket([]byte("widgets")); err != nil { t.Fatal(err) } if tx.Bucket([]byte("widgets")) != nil { t.Fatal("unexpected bucket") } return nil }); err != nil { t.Fatal(err) } if err := db.Update(func(tx *bolt.Tx) error { // Create the bucket again and make sure there's not a phantom value. b, err := tx.CreateBucket([]byte("widgets")) if err != nil { t.Fatal(err) } if v := b.Get([]byte("foo")); v != nil { t.Fatalf("unexpected phantom value: %v", v) } return nil }); err != nil { t.Fatal(err) } } // Ensure that deleting a bucket on a closed transaction returns an error. func TestTx_DeleteBucket_ErrTxClosed(t *testing.T) { db := MustOpenDB() defer db.MustClose() tx, err := db.Begin(true) if err != nil { t.Fatal(err) } if err := tx.Commit(); err != nil { t.Fatal(err) } if err := tx.DeleteBucket([]byte("foo")); err != bolt.ErrTxClosed { t.Fatalf("unexpected error: %s", err) } } // Ensure that deleting a bucket with a read-only transaction returns an error. func TestTx_DeleteBucket_ReadOnly(t *testing.T) { db := MustOpenDB() defer db.MustClose() if err := db.View(func(tx *bolt.Tx) error { if err := tx.DeleteBucket([]byte("foo")); err != bolt.ErrTxNotWritable { t.Fatalf("unexpected error: %s", err) } return nil }); err != nil { t.Fatal(err) } } // Ensure that nothing happens when deleting a bucket that doesn't exist. func TestTx_DeleteBucket_NotFound(t *testing.T) { db := MustOpenDB() defer db.MustClose() if err := db.Update(func(tx *bolt.Tx) error { if err := tx.DeleteBucket([]byte("widgets")); err != bolt.ErrBucketNotFound { t.Fatalf("unexpected error: %s", err) } return nil }); err != nil { t.Fatal(err) } } // Ensure that no error is returned when a tx.ForEach function does not return // an error. func TestTx_ForEach_NoError(t *testing.T) { db := MustOpenDB() defer db.MustClose() 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("bar")); err != nil { t.Fatal(err) } if err := tx.ForEach(func(name []byte, b *bolt.Bucket) error { return nil }); err != nil { t.Fatal(err) } return nil }); err != nil { t.Fatal(err) } } // Ensure that an error is returned when a tx.ForEach function returns an error. func TestTx_ForEach_WithError(t *testing.T) { db := MustOpenDB() defer db.MustClose() 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("bar")); err != nil { t.Fatal(err) } marker := errors.New("marker") if err := tx.ForEach(func(name []byte, b *bolt.Bucket) error { return marker }); err != marker { t.Fatalf("unexpected error: %s", err) } return nil }); err != nil { t.Fatal(err) } } // Ensure that Tx commit handlers are called after a transaction successfully commits. func TestTx_OnCommit(t *testing.T) { db := MustOpenDB() defer db.MustClose() var x int if err := db.Update(func(tx *bolt.Tx) error { tx.OnCommit(func() { x += 1 }) tx.OnCommit(func() { x += 2 }) if _, err := tx.CreateBucket([]byte("widgets")); err != nil { t.Fatal(err) } return nil }); err != nil { t.Fatal(err) } else if x != 3 { t.Fatalf("unexpected x: %d", x) } } // Ensure that Tx commit handlers are NOT called after a transaction rolls back. func TestTx_OnCommit_Rollback(t *testing.T) { db := MustOpenDB() defer db.MustClose() var x int if err := db.Update(func(tx *bolt.Tx) error { tx.OnCommit(func() { x += 1 }) tx.OnCommit(func() { x += 2 }) if _, err := tx.CreateBucket([]byte("widgets")); err != nil { t.Fatal(err) } return errors.New("rollback this commit") }); err == nil || err.Error() != "rollback this commit" { t.Fatalf("unexpected error: %s", err) } else if x != 0 { t.Fatalf("unexpected x: %d", x) } } // Ensure that the database can be copied to a file path. func TestTx_CopyFile(t *testing.T) { db := MustOpenDB() defer db.MustClose() path := tempfile() 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("bar")); err != nil { t.Fatal(err) } if err := b.Put([]byte("baz"), []byte("bat")); err != nil { t.Fatal(err) } return nil }); err != nil { t.Fatal(err) } if err := db.View(func(tx *bolt.Tx) error { return tx.CopyFile(path, 0600) }); err != nil { t.Fatal(err) } db2, err := bolt.Open(path, 0600, nil) if err != nil { t.Fatal(err) } if err := db2.View(func(tx *bolt.Tx) error { if v := tx.Bucket([]byte("widgets")).Get([]byte("foo")); !bytes.Equal(v, []byte("bar")) { t.Fatalf("unexpected value: %v", v) } if v := tx.Bucket([]byte("widgets")).Get([]byte("baz")); !bytes.Equal(v, []byte("bat")) { t.Fatalf("unexpected value: %v", v) } return nil }); err != nil { t.Fatal(err) } if err := db2.Close(); err != nil { t.Fatal(err) } } type failWriterError struct{} func (failWriterError) Error() string { return "error injected for tests" } type failWriter struct { // fail after this many bytes After int } func (f *failWriter) Write(p []byte) (n int, err error) { n = len(p) if n > f.After { n = f.After err = failWriterError{} } f.After -= n return n, err } // Ensure that Copy handles write errors right. func TestTx_CopyFile_Error_Meta(t *testing.T) { db := MustOpenDB() defer db.MustClose() 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("bar")); err != nil { t.Fatal(err) } if err := b.Put([]byte("baz"), []byte("bat")); err != nil { t.Fatal(err) } return nil }); err != nil { t.Fatal(err) } if err := db.View(func(tx *bolt.Tx) error { return tx.Copy(&failWriter{}) }); err == nil || err.Error() != "meta 0 copy: error injected for tests" { t.Fatalf("unexpected error: %v", err) } } // Ensure that Copy handles write errors right. func TestTx_CopyFile_Error_Normal(t *testing.T) { db := MustOpenDB() defer db.MustClose() 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("bar")); err != nil { t.Fatal(err) } if err := b.Put([]byte("baz"), []byte("bat")); err != nil { t.Fatal(err) } return nil }); err != nil { t.Fatal(err) } if err := db.View(func(tx *bolt.Tx) error { return tx.Copy(&failWriter{3 * db.Info().PageSize}) }); err == nil || err.Error() != "error injected for tests" { t.Fatalf("unexpected error: %v", err) } } func ExampleTx_Rollback() { // Open the database. db, err := bolt.Open(tempfile(), 0666, nil) if err != nil { log.Fatal(err) } defer os.Remove(db.Path()) // Create a bucket. if err := db.Update(func(tx *bolt.Tx) error { _, err := tx.CreateBucket([]byte("widgets")) return err }); err != nil { log.Fatal(err) } // Set a value for a key. if err := db.Update(func(tx *bolt.Tx) error { return tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")) }); err != nil { log.Fatal(err) } // Update the key but rollback the transaction so it never saves. tx, err := db.Begin(true) if err != nil { log.Fatal(err) } b := tx.Bucket([]byte("widgets")) if err := b.Put([]byte("foo"), []byte("baz")); err != nil { log.Fatal(err) } if err := tx.Rollback(); err != nil { log.Fatal(err) } // Ensure that our original value is still set. if err := db.View(func(tx *bolt.Tx) error { value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) fmt.Printf("The value for 'foo' is still: %s\n", value) return nil }); err != nil { log.Fatal(err) } // Close database to release file lock. if err := db.Close(); err != nil { log.Fatal(err) } // Output: // The value for 'foo' is still: bar } func ExampleTx_CopyFile() { // Open the database. db, err := bolt.Open(tempfile(), 0666, nil) if err != nil { log.Fatal(err) } defer os.Remove(db.Path()) // Create a bucket and a key. if err := db.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucket([]byte("widgets")) if err != nil { return err } if err := b.Put([]byte("foo"), []byte("bar")); err != nil { return err } return nil }); err != nil { log.Fatal(err) } // Copy the database to another file. toFile := tempfile() if err := db.View(func(tx *bolt.Tx) error { return tx.CopyFile(toFile, 0666) }); err != nil { log.Fatal(err) } defer os.Remove(toFile) // Open the cloned database. db2, err := bolt.Open(toFile, 0666, nil) if err != nil { log.Fatal(err) } // Ensure that the key exists in the copy. if err := db2.View(func(tx *bolt.Tx) error { value := tx.Bucket([]byte("widgets")).Get([]byte("foo")) fmt.Printf("The value for 'foo' in the clone is: %s\n", value) return nil }); err != nil { log.Fatal(err) } // Close database to release file lock. if err := db.Close(); err != nil { log.Fatal(err) } if err := db2.Close(); err != nil { log.Fatal(err) } // Output: // The value for 'foo' in the clone is: bar }