diff --git a/bucket_test.go b/bucket_test.go index 1970260..92d5288 100644 --- a/bucket_test.go +++ b/bucket_test.go @@ -15,19 +15,20 @@ import ( // Ensure that a bucket that gets a non-existent key returns nil. func TestBucketGetNonExistent(t *testing.T) { withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") - value, err := db.Get("widgets", []byte("foo")) - if assert.NoError(t, err) { + db.Do(func(tx *Tx) error { + tx.CreateBucket("widgets") + value := tx.Bucket("widgets").Get([]byte("foo")) assert.Nil(t, value) - } + return nil + }) }) } // Ensure that a bucket can read a value that is not flushed yet. func TestBucketGetFromNode(t *testing.T) { withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") db.Do(func(tx *Tx) error { + tx.CreateBucket("widgets") b := tx.Bucket("widgets") b.Put([]byte("foo"), []byte("bar")) value := b.Get([]byte("foo")) @@ -40,20 +41,24 @@ func TestBucketGetFromNode(t *testing.T) { // Ensure that a bucket can write a key/value. func TestBucketPut(t *testing.T) { withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") - err := db.Put("widgets", []byte("foo"), []byte("bar")) - assert.NoError(t, err) - value, err := db.Get("widgets", []byte("foo")) - if assert.NoError(t, err) { + db.Do(func(tx *Tx) error { + tx.CreateBucket("widgets") + err := tx.Bucket("widgets").Put([]byte("foo"), []byte("bar")) + assert.NoError(t, err) + value := tx.Bucket("widgets").Get([]byte("foo")) assert.Equal(t, value, []byte("bar")) - } + return nil + }) }) } // Ensure that setting a value on a read-only bucket returns an error. func TestBucketPutReadOnly(t *testing.T) { withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") + db.Do(func(tx *Tx) error { + tx.CreateBucket("widgets") + return nil + }) db.With(func(tx *Tx) error { b := tx.Bucket("widgets") err := b.Put([]byte("foo"), []byte("bar")) @@ -66,21 +71,25 @@ func TestBucketPutReadOnly(t *testing.T) { // Ensure that a bucket can delete an existing key. func TestBucketDelete(t *testing.T) { withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") - db.Put("widgets", []byte("foo"), []byte("bar")) - err := db.Delete("widgets", []byte("foo")) - assert.NoError(t, err) - value, err := db.Get("widgets", []byte("foo")) - if assert.NoError(t, err) { + db.Do(func(tx *Tx) error { + tx.CreateBucket("widgets") + tx.Bucket("widgets").Put([]byte("foo"), []byte("bar")) + err := tx.Bucket("widgets").Delete([]byte("foo")) + assert.NoError(t, err) + value := tx.Bucket("widgets").Get([]byte("foo")) assert.Nil(t, value) - } + return nil + }) }) } // Ensure that deleting a key on a read-only bucket returns an error. func TestBucketDeleteReadOnly(t *testing.T) { withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") + db.Do(func(tx *Tx) error { + tx.CreateBucket("widgets") + return nil + }) db.With(func(tx *Tx) error { b := tx.Bucket("widgets") err := b.Delete([]byte("foo")) @@ -93,33 +102,34 @@ func TestBucketDeleteReadOnly(t *testing.T) { // Ensure that a bucket can return an autoincrementing sequence. func TestBucketNextSequence(t *testing.T) { withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") - db.CreateBucket("woojits") + db.Do(func(tx *Tx) error { + tx.CreateBucket("widgets") + tx.CreateBucket("woojits") - // Make sure sequence increments. - seq, err := db.NextSequence("widgets") - assert.NoError(t, err) - assert.Equal(t, seq, 1) - seq, err = db.NextSequence("widgets") - assert.NoError(t, err) - assert.Equal(t, seq, 2) + // Make sure sequence increments. + seq, err := tx.Bucket("widgets").NextSequence() + assert.NoError(t, err) + assert.Equal(t, seq, 1) + seq, err = tx.Bucket("widgets").NextSequence() + assert.NoError(t, err) + assert.Equal(t, seq, 2) - // Buckets should be separate. - seq, err = db.NextSequence("woojits") - assert.NoError(t, err) - assert.Equal(t, seq, 1) - - // Missing buckets return an error. - seq, err = db.NextSequence("no_such_bucket") - assert.Equal(t, err, ErrBucketNotFound) - assert.Equal(t, seq, 0) + // Buckets should be separate. + seq, err = tx.Bucket("woojits").NextSequence() + assert.NoError(t, err) + assert.Equal(t, seq, 1) + return nil + }) }) } // Ensure that retrieving the next sequence on a read-only bucket returns an error. func TestBucketNextSequenceReadOnly(t *testing.T) { withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") + db.Do(func(tx *Tx) error { + tx.CreateBucket("widgets") + return nil + }) db.With(func(tx *Tx) error { b := tx.Bucket("widgets") i, err := b.NextSequence() @@ -133,7 +143,10 @@ func TestBucketNextSequenceReadOnly(t *testing.T) { // Ensure that incrementing past the maximum sequence number will return an error. func TestBucketNextSequenceOverflow(t *testing.T) { withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") + db.Do(func(tx *Tx) error { + tx.CreateBucket("widgets") + return nil + }) db.Do(func(tx *Tx) error { b := tx.Bucket("widgets") b.bucket.sequence = uint64(maxInt) @@ -148,70 +161,82 @@ func TestBucketNextSequenceOverflow(t *testing.T) { // Ensure a database can loop over all key/value pairs in a bucket. func TestBucketForEach(t *testing.T) { withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") - db.Put("widgets", []byte("foo"), []byte("0000")) - db.Put("widgets", []byte("baz"), []byte("0001")) - db.Put("widgets", []byte("bar"), []byte("0002")) + db.Do(func(tx *Tx) error { + tx.CreateBucket("widgets") + tx.Bucket("widgets").Put([]byte("foo"), []byte("0000")) + tx.Bucket("widgets").Put([]byte("baz"), []byte("0001")) + tx.Bucket("widgets").Put([]byte("bar"), []byte("0002")) - var index int - err := db.ForEach("widgets", func(k, v []byte) error { - switch index { - case 0: - assert.Equal(t, k, []byte("bar")) - assert.Equal(t, v, []byte("0002")) - case 1: - assert.Equal(t, k, []byte("baz")) - assert.Equal(t, v, []byte("0001")) - case 2: - assert.Equal(t, k, []byte("foo")) - assert.Equal(t, v, []byte("0000")) - } - index++ + var index int + err := tx.Bucket("widgets").ForEach(func(k, v []byte) error { + switch index { + case 0: + assert.Equal(t, k, []byte("bar")) + assert.Equal(t, v, []byte("0002")) + case 1: + assert.Equal(t, k, []byte("baz")) + assert.Equal(t, v, []byte("0001")) + case 2: + assert.Equal(t, k, []byte("foo")) + assert.Equal(t, v, []byte("0000")) + } + index++ + return nil + }) + assert.NoError(t, err) + assert.Equal(t, index, 3) return nil }) - assert.NoError(t, err) - assert.Equal(t, index, 3) }) } // Ensure a database can stop iteration early. func TestBucketForEachShortCircuit(t *testing.T) { withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") - db.Put("widgets", []byte("bar"), []byte("0000")) - db.Put("widgets", []byte("baz"), []byte("0000")) - db.Put("widgets", []byte("foo"), []byte("0000")) + db.Do(func(tx *Tx) error { + tx.CreateBucket("widgets") + tx.Bucket("widgets").Put([]byte("bar"), []byte("0000")) + tx.Bucket("widgets").Put([]byte("baz"), []byte("0000")) + tx.Bucket("widgets").Put([]byte("foo"), []byte("0000")) - var index int - err := db.ForEach("widgets", func(k, v []byte) error { - index++ - if bytes.Equal(k, []byte("baz")) { - return &Error{"marker", nil} - } + var index int + err := tx.Bucket("widgets").ForEach(func(k, v []byte) error { + index++ + if bytes.Equal(k, []byte("baz")) { + return &Error{"marker", nil} + } + return nil + }) + assert.Equal(t, err, &Error{"marker", nil}) + assert.Equal(t, index, 2) return nil }) - assert.Equal(t, err, &Error{"marker", nil}) - assert.Equal(t, index, 2) }) } // Ensure that an error is returned when inserting with an empty key. func TestBucketPutEmptyKey(t *testing.T) { withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") - err := db.Put("widgets", []byte(""), []byte("bar")) - assert.Equal(t, err, ErrKeyRequired) - err = db.Put("widgets", nil, []byte("bar")) - assert.Equal(t, err, ErrKeyRequired) + db.Do(func(tx *Tx) error { + tx.CreateBucket("widgets") + err := tx.Bucket("widgets").Put([]byte(""), []byte("bar")) + assert.Equal(t, err, ErrKeyRequired) + err = tx.Bucket("widgets").Put(nil, []byte("bar")) + assert.Equal(t, err, ErrKeyRequired) + return nil + }) }) } // Ensure that an error is returned when inserting with a key that's too large. func TestBucketPutKeyTooLarge(t *testing.T) { withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") - err := db.Put("widgets", make([]byte, 32769), []byte("bar")) - assert.Equal(t, err, ErrKeyTooLarge) + db.Do(func(tx *Tx) error { + tx.CreateBucket("widgets") + err := tx.Bucket("widgets").Put(make([]byte, 32769), []byte("bar")) + assert.Equal(t, err, ErrKeyTooLarge) + return nil + }) }) } @@ -286,30 +311,35 @@ func TestBucketPutSingle(t *testing.T) { withOpenDB(func(db *DB, path string) { m := make(map[string][]byte) - db.CreateBucket("widgets") + db.Do(func(tx *Tx) error { + return tx.CreateBucket("widgets") + }) for _, item := range items { - if err := db.Put("widgets", item.Key, item.Value); err != nil { - panic("put error: " + err.Error()) - } - m[string(item.Key)] = item.Value + db.Do(func(tx *Tx) error { + if err := tx.Bucket("widgets").Put(item.Key, item.Value); err != nil { + panic("put error: " + err.Error()) + } + m[string(item.Key)] = item.Value + return nil + }) // Verify all key/values so far. - i := 0 - for k, v := range m { - value, err := db.Get("widgets", []byte(k)) - if err != nil { - panic("get error: " + err.Error()) + db.With(func(tx *Tx) error { + i := 0 + for k, v := range m { + value := tx.Bucket("widgets").Get([]byte(k)) + if !bytes.Equal(value, v) { + db.CopyFile("/tmp/bolt.put.single.db", 0666) + t.Fatalf("value mismatch [run %d] (%d of %d):\nkey: %x\ngot: %x\nexp: %x", index, i, len(m), []byte(k), value, v) + } + i++ } - if !bytes.Equal(value, v) { - db.CopyFile("/tmp/bolt.put.single.db", 0666) - t.Fatalf("value mismatch [run %d] (%d of %d):\nkey: %x\ngot: %x\nexp: %x", index, i, len(m), []byte(k), value, v) - } - i++ - } + return nil + }) } - - fmt.Fprint(os.Stderr, ".") }) + + fmt.Fprint(os.Stderr, ".") index++ return true } @@ -328,25 +358,30 @@ func TestBucketPutMultiple(t *testing.T) { f := func(items testdata) bool { withOpenDB(func(db *DB, path string) { // Bulk insert all values. - db.CreateBucket("widgets") - tx, _ := db.RWTx() - b := tx.Bucket("widgets") - for _, item := range items { - assert.NoError(t, b.Put(item.Key, item.Value)) - } - assert.NoError(t, tx.Commit()) + db.Do(func(tx *Tx) error { + return tx.CreateBucket("widgets") + }) + err := db.Do(func(tx *Tx) error { + b := tx.Bucket("widgets") + for _, item := range items { + assert.NoError(t, b.Put(item.Key, item.Value)) + } + return nil + }) + assert.NoError(t, err) // Verify all items exist. - tx, _ = db.Tx() - b = tx.Bucket("widgets") - for _, item := range items { - value := b.Get(item.Key) - if !assert.Equal(t, item.Value, value) { - db.CopyFile("/tmp/bolt.put.multiple.db", 0666) - t.FailNow() + db.With(func(tx *Tx) error { + b := tx.Bucket("widgets") + for _, item := range items { + value := b.Get(item.Key) + if !assert.Equal(t, item.Value, value) { + db.CopyFile("/tmp/bolt.put.multiple.db", 0666) + t.FailNow() + } } - } - tx.Rollback() + return nil + }) }) fmt.Fprint(os.Stderr, ".") return true @@ -366,35 +401,43 @@ func TestBucketDeleteQuick(t *testing.T) { f := func(items testdata) bool { withOpenDB(func(db *DB, path string) { // Bulk insert all values. - db.CreateBucket("widgets") - tx, _ := db.RWTx() - b := tx.Bucket("widgets") - for _, item := range items { - assert.NoError(t, b.Put(item.Key, item.Value)) - } - assert.NoError(t, tx.Commit()) + db.Do(func(tx *Tx) error { + return tx.CreateBucket("widgets") + }) + err := db.Do(func(tx *Tx) error { + b := tx.Bucket("widgets") + for _, item := range items { + assert.NoError(t, b.Put(item.Key, item.Value)) + } + return nil + }) + assert.NoError(t, err) // Remove items one at a time and check consistency. for i, item := range items { - assert.NoError(t, db.Delete("widgets", item.Key)) + err := db.Do(func(tx *Tx) error { + return tx.Bucket("widgets").Delete(item.Key) + }) + assert.NoError(t, err) // Anything before our deletion index should be nil. - tx, _ := db.Tx() - b := tx.Bucket("widgets") - for j, exp := range items { - if j > i { - value := b.Get(exp.Key) - if !assert.Equal(t, exp.Value, value) { - t.FailNow() - } - } else { - value := b.Get(exp.Key) - if !assert.Nil(t, value) { - t.FailNow() + db.With(func(tx *Tx) error { + b := tx.Bucket("widgets") + for j, exp := range items { + if j > i { + value := b.Get(exp.Key) + if !assert.Equal(t, exp.Value, value) { + t.FailNow() + } + } else { + value := b.Get(exp.Key) + if !assert.Nil(t, value) { + t.FailNow() + } } } - } - tx.Rollback() + return nil + }) } }) fmt.Fprint(os.Stderr, ".") diff --git a/db.go b/db.go index 6493e1b..ed6f176 100644 --- a/db.go +++ b/db.go @@ -372,135 +372,6 @@ func (db *DB) With(fn func(*Tx) error) error { return fn(t) } -// ForEach executes a function for each key/value pair in a bucket. -// An error is returned if the bucket cannot be found. -func (db *DB) ForEach(name string, fn func(k, v []byte) error) error { - return db.With(func(t *Tx) error { - b := t.Bucket(name) - if b == nil { - return ErrBucketNotFound - } - return b.ForEach(fn) - }) -} - -// Bucket retrieves a reference to a bucket. -// This is typically useful for checking the existence of a bucket. -func (db *DB) Bucket(name string) (*Bucket, error) { - t, err := db.Tx() - if err != nil { - return nil, err - } - defer t.Rollback() - return t.Bucket(name), nil -} - -// Buckets retrieves a list of all buckets in the database. -func (db *DB) Buckets() ([]*Bucket, error) { - t, err := db.Tx() - if err != nil { - return nil, err - } - defer t.Rollback() - return t.Buckets(), nil -} - -// CreateBucket creates a new bucket with the given name. -// This function can return an error if the bucket already exists, if the name -// is blank, or the bucket name is too long. -func (db *DB) CreateBucket(name string) error { - return db.Do(func(t *Tx) error { - return t.CreateBucket(name) - }) -} - -// CreateBucketIfNotExists creates a new bucket with the given name if it doesn't already exist. -// This function can return an error if the name is blank, or the bucket name is too long. -func (db *DB) CreateBucketIfNotExists(name string) error { - return db.Do(func(t *Tx) error { - return t.CreateBucketIfNotExists(name) - }) -} - -// DeleteBucket removes a bucket from the database. -// Returns an error if the bucket does not exist. -func (db *DB) DeleteBucket(name string) error { - return db.Do(func(t *Tx) error { - return t.DeleteBucket(name) - }) -} - -// NextSequence returns an autoincrementing integer for the bucket. -// This function can return an error if the bucket does not exist. -func (db *DB) NextSequence(name string) (int, error) { - var seq int - err := db.Do(func(t *Tx) error { - b := t.Bucket(name) - if b == nil { - return ErrBucketNotFound - } - - var err error - seq, err = b.NextSequence() - return err - }) - if err != nil { - return 0, err - } - return seq, nil -} - -// Get retrieves the value for a key in a bucket. -// Returns an error if the key does not exist. -func (db *DB) Get(name string, key []byte) ([]byte, error) { - t, err := db.Tx() - if err != nil { - return nil, err - } - defer t.Rollback() - - // Open bucket and retrieve value for key. - b := t.Bucket(name) - if b == nil { - return nil, ErrBucketNotFound - } - value := b.Get(key) - if value == nil { - return nil, nil - } - - // Copy the value out since the transaction will be closed after this - // function ends. The data can get reclaimed between now and when the - // value is used. - tmp := make([]byte, len(value)) - copy(tmp, value) - return tmp, nil -} - -// Put sets the value for a key in a bucket. -// Returns an error if the bucket is not found, if key is blank, if the key is too large, or if the value is too large. -func (db *DB) Put(name string, key []byte, value []byte) error { - return db.Do(func(t *Tx) error { - b := t.Bucket(name) - if b == nil { - return ErrBucketNotFound - } - return b.Put(key, value) - }) -} - -// Delete removes a key from a bucket. -// Returns an error if the bucket cannot be found. -func (db *DB) Delete(name string, key []byte) error { - return db.Do(func(t *Tx) error { - b := t.Bucket(name) - if b == nil { - return ErrBucketNotFound - } - return b.Delete(key) - }) -} - // Copy writes the entire database to a writer. // A reader transaction is maintained during the copy so it is safe to continue // using the database while a copy is in progress. diff --git a/db_test.go b/db_test.go index e35502c..d7db679 100644 --- a/db_test.go +++ b/db_test.go @@ -164,14 +164,6 @@ func TestDBTxErrDatabaseNotOpen(t *testing.T) { }) } -// Ensure that a delete on a missing bucket returns an error. -func TestDBDeleteFromMissingBucket(t *testing.T) { - withOpenDB(func(db *DB, path string) { - err := db.Delete("widgets", []byte("foo")) - assert.Equal(t, err, ErrBucketNotFound) - }) -} - // Ensure that a read-write transaction can be retrieved. func TestDBRWTx(t *testing.T) { withOpenDB(func(db *DB, path string) { @@ -204,10 +196,12 @@ func TestDBTxBlock(t *testing.T) { return nil }) assert.NoError(t, err) - value, _ := db.Get("widgets", []byte("foo")) - assert.Nil(t, value) - value, _ = db.Get("widgets", []byte("baz")) - assert.Equal(t, value, []byte("bat")) + err = db.With(func(tx *Tx) error { + assert.Nil(t, tx.Bucket("widgets").Get([]byte("foo"))) + assert.Equal(t, []byte("bat"), tx.Bucket("widgets").Get([]byte("baz"))) + return nil + }) + assert.NoError(t, err) }) } @@ -222,63 +216,15 @@ func TestDBTxBlockWhileClosed(t *testing.T) { }) } -// Ensure a database returns an error when trying to attempt a for each on a missing bucket. -func TestDBForEachBucketNotFound(t *testing.T) { - withOpenDB(func(db *DB, path string) { - err := db.ForEach("widgets", func(k, v []byte) error { return nil }) - assert.Equal(t, err, ErrBucketNotFound) - }) -} - -// Ensure a closed database returns an error when executing a for each. -func TestDBForEachWhileClosed(t *testing.T) { - withDB(func(db *DB, path string) { - err := db.ForEach("widgets", func(k, v []byte) error { return nil }) - assert.Equal(t, err, ErrDatabaseNotOpen) - }) -} - -// Ensure a closed database returns an error when finding a bucket. -func TestDBBucketWhileClosed(t *testing.T) { - withDB(func(db *DB, path string) { - b, err := db.Bucket("widgets") - assert.Equal(t, err, ErrDatabaseNotOpen) - assert.Nil(t, b) - }) -} - -// Ensure a closed database returns an error when finding all buckets. -func TestDBBucketsWhileClosed(t *testing.T) { - withDB(func(db *DB, path string) { - b, err := db.Buckets() - assert.Equal(t, err, ErrDatabaseNotOpen) - assert.Nil(t, b) - }) -} - -// Ensure a closed database returns an error when getting a key. -func TestDBGetWhileClosed(t *testing.T) { - withDB(func(db *DB, path string) { - value, err := db.Get("widgets", []byte("foo")) - assert.Equal(t, err, ErrDatabaseNotOpen) - assert.Nil(t, value) - }) -} - -// Ensure that an error is returned when inserting into a bucket that doesn't exist. -func TestDBPutBucketNotFound(t *testing.T) { - withOpenDB(func(db *DB, path string) { - err := db.Put("widgets", []byte("foo"), []byte("bar")) - assert.Equal(t, err, ErrBucketNotFound) - }) -} - // Ensure that the database can be copied to a file path. func TestDBCopyFile(t *testing.T) { withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") - db.Put("widgets", []byte("foo"), []byte("bar")) - db.Put("widgets", []byte("baz"), []byte("bat")) + db.Do(func(tx *Tx) error { + tx.CreateBucket("widgets") + tx.Bucket("widgets").Put([]byte("foo"), []byte("bar")) + tx.Bucket("widgets").Put([]byte("baz"), []byte("bat")) + return nil + }) assert.NoError(t, os.RemoveAll("/tmp/bolt.copyfile.db")) assert.NoError(t, db.CopyFile("/tmp/bolt.copyfile.db", 0666)) @@ -286,10 +232,11 @@ func TestDBCopyFile(t *testing.T) { assert.NoError(t, db2.Open("/tmp/bolt.copyfile.db", 0666)) defer db2.Close() - value, _ := db2.Get("widgets", []byte("foo")) - assert.Equal(t, value, []byte("bar")) - value, _ = db2.Get("widgets", []byte("baz")) - assert.Equal(t, value, []byte("bat")) + db2.With(func(tx *Tx) error { + assert.Equal(t, []byte("bar"), tx.Bucket("widgets").Get([]byte("foo"))) + assert.Equal(t, []byte("bat"), tx.Bucket("widgets").Get([]byte("baz"))) + return nil + }) }) } @@ -306,8 +253,12 @@ func TestDBStat(t *testing.T) { }) // Delete some keys. - db.Delete("widgets", []byte("10")) - db.Delete("widgets", []byte("1000")) + db.Do(func(tx *Tx) error { + return tx.Bucket("widgets").Delete([]byte("10")) + }) + db.Do(func(tx *Tx) error { + return tx.Bucket("widgets").Delete([]byte("1000")) + }) // Open some readers. t0, _ := db.Tx() @@ -367,9 +318,13 @@ func TestDBString(t *testing.T) { func BenchmarkDBPutSequential(b *testing.B) { value := []byte(strings.Repeat("0", 64)) withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") + db.Do(func(tx *Tx) error { + return tx.CreateBucket("widgets") + }) for i := 0; i < b.N; i++ { - db.Put("widgets", []byte(strconv.Itoa(i)), value) + db.Do(func(tx *Tx) error { + return tx.Bucket("widgets").Put([]byte(strconv.Itoa(i)), value) + }) } }) } @@ -379,9 +334,13 @@ func BenchmarkDBPutRandom(b *testing.B) { indexes := rand.Perm(b.N) value := []byte(strings.Repeat("0", 64)) withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") + db.Do(func(tx *Tx) error { + return tx.CreateBucket("widgets") + }) for i := 0; i < b.N; i++ { - db.Put("widgets", []byte(strconv.Itoa(indexes[i])), value) + db.Do(func(tx *Tx) error { + return tx.Bucket("widgets").Put([]byte(strconv.Itoa(indexes[i])), value) + }) } }) } diff --git a/example_test.go b/example_test.go index 0185bb1..6c6e6f5 100644 --- a/example_test.go +++ b/example_test.go @@ -10,56 +10,6 @@ func init() { os.MkdirAll("/tmp/bolt", 0777) } -func ExampleDB_Put() { - // Open the database. - var db DB - db.Open("/tmp/bolt/db_put.db", 0666) - defer db.Close() - - // Create a bucket. - db.CreateBucket("widgets") - - // Set the value "bar" for the key "foo". - db.Put("widgets", []byte("foo"), []byte("bar")) - - // Retrieve the key back from the database and verify it. - value, _ := db.Get("widgets", []byte("foo")) - fmt.Printf("The value of 'foo' is: %s\n", string(value)) - - // Output: - // The value of 'foo' is: bar -} - -func ExampleDB_Delete() { - // Open the database. - var db DB - db.Open("/tmp/bolt/db_delete.db", 0666) - defer db.Close() - - // Create a bucket. - db.CreateBucket("widgets") - - // Set the value "bar" for the key "foo". - db.Put("widgets", []byte("foo"), []byte("bar")) - - // Retrieve the key back from the database and verify it. - value, _ := db.Get("widgets", []byte("foo")) - fmt.Printf("The value of 'foo' was: %s\n", string(value)) - - // Delete the "foo" key. - db.Delete("widgets", []byte("foo")) - - // Retrieve the key again. - value, _ = db.Get("widgets", []byte("foo")) - if value == nil { - fmt.Printf("The value of 'foo' is now: nil\n") - } - - // Output: - // The value of 'foo' was: bar - // The value of 'foo' is now: nil -} - func ExampleDB_Do() { // Open the database. var db DB @@ -67,11 +17,11 @@ func ExampleDB_Do() { defer db.Close() // Execute several commands within a write transaction. - err := db.Do(func(t *Tx) error { - if err := t.CreateBucket("widgets"); err != nil { + err := db.Do(func(tx *Tx) error { + if err := tx.CreateBucket("widgets"); err != nil { return err } - b := t.Bucket("widgets") + b := tx.Bucket("widgets") if err := b.Put([]byte("foo"), []byte("bar")); err != nil { return err } @@ -80,8 +30,11 @@ func ExampleDB_Do() { // If our transactional block didn't return an error then our data is saved. if err == nil { - value, _ := db.Get("widgets", []byte("foo")) - fmt.Printf("The value of 'foo' is: %s\n", string(value)) + db.With(func(tx *Tx) error { + value := tx.Bucket("widgets").Get([]byte("foo")) + fmt.Printf("The value of 'foo' is: %s\n", string(value)) + return nil + }) } // Output: @@ -91,13 +44,16 @@ func ExampleDB_Do() { func ExampleDB_With() { // Open the database. var db DB - db.Open("/tmp/bolt/db_foreach.db", 0666) + db.Open("/tmp/bolt/db_with.db", 0666) defer db.Close() // Insert data into a bucket. - db.CreateBucket("people") - db.Put("people", []byte("john"), []byte("doe")) - db.Put("people", []byte("susy"), []byte("que")) + db.Do(func(tx *Tx) error { + tx.CreateBucket("people") + tx.Bucket("people").Put([]byte("john"), []byte("doe")) + tx.Bucket("people").Put([]byte("susy"), []byte("que")) + return nil + }) // Access data from within a read-only transactional block. db.With(func(t *Tx) error { @@ -110,21 +66,92 @@ func ExampleDB_With() { // John's last name is doe. } -func ExampleDB_ForEach() { +func ExampleTx_Put() { // Open the database. var db DB - db.Open("/tmp/bolt/db_foreach.db", 0666) + db.Open("/tmp/bolt/db_put.db", 0666) + defer db.Close() + + // Start a write transaction. + db.Do(func(tx *Tx) error { + // Create a bucket. + tx.CreateBucket("widgets") + + // Set the value "bar" for the key "foo". + tx.Bucket("widgets").Put([]byte("foo"), []byte("bar")) + return nil + }) + + // Read value back in a different read-only transaction. + db.Do(func(tx *Tx) error { + value := tx.Bucket("widgets").Get([]byte("foo")) + fmt.Printf("The value of 'foo' is: %s\n", string(value)) + return nil + }) + + // Output: + // The value of 'foo' is: bar +} + +func ExampleTx_Delete() { + // Open the database. + var db DB + db.Open("/tmp/bolt/db_delete.db", 0666) + defer db.Close() + + // Start a write transaction. + db.Do(func(tx *Tx) error { + // Create a bucket. + tx.CreateBucket("widgets") + b := tx.Bucket("widgets") + + // Set the value "bar" for the key "foo". + b.Put([]byte("foo"), []byte("bar")) + + // Retrieve the key back from the database and verify it. + value := b.Get([]byte("foo")) + fmt.Printf("The value of 'foo' was: %s\n", string(value)) + return nil + }) + + // Delete the key in a different write transaction. + db.Do(func(tx *Tx) error { + return tx.Bucket("widgets").Delete([]byte("foo")) + }) + + // Retrieve the key again. + db.With(func(tx *Tx) error { + value := tx.Bucket("widgets").Get([]byte("foo")) + if value == nil { + fmt.Printf("The value of 'foo' is now: nil\n") + } + return nil + }) + + // Output: + // The value of 'foo' was: bar + // The value of 'foo' is now: nil +} + +func ExampleTx_ForEach() { + // Open the database. + var db DB + db.Open("/tmp/bolt/tx_foreach.db", 0666) defer db.Close() // Insert data into a bucket. - db.CreateBucket("animals") - db.Put("animals", []byte("dog"), []byte("fun")) - db.Put("animals", []byte("cat"), []byte("lame")) - db.Put("animals", []byte("liger"), []byte("awesome")) + db.Do(func(tx *Tx) error { + tx.CreateBucket("animals") + b := tx.Bucket("animals") + b.Put([]byte("dog"), []byte("fun")) + b.Put([]byte("cat"), []byte("lame")) + b.Put([]byte("liger"), []byte("awesome")) - // Iterate over items in sorted key order. - db.ForEach("animals", func(k, v []byte) error { - fmt.Printf("A %s is %s.\n", string(k), string(v)) + // Iterate over items in sorted key order. + b.ForEach(func(k, v []byte) error { + fmt.Printf("A %s is %s.\n", string(k), string(v)) + return nil + }) return nil }) @@ -141,7 +168,9 @@ func ExampleTx() { defer db.Close() // Create a bucket. - db.CreateBucket("widgets") + db.Do(func(tx *Tx) error { + return tx.CreateBucket("widgets") + }) // Create several keys in a transaction. tx, _ := db.RWTx() @@ -172,10 +201,14 @@ func ExampleTx_rollback() { defer db.Close() // Create a bucket. - db.CreateBucket("widgets") + db.Do(func(tx *Tx) error { + return tx.CreateBucket("widgets") + }) // Set a value for a key. - db.Put("widgets", []byte("foo"), []byte("bar")) + db.Do(func(tx *Tx) error { + return tx.Bucket("widgets").Put([]byte("foo"), []byte("bar")) + }) // Update the key but rollback the transaction so it never saves. tx, _ := db.RWTx() @@ -184,8 +217,11 @@ func ExampleTx_rollback() { tx.Rollback() // Ensure that our original value is still set. - value, _ := db.Get("widgets", []byte("foo")) - fmt.Printf("The value for 'foo' is still: %s\n", string(value)) + db.With(func(tx *Tx) error { + value := tx.Bucket("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 @@ -198,8 +234,11 @@ func ExampleDB_CopyFile() { defer db.Close() // Create a bucket and a key. - db.CreateBucket("widgets") - db.Put("widgets", []byte("foo"), []byte("bar")) + db.Do(func(tx *Tx) error { + tx.CreateBucket("widgets") + tx.Bucket("widgets").Put([]byte("foo"), []byte("bar")) + return nil + }) // Copy the database to another file. db.CopyFile("/tmp/bolt/db_copy_2.db", 0666) @@ -210,8 +249,11 @@ func ExampleDB_CopyFile() { defer db2.Close() // Ensure that the key exists in the copy. - value, _ := db2.Get("widgets", []byte("foo")) - fmt.Printf("The value for 'foo' in the clone is: %s\n", string(value)) + db2.With(func(tx *Tx) error { + value := tx.Bucket("widgets").Get([]byte("foo")) + fmt.Printf("The value for 'foo' in the clone is: %s\n", string(value)) + return nil + }) // Output: // The value for 'foo' in the clone is: bar diff --git a/functional_test.go b/functional_test.go index 41bffd6..866ec95 100644 --- a/functional_test.go +++ b/functional_test.go @@ -28,7 +28,9 @@ func TestParallelTxs(t *testing.T) { var current testdata withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") + db.Do(func(tx *Tx) error { + return tx.CreateBucket("widgets") + }) // Maintain a set of concurrent readers. var wg sync.WaitGroup diff --git a/tx_test.go b/tx_test.go index d3ad131..afdbb02 100644 --- a/tx_test.go +++ b/tx_test.go @@ -16,15 +16,18 @@ import ( // Ensure that the database can retrieve a list of buckets. func TestTxBuckets(t *testing.T) { withOpenDB(func(db *DB, path string) { - db.CreateBucket("foo") - db.CreateBucket("bar") - db.CreateBucket("baz") - buckets, err := db.Buckets() - if assert.NoError(t, err) && 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") - } + db.Do(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 + }) }) } @@ -39,33 +42,40 @@ func TestTxCreateBucketReadOnly(t *testing.T) { } // Ensure that a Tx can retrieve a bucket. -func TestTxBucketMissing(t *testing.T) { +func TestTxBucket(t *testing.T) { withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") - b, err := db.Bucket("widgets") - assert.NoError(t, err) - if assert.NotNil(t, b) { - assert.Equal(t, "widgets", b.Name()) - } + db.Do(func(tx *Tx) error { + tx.CreateBucket("widgets") + b := tx.Bucket("widgets") + if assert.NotNil(t, b) { + assert.Equal(t, "widgets", b.Name()) + } + return nil + }) }) } // Ensure that a Tx retrieving a non-existent key returns nil. func TestTxGetMissing(t *testing.T) { withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") - db.Put("widgets", []byte("foo"), []byte("bar")) - value, err := db.Get("widgets", []byte("no_such_key")) - assert.NoError(t, err) - assert.Nil(t, value) + db.Do(func(tx *Tx) error { + tx.CreateBucket("widgets") + tx.Bucket("widgets").Put([]byte("foo"), []byte("bar")) + value := tx.Bucket("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.CreateBucket("widgets") - db.CreateBucket("woojits") + db.Do(func(tx *Tx) error { + tx.CreateBucket("widgets") + tx.CreateBucket("woojits") + return nil + }) db.Do(func(tx *Tx) error { buckets := tx.Buckets() assert.Equal(t, len(buckets), 2) @@ -75,10 +85,11 @@ func TestTxWritableBuckets(t *testing.T) { buckets[1].Put([]byte("bar"), []byte("0001")) return nil }) - v, _ := db.Get("widgets", []byte("foo")) - assert.Equal(t, v, []byte("0000")) - v, _ = db.Get("woojits", []byte("bar")) - assert.Equal(t, v, []byte("0001")) + db.With(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 + }) }) } @@ -86,27 +97,36 @@ func TestTxWritableBuckets(t *testing.T) { func TestTxCreateBucket(t *testing.T) { withOpenDB(func(db *DB, path string) { // Create a bucket. - err := db.CreateBucket("widgets") - assert.NoError(t, err) + db.Do(func(tx *Tx) error { + assert.NoError(t, tx.CreateBucket("widgets")) + return nil + }) // Read the bucket through a separate transaction. - b, err := db.Bucket("widgets") - assert.NotNil(t, b) - assert.NoError(t, err) + db.With(func(tx *Tx) error { + b := tx.Bucket("widgets") + assert.NotNil(t, b) + return nil + }) }) } // Ensure that a bucket can be created if it doesn't already exist. func TestTxCreateBucketIfNotExists(t *testing.T) { withOpenDB(func(db *DB, path string) { - assert.NoError(t, db.CreateBucketIfNotExists("widgets")) - assert.NoError(t, db.CreateBucketIfNotExists("widgets")) - assert.Equal(t, db.CreateBucketIfNotExists(""), ErrBucketNameRequired) + db.Do(func(tx *Tx) error { + assert.NoError(t, tx.CreateBucketIfNotExists("widgets")) + assert.NoError(t, tx.CreateBucketIfNotExists("widgets")) + assert.Equal(t, tx.CreateBucketIfNotExists(""), ErrBucketNameRequired) + return nil + }) // Read the bucket through a separate transaction. - b, err := db.Bucket("widgets") - assert.NotNil(t, b) - assert.NoError(t, err) + db.With(func(tx *Tx) error { + b := tx.Bucket("widgets") + assert.NotNil(t, b) + return nil + }) }) } @@ -114,31 +134,37 @@ func TestTxCreateBucketIfNotExists(t *testing.T) { func TestTxRecreateBucket(t *testing.T) { withOpenDB(func(db *DB, path string) { // Create a bucket. - err := db.CreateBucket("widgets") - assert.NoError(t, err) + db.Do(func(tx *Tx) error { + assert.NoError(t, tx.CreateBucket("widgets")) + return nil + }) // Create the same bucket again. - err = db.CreateBucket("widgets") - assert.Equal(t, err, ErrBucketExists) + db.Do(func(tx *Tx) error { + assert.Equal(t, ErrBucketExists, tx.CreateBucket("widgets")) + return nil + }) }) } // Ensure that a bucket is created with a non-blank name. func TestTxCreateBucketWithoutName(t *testing.T) { withOpenDB(func(db *DB, path string) { - err := db.CreateBucket("") - assert.Equal(t, err, ErrBucketNameRequired) + db.Do(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) { - err := db.CreateBucket(strings.Repeat("X", 255)) - assert.NoError(t, err) - - err = db.CreateBucket(strings.Repeat("X", 256)) - assert.Equal(t, err, ErrBucketNameTooLarge) + db.Do(func(tx *Tx) error { + assert.NoError(t, tx.CreateBucket(strings.Repeat("X", 255))) + assert.Equal(t, ErrBucketNameTooLarge, tx.CreateBucket(strings.Repeat("X", 256))) + return nil + }) }) } @@ -146,25 +172,35 @@ func TestTxCreateBucketWithLongName(t *testing.T) { func TestTxDeleteBucket(t *testing.T) { withOpenDB(func(db *DB, path string) { // Create a bucket and add a value. - db.CreateBucket("widgets") - db.Put("widgets", []byte("foo"), []byte("bar")) + db.Do(func(tx *Tx) error { + tx.CreateBucket("widgets") + tx.Bucket("widgets").Put([]byte("foo"), []byte("bar")) + return nil + }) - b, _ := db.Bucket("widgets") + // Save root page id. + var root pgid + db.With(func(tx *Tx) error { + root = tx.Bucket("widgets").root + return nil + }) // Delete the bucket and make sure we can't get the value. - assert.NoError(t, db.DeleteBucket("widgets")) - value, err := db.Get("widgets", []byte("foo")) - assert.Equal(t, err, ErrBucketNotFound) - assert.Nil(t, value) + db.Do(func(tx *Tx) error { + assert.NoError(t, tx.DeleteBucket("widgets")) + assert.Nil(t, tx.Bucket("widgets")) + return nil + }) - // Verify that the bucket's page is free. - assert.Equal(t, db.freelist.all(), []pgid{b.root}) + db.Do(func(tx *Tx) error { + // Verify that the bucket's page is free. + assert.Equal(t, []pgid{root}, db.freelist.all()) - // Create the bucket again and make sure there's not a phantom value. - assert.NoError(t, db.CreateBucket("widgets")) - value, err = db.Get("widgets", []byte("foo")) - assert.NoError(t, err) - assert.Nil(t, value) + // 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"))) + return nil + }) }) } @@ -181,15 +217,19 @@ func TestTxDeleteBucketReadOnly(t *testing.T) { // 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) { - err := db.DeleteBucket("widgets") - assert.Equal(t, err, ErrBucketNotFound) + db.Do(func(tx *Tx) error { + assert.Equal(t, ErrBucketNotFound, tx.DeleteBucket("widgets")) + return nil + }) }) } // Ensure that a Tx cursor can iterate over an empty bucket without error. func TestTxCursorEmptyBucket(t *testing.T) { withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") + db.Do(func(tx *Tx) error { + return tx.CreateBucket("widgets") + }) db.With(func(tx *Tx) error { c := tx.Bucket("widgets").Cursor() k, v := c.First() @@ -203,7 +243,9 @@ func TestTxCursorEmptyBucket(t *testing.T) { // 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.CreateBucket("widgets") + db.Do(func(tx *Tx) error { + return tx.CreateBucket("widgets") + }) db.With(func(tx *Tx) error { c := tx.Bucket("widgets").Cursor() k, v := c.Last() @@ -217,10 +259,13 @@ func TestCursorEmptyBucketReverse(t *testing.T) { // 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.CreateBucket("widgets") - db.Put("widgets", []byte("baz"), []byte{}) - db.Put("widgets", []byte("foo"), []byte{0}) - db.Put("widgets", []byte("bar"), []byte{1}) + db.Do(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.Tx() c := tx.Bucket("widgets").Cursor() @@ -251,10 +296,13 @@ func TestTxCursorLeafRoot(t *testing.T) { // 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.CreateBucket("widgets") - db.Put("widgets", []byte("baz"), []byte{}) - db.Put("widgets", []byte("foo"), []byte{0}) - db.Put("widgets", []byte("bar"), []byte{1}) + db.Do(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.Tx() c := tx.Bucket("widgets").Cursor() @@ -285,9 +333,12 @@ func TestTxCursorLeafRootReverse(t *testing.T) { // Ensure that a Tx cursor can restart from the beginning. func TestTxCursorRestart(t *testing.T) { withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") - db.Put("widgets", []byte("bar"), []byte{}) - db.Put("widgets", []byte("foo"), []byte{}) + db.Do(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.Tx() c := tx.Bucket("widgets").Cursor() @@ -317,8 +368,8 @@ func TestTxCursorIterate(t *testing.T) { f := func(items testdata) bool { withOpenDB(func(db *DB, path string) { // Bulk insert all values. - db.CreateBucket("widgets") tx, _ := db.RWTx() + tx.CreateBucket("widgets") b := tx.Bucket("widgets") for _, item := range items { assert.NoError(t, b.Put(item.Key, item.Value)) @@ -358,8 +409,8 @@ func TestTxCursorIterateReverse(t *testing.T) { f := func(items testdata) bool { withOpenDB(func(db *DB, path string) { // Bulk insert all values. - db.CreateBucket("widgets") tx, _ := db.RWTx() + tx.CreateBucket("widgets") b := tx.Bucket("widgets") for _, item := range items { assert.NoError(t, b.Put(item.Key, item.Value)) @@ -397,8 +448,8 @@ func BenchmarkTxCursor(b *testing.B) { withOpenDB(func(db *DB, path string) { // Write data to bucket. - db.CreateBucket("widgets") db.Do(func(tx *Tx) error { + tx.CreateBucket("widgets") bucket := tx.Bucket("widgets") for i := 0; i < b.N; i++ { bucket.Put([]byte(strconv.Itoa(indexes[i])), value) @@ -427,7 +478,9 @@ func BenchmarkTxPutRandom(b *testing.B) { indexes := rand.Perm(b.N) value := []byte(strings.Repeat("0", 64)) withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") + db.Do(func(tx *Tx) error { + return tx.CreateBucket("widgets") + }) var tx *Tx var bucket *Bucket for i := 0; i < b.N; i++ { @@ -448,7 +501,9 @@ func BenchmarkTxPutRandom(b *testing.B) { func BenchmarkTxPutSequential(b *testing.B) { value := []byte(strings.Repeat("0", 64)) withOpenDB(func(db *DB, path string) { - db.CreateBucket("widgets") + db.Do(func(tx *Tx) error { + return tx.CreateBucket("widgets") + }) db.Do(func(tx *Tx) error { bucket := tx.Bucket("widgets") for i := 0; i < b.N; i++ {