test suite refactoring

This commit refactors the test suite to make it cleaner and to use the
standard testing library better. The `assert()`, `equals()`, and `ok()`
functions have been removed and some test names have been changed for
clarity.

No functionality has been changed.
master
Ben Johnson 2015-12-27 15:27:39 -07:00
parent 2c56b2a28a
commit 8b08bd4a80
14 changed files with 3359 additions and 2079 deletions

View File

@ -1,54 +1,18 @@
TEST=.
BENCH=.
COVERPROFILE=/tmp/c.out
BRANCH=`git rev-parse --abbrev-ref HEAD` BRANCH=`git rev-parse --abbrev-ref HEAD`
COMMIT=`git rev-parse --short HEAD` COMMIT=`git rev-parse --short HEAD`
GOLDFLAGS="-X main.branch $(BRANCH) -X main.commit $(COMMIT)" GOLDFLAGS="-X main.branch $(BRANCH) -X main.commit $(COMMIT)"
default: build default: build
bench: race:
go test -v -test.run=NOTHINCONTAINSTHIS -test.bench=$(BENCH) @go test -v -race -test.run="TestSimulate_(100op|1000op)"
# http://cloc.sourceforge.net/
cloc:
@cloc --not-match-f='Makefile|_test.go' .
cover: fmt
go test -coverprofile=$(COVERPROFILE) -test.run=$(TEST) $(COVERFLAG) .
go tool cover -html=$(COVERPROFILE)
rm $(COVERPROFILE)
cpuprofile: fmt
@go test -c
@./bolt.test -test.v -test.run=$(TEST) -test.cpuprofile cpu.prof
# go get github.com/kisielk/errcheck # go get github.com/kisielk/errcheck
errcheck: errcheck:
@echo "=== errcheck ===" @errcheck -ignorepkg=bytes -ignore=os:Remove github.com/boltdb/bolt
@errcheck github.com/boltdb/bolt
fmt: test:
@go fmt ./... @go test -v -cover .
@go test -v ./cmd/bolt
get: .PHONY: fmt test
@go get -d ./...
build: get
@mkdir -p bin
@go build -ldflags=$(GOLDFLAGS) -a -o bin/bolt ./cmd/bolt
test: fmt
@go get github.com/stretchr/testify/assert
@echo "=== TESTS ==="
@go test -v -cover -test.run=$(TEST)
@echo ""
@echo ""
@echo "=== CLI ==="
@go test -v -test.run=$(TEST) ./cmd/bolt
@echo ""
@echo ""
@echo "=== RACE DETECTOR ==="
@go test -v -race -test.run="TestSimulate_(100op|1000op)"
.PHONY: bench cloc cover cpuprofile fmt memprofile test

138
batch.go
View File

@ -1,138 +0,0 @@
package bolt
import (
"errors"
"fmt"
"sync"
"time"
)
// Batch calls fn as part of a batch. It behaves similar to Update,
// except:
//
// 1. concurrent Batch calls can be combined into a single Bolt
// transaction.
//
// 2. the function passed to Batch may be called multiple times,
// regardless of whether it returns error or not.
//
// This means that Batch function side effects must be idempotent and
// take permanent effect only after a successful return is seen in
// caller.
//
// The maximum batch size and delay can be adjusted with DB.MaxBatchSize
// and DB.MaxBatchDelay, respectively.
//
// Batch is only useful when there are multiple goroutines calling it.
func (db *DB) Batch(fn func(*Tx) error) error {
errCh := make(chan error, 1)
db.batchMu.Lock()
if (db.batch == nil) || (db.batch != nil && len(db.batch.calls) >= db.MaxBatchSize) {
// There is no existing batch, or the existing batch is full; start a new one.
db.batch = &batch{
db: db,
}
db.batch.timer = time.AfterFunc(db.MaxBatchDelay, db.batch.trigger)
}
db.batch.calls = append(db.batch.calls, call{fn: fn, err: errCh})
if len(db.batch.calls) >= db.MaxBatchSize {
// wake up batch, it's ready to run
go db.batch.trigger()
}
db.batchMu.Unlock()
err := <-errCh
if err == trySolo {
err = db.Update(fn)
}
return err
}
type call struct {
fn func(*Tx) error
err chan<- error
}
type batch struct {
db *DB
timer *time.Timer
start sync.Once
calls []call
}
// trigger runs the batch if it hasn't already been run.
func (b *batch) trigger() {
b.start.Do(b.run)
}
// run performs the transactions in the batch and communicates results
// back to DB.Batch.
func (b *batch) run() {
b.db.batchMu.Lock()
b.timer.Stop()
// Make sure no new work is added to this batch, but don't break
// other batches.
if b.db.batch == b {
b.db.batch = nil
}
b.db.batchMu.Unlock()
retry:
for len(b.calls) > 0 {
var failIdx = -1
err := b.db.Update(func(tx *Tx) error {
for i, c := range b.calls {
if err := safelyCall(c.fn, tx); err != nil {
failIdx = i
return err
}
}
return nil
})
if failIdx >= 0 {
// take the failing transaction out of the batch. it's
// safe to shorten b.calls here because db.batch no longer
// points to us, and we hold the mutex anyway.
c := b.calls[failIdx]
b.calls[failIdx], b.calls = b.calls[len(b.calls)-1], b.calls[:len(b.calls)-1]
// tell the submitter re-run it solo, continue with the rest of the batch
c.err <- trySolo
continue retry
}
// pass success, or bolt internal errors, to all callers
for _, c := range b.calls {
if c.err != nil {
c.err <- err
}
}
break retry
}
}
// trySolo is a special sentinel error value used for signaling that a
// transaction function should be re-run. It should never be seen by
// callers.
var trySolo = errors.New("batch function returned an error and should be re-run solo")
type panicked struct {
reason interface{}
}
func (p panicked) Error() string {
if err, ok := p.reason.(error); ok {
return err.Error()
}
return fmt.Sprintf("panic: %v", p.reason)
}
func safelyCall(fn func(*Tx) error, tx *Tx) (err error) {
defer func() {
if p := recover(); p != nil {
err = panicked{p}
}
}()
return fn(tx)
}

View File

@ -1,170 +0,0 @@
package bolt_test
import (
"bytes"
"encoding/binary"
"errors"
"hash/fnv"
"sync"
"testing"
"github.com/boltdb/bolt"
)
func validateBatchBench(b *testing.B, db *TestDB) {
var rollback = errors.New("sentinel error to cause rollback")
validate := func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte("bench"))
h := fnv.New32a()
buf := make([]byte, 4)
for id := uint32(0); id < 1000; id++ {
binary.LittleEndian.PutUint32(buf, id)
h.Reset()
h.Write(buf[:])
k := h.Sum(nil)
v := bucket.Get(k)
if v == nil {
b.Errorf("not found id=%d key=%x", id, k)
continue
}
if g, e := v, []byte("filler"); !bytes.Equal(g, e) {
b.Errorf("bad value for id=%d key=%x: %s != %q", id, k, g, e)
}
if err := bucket.Delete(k); err != nil {
return err
}
}
// should be empty now
c := bucket.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
b.Errorf("unexpected key: %x = %q", k, v)
}
return rollback
}
if err := db.Update(validate); err != nil && err != rollback {
b.Error(err)
}
}
func BenchmarkDBBatchAutomatic(b *testing.B) {
db := NewTestDB()
defer db.Close()
db.MustCreateBucket([]byte("bench"))
b.ResetTimer()
for i := 0; i < b.N; i++ {
start := make(chan struct{})
var wg sync.WaitGroup
for round := 0; round < 1000; round++ {
wg.Add(1)
go func(id uint32) {
defer wg.Done()
<-start
h := fnv.New32a()
buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf, id)
h.Write(buf[:])
k := h.Sum(nil)
insert := func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("bench"))
return b.Put(k, []byte("filler"))
}
if err := db.Batch(insert); err != nil {
b.Error(err)
return
}
}(uint32(round))
}
close(start)
wg.Wait()
}
b.StopTimer()
validateBatchBench(b, db)
}
func BenchmarkDBBatchSingle(b *testing.B) {
db := NewTestDB()
defer db.Close()
db.MustCreateBucket([]byte("bench"))
b.ResetTimer()
for i := 0; i < b.N; i++ {
start := make(chan struct{})
var wg sync.WaitGroup
for round := 0; round < 1000; round++ {
wg.Add(1)
go func(id uint32) {
defer wg.Done()
<-start
h := fnv.New32a()
buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf, id)
h.Write(buf[:])
k := h.Sum(nil)
insert := func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("bench"))
return b.Put(k, []byte("filler"))
}
if err := db.Update(insert); err != nil {
b.Error(err)
return
}
}(uint32(round))
}
close(start)
wg.Wait()
}
b.StopTimer()
validateBatchBench(b, db)
}
func BenchmarkDBBatchManual10x100(b *testing.B) {
db := NewTestDB()
defer db.Close()
db.MustCreateBucket([]byte("bench"))
b.ResetTimer()
for i := 0; i < b.N; i++ {
start := make(chan struct{})
var wg sync.WaitGroup
for major := 0; major < 10; major++ {
wg.Add(1)
go func(id uint32) {
defer wg.Done()
<-start
insert100 := func(tx *bolt.Tx) error {
h := fnv.New32a()
buf := make([]byte, 4)
for minor := uint32(0); minor < 100; minor++ {
binary.LittleEndian.PutUint32(buf, uint32(id*100+minor))
h.Reset()
h.Write(buf[:])
k := h.Sum(nil)
b := tx.Bucket([]byte("bench"))
if err := b.Put(k, []byte("filler")); err != nil {
return err
}
}
return nil
}
if err := db.Update(insert100); err != nil {
b.Fatal(err)
}
}(uint32(major))
}
close(start)
wg.Wait()
}
b.StopTimer()
validateBatchBench(b, db)
}

View File

@ -1,148 +0,0 @@
package bolt_test
import (
"encoding/binary"
"fmt"
"io/ioutil"
"log"
"math/rand"
"net/http"
"net/http/httptest"
"os"
"github.com/boltdb/bolt"
)
// Set this to see how the counts are actually updated.
const verbose = false
// Counter updates a counter in Bolt for every URL path requested.
type counter struct {
db *bolt.DB
}
func (c counter) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// Communicates the new count from a successful database
// transaction.
var result uint64
increment := func(tx *bolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte("hits"))
if err != nil {
return err
}
key := []byte(req.URL.String())
// Decode handles key not found for us.
count := decode(b.Get(key)) + 1
b.Put(key, encode(count))
// All good, communicate new count.
result = count
return nil
}
if err := c.db.Batch(increment); err != nil {
http.Error(rw, err.Error(), 500)
return
}
if verbose {
log.Printf("server: %s: %d", req.URL.String(), result)
}
rw.Header().Set("Content-Type", "application/octet-stream")
fmt.Fprintf(rw, "%d\n", result)
}
func client(id int, base string, paths []string) error {
// Process paths in random order.
rng := rand.New(rand.NewSource(int64(id)))
permutation := rng.Perm(len(paths))
for i := range paths {
path := paths[permutation[i]]
resp, err := http.Get(base + path)
if err != nil {
return err
}
defer resp.Body.Close()
buf, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if verbose {
log.Printf("client: %s: %s", path, buf)
}
}
return nil
}
func ExampleDB_Batch() {
// Open the database.
db, _ := bolt.Open(tempfile(), 0666, nil)
defer os.Remove(db.Path())
defer db.Close()
// Start our web server
count := counter{db}
srv := httptest.NewServer(count)
defer srv.Close()
// Decrease the batch size to make things more interesting.
db.MaxBatchSize = 3
// Get every path multiple times concurrently.
const clients = 10
paths := []string{
"/foo",
"/bar",
"/baz",
"/quux",
"/thud",
"/xyzzy",
}
errors := make(chan error, clients)
for i := 0; i < clients; i++ {
go func(id int) {
errors <- client(id, srv.URL, paths)
}(i)
}
// Check all responses to make sure there's no error.
for i := 0; i < clients; i++ {
if err := <-errors; err != nil {
fmt.Printf("client error: %v", err)
return
}
}
// Check the final result
db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("hits"))
c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
fmt.Printf("hits to %s: %d\n", k, decode(v))
}
return nil
})
// Output:
// hits to /bar: 10
// hits to /baz: 10
// hits to /foo: 10
// hits to /quux: 10
// hits to /thud: 10
// hits to /xyzzy: 10
}
// encode marshals a counter.
func encode(n uint64) []byte {
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, n)
return buf
}
// decode unmarshals a counter. Nil buffers are decoded as 0.
func decode(buf []byte) uint64 {
if buf == nil {
return 0
}
return binary.BigEndian.Uint64(buf)
}

View File

@ -1,167 +0,0 @@
package bolt_test
import (
"testing"
"time"
"github.com/boltdb/bolt"
)
// Ensure two functions can perform updates in a single batch.
func TestDB_Batch(t *testing.T) {
db := NewTestDB()
defer db.Close()
db.MustCreateBucket([]byte("widgets"))
// Iterate over multiple updates in separate goroutines.
n := 2
ch := make(chan error)
for i := 0; i < n; i++ {
go func(i int) {
ch <- db.Batch(func(tx *bolt.Tx) error {
return tx.Bucket([]byte("widgets")).Put(u64tob(uint64(i)), []byte{})
})
}(i)
}
// Check all responses to make sure there's no error.
for i := 0; i < n; i++ {
if err := <-ch; err != nil {
t.Fatal(err)
}
}
// Ensure data is correct.
db.MustView(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("widgets"))
for i := 0; i < n; i++ {
if v := b.Get(u64tob(uint64(i))); v == nil {
t.Errorf("key not found: %d", i)
}
}
return nil
})
}
func TestDB_Batch_Panic(t *testing.T) {
db := NewTestDB()
defer db.Close()
var sentinel int
var bork = &sentinel
var problem interface{}
var err error
// Execute a function inside a batch that panics.
func() {
defer func() {
if p := recover(); p != nil {
problem = p
}
}()
err = db.Batch(func(tx *bolt.Tx) error {
panic(bork)
})
}()
// Verify there is no error.
if g, e := err, error(nil); g != e {
t.Fatalf("wrong error: %v != %v", g, e)
}
// Verify the panic was captured.
if g, e := problem, bork; g != e {
t.Fatalf("wrong error: %v != %v", g, e)
}
}
func TestDB_BatchFull(t *testing.T) {
db := NewTestDB()
defer db.Close()
db.MustCreateBucket([]byte("widgets"))
const size = 3
// buffered so we never leak goroutines
ch := make(chan error, size)
put := func(i int) {
ch <- db.Batch(func(tx *bolt.Tx) error {
return tx.Bucket([]byte("widgets")).Put(u64tob(uint64(i)), []byte{})
})
}
db.MaxBatchSize = size
// high enough to never trigger here
db.MaxBatchDelay = 1 * time.Hour
go put(1)
go put(2)
// Give the batch a chance to exhibit bugs.
time.Sleep(10 * time.Millisecond)
// not triggered yet
select {
case <-ch:
t.Fatalf("batch triggered too early")
default:
}
go put(3)
// Check all responses to make sure there's no error.
for i := 0; i < size; i++ {
if err := <-ch; err != nil {
t.Fatal(err)
}
}
// Ensure data is correct.
db.MustView(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("widgets"))
for i := 1; i <= size; i++ {
if v := b.Get(u64tob(uint64(i))); v == nil {
t.Errorf("key not found: %d", i)
}
}
return nil
})
}
func TestDB_BatchTime(t *testing.T) {
db := NewTestDB()
defer db.Close()
db.MustCreateBucket([]byte("widgets"))
const size = 1
// buffered so we never leak goroutines
ch := make(chan error, size)
put := func(i int) {
ch <- db.Batch(func(tx *bolt.Tx) error {
return tx.Bucket([]byte("widgets")).Put(u64tob(uint64(i)), []byte{})
})
}
db.MaxBatchSize = 1000
db.MaxBatchDelay = 0
go put(1)
// Batch must trigger by time alone.
// Check all responses to make sure there's no error.
for i := 0; i < size; i++ {
if err := <-ch; err != nil {
t.Fatal(err)
}
}
// Ensure data is correct.
db.MustView(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("widgets"))
for i := 1; i <= size; i++ {
if v := b.Get(u64tob(uint64(i))); v == nil {
t.Errorf("key not found: %d", i)
}
}
return nil
})
}

View File

@ -1,36 +0,0 @@
package bolt_test
import (
"fmt"
"path/filepath"
"reflect"
"runtime"
"testing"
)
// assert fails the test if the condition is false.
func assert(tb testing.TB, condition bool, msg string, v ...interface{}) {
if !condition {
_, file, line, _ := runtime.Caller(1)
fmt.Printf("\033[31m%s:%d: "+msg+"\033[39m\n\n", append([]interface{}{filepath.Base(file), line}, v...)...)
tb.FailNow()
}
}
// ok fails the test if an err is not nil.
func ok(tb testing.TB, err error) {
if err != nil {
_, file, line, _ := runtime.Caller(1)
fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error())
tb.FailNow()
}
}
// equals fails the test if exp is not equal to act.
func equals(tb testing.TB, exp, act interface{}) {
if !reflect.DeepEqual(exp, act) {
_, file, line, _ := runtime.Caller(1)
fmt.Printf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act)
tb.FailNow()
}
}

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,9 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"log"
"os" "os"
"reflect"
"sort" "sort"
"testing" "testing"
"testing/quick" "testing/quick"
@ -14,100 +16,149 @@ import (
// Ensure that a cursor can return a reference to the bucket that created it. // Ensure that a cursor can return a reference to the bucket that created it.
func TestCursor_Bucket(t *testing.T) { func TestCursor_Bucket(t *testing.T) {
db := NewTestDB() db := MustOpenDB()
defer db.Close() defer db.MustClose()
db.Update(func(tx *bolt.Tx) error { if err := db.Update(func(tx *bolt.Tx) error {
b, _ := tx.CreateBucket([]byte("widgets")) b, err := tx.CreateBucket([]byte("widgets"))
c := b.Cursor() if err != nil {
equals(t, b, c.Bucket()) t.Fatal(err)
}
if cb := b.Cursor().Bucket(); !reflect.DeepEqual(cb, b) {
t.Fatal("cursor bucket mismatch")
}
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
} }
// Ensure that a Tx cursor can seek to the appropriate keys. // Ensure that a Tx cursor can seek to the appropriate keys.
func TestCursor_Seek(t *testing.T) { func TestCursor_Seek(t *testing.T) {
db := NewTestDB() db := MustOpenDB()
defer db.Close() defer db.MustClose()
db.Update(func(tx *bolt.Tx) error { if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets")) b, err := tx.CreateBucket([]byte("widgets"))
ok(t, err) if err != nil {
ok(t, b.Put([]byte("foo"), []byte("0001"))) t.Fatal(err)
ok(t, b.Put([]byte("bar"), []byte("0002"))) }
ok(t, b.Put([]byte("baz"), []byte("0003"))) if err := b.Put([]byte("foo"), []byte("0001")); err != nil {
_, err = b.CreateBucket([]byte("bkt")) t.Fatal(err)
ok(t, err) }
if err := b.Put([]byte("bar"), []byte("0002")); err != nil {
t.Fatal(err)
}
if err := b.Put([]byte("baz"), []byte("0003")); err != nil {
t.Fatal(err)
}
if _, err := b.CreateBucket([]byte("bkt")); err != nil {
t.Fatal(err)
}
return nil return nil
}) }); err != nil {
db.View(func(tx *bolt.Tx) error { t.Fatal(err)
}
if err := db.View(func(tx *bolt.Tx) error {
c := tx.Bucket([]byte("widgets")).Cursor() c := tx.Bucket([]byte("widgets")).Cursor()
// Exact match should go to the key. // Exact match should go to the key.
k, v := c.Seek([]byte("bar")) if k, v := c.Seek([]byte("bar")); !bytes.Equal(k, []byte("bar")) {
equals(t, []byte("bar"), k) t.Fatalf("unexpected key: %v", k)
equals(t, []byte("0002"), v) } else if !bytes.Equal(v, []byte("0002")) {
t.Fatalf("unexpected value: %v", v)
}
// Inexact match should go to the next key. // Inexact match should go to the next key.
k, v = c.Seek([]byte("bas")) if k, v := c.Seek([]byte("bas")); !bytes.Equal(k, []byte("baz")) {
equals(t, []byte("baz"), k) t.Fatalf("unexpected key: %v", k)
equals(t, []byte("0003"), v) } else if !bytes.Equal(v, []byte("0003")) {
t.Fatalf("unexpected value: %v", v)
}
// Low key should go to the first key. // Low key should go to the first key.
k, v = c.Seek([]byte("")) if k, v := c.Seek([]byte("")); !bytes.Equal(k, []byte("bar")) {
equals(t, []byte("bar"), k) t.Fatalf("unexpected key: %v", k)
equals(t, []byte("0002"), v) } else if !bytes.Equal(v, []byte("0002")) {
t.Fatalf("unexpected value: %v", v)
}
// High key should return no key. // High key should return no key.
k, v = c.Seek([]byte("zzz")) if k, v := c.Seek([]byte("zzz")); k != nil {
assert(t, k == nil, "") t.Fatalf("expected nil key: %v", k)
assert(t, v == nil, "") } else if v != nil {
t.Fatalf("expected nil value: %v", v)
}
// Buckets should return their key but no value. // Buckets should return their key but no value.
k, v = c.Seek([]byte("bkt")) if k, v := c.Seek([]byte("bkt")); !bytes.Equal(k, []byte("bkt")) {
equals(t, []byte("bkt"), k) t.Fatalf("unexpected key: %v", k)
assert(t, v == nil, "") } else if v != nil {
t.Fatalf("expected nil value: %v", v)
}
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
} }
func TestCursor_Delete(t *testing.T) { func TestCursor_Delete(t *testing.T) {
db := NewTestDB() db := MustOpenDB()
defer db.Close() defer db.MustClose()
var count = 1000 const count = 1000
// Insert every other key between 0 and $count. // Insert every other key between 0 and $count.
db.Update(func(tx *bolt.Tx) error { if err := db.Update(func(tx *bolt.Tx) error {
b, _ := tx.CreateBucket([]byte("widgets")) b, err := tx.CreateBucket([]byte("widgets"))
if err != nil {
t.Fatal(err)
}
for i := 0; i < count; i += 1 { for i := 0; i < count; i += 1 {
k := make([]byte, 8) k := make([]byte, 8)
binary.BigEndian.PutUint64(k, uint64(i)) binary.BigEndian.PutUint64(k, uint64(i))
b.Put(k, make([]byte, 100)) if err := b.Put(k, make([]byte, 100)); err != nil {
t.Fatal(err)
}
}
if _, err := b.CreateBucket([]byte("sub")); err != nil {
t.Fatal(err)
} }
b.CreateBucket([]byte("sub"))
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
db.Update(func(tx *bolt.Tx) error { if err := db.Update(func(tx *bolt.Tx) error {
c := tx.Bucket([]byte("widgets")).Cursor() c := tx.Bucket([]byte("widgets")).Cursor()
bound := make([]byte, 8) bound := make([]byte, 8)
binary.BigEndian.PutUint64(bound, uint64(count/2)) binary.BigEndian.PutUint64(bound, uint64(count/2))
for key, _ := c.First(); bytes.Compare(key, bound) < 0; key, _ = c.Next() { for key, _ := c.First(); bytes.Compare(key, bound) < 0; key, _ = c.Next() {
if err := c.Delete(); err != nil { if err := c.Delete(); err != nil {
return err t.Fatal(err)
} }
} }
c.Seek([]byte("sub"))
err := c.Delete()
equals(t, err, bolt.ErrIncompatibleValue)
return nil
})
db.View(func(tx *bolt.Tx) error { c.Seek([]byte("sub"))
b := tx.Bucket([]byte("widgets")) if err := c.Delete(); err != bolt.ErrIncompatibleValue {
equals(t, b.Stats().KeyN, count/2+1) t.Fatalf("unexpected error: %s", err)
}
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
if err := db.View(func(tx *bolt.Tx) error {
stats := tx.Bucket([]byte("widgets")).Stats()
if stats.KeyN != count/2+1 {
t.Fatalf("unexpected KeyN: %d", stats.KeyN)
}
return nil
}); err != nil {
t.Fatal(err)
}
} }
// Ensure that a Tx cursor can seek to the appropriate keys when there are a // Ensure that a Tx cursor can seek to the appropriate keys when there are a
@ -116,25 +167,33 @@ func TestCursor_Delete(t *testing.T) {
// //
// Related: https://github.com/boltdb/bolt/pull/187 // Related: https://github.com/boltdb/bolt/pull/187
func TestCursor_Seek_Large(t *testing.T) { func TestCursor_Seek_Large(t *testing.T) {
db := NewTestDB() db := MustOpenDB()
defer db.Close() defer db.MustClose()
var count = 10000 var count = 10000
// Insert every other key between 0 and $count. // Insert every other key between 0 and $count.
db.Update(func(tx *bolt.Tx) error { if err := db.Update(func(tx *bolt.Tx) error {
b, _ := tx.CreateBucket([]byte("widgets")) b, err := tx.CreateBucket([]byte("widgets"))
if err != nil {
t.Fatal(err)
}
for i := 0; i < count; i += 100 { for i := 0; i < count; i += 100 {
for j := i; j < i+100; j += 2 { for j := i; j < i+100; j += 2 {
k := make([]byte, 8) k := make([]byte, 8)
binary.BigEndian.PutUint64(k, uint64(j)) binary.BigEndian.PutUint64(k, uint64(j))
b.Put(k, make([]byte, 100)) if err := b.Put(k, make([]byte, 100)); err != nil {
t.Fatal(err)
}
} }
} }
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
db.View(func(tx *bolt.Tx) error { if err := db.View(func(tx *bolt.Tx) error {
c := tx.Bucket([]byte("widgets")).Cursor() c := tx.Bucket([]byte("widgets")).Cursor()
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
seek := make([]byte, 8) seek := make([]byte, 8)
@ -145,171 +204,269 @@ func TestCursor_Seek_Large(t *testing.T) {
// The last seek is beyond the end of the the range so // The last seek is beyond the end of the the range so
// it should return nil. // it should return nil.
if i == count-1 { if i == count-1 {
assert(t, k == nil, "") if k != nil {
t.Fatal("expected nil key")
}
continue continue
} }
// Otherwise we should seek to the exact key or the next key. // Otherwise we should seek to the exact key or the next key.
num := binary.BigEndian.Uint64(k) num := binary.BigEndian.Uint64(k)
if i%2 == 0 { if i%2 == 0 {
equals(t, uint64(i), num) if num != uint64(i) {
t.Fatalf("unexpected num: %d", num)
}
} else { } else {
equals(t, uint64(i+1), num) if num != uint64(i+1) {
t.Fatalf("unexpected num: %d", num)
}
} }
} }
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
} }
// Ensure that a cursor can iterate over an empty bucket without error. // Ensure that a cursor can iterate over an empty bucket without error.
func TestCursor_EmptyBucket(t *testing.T) { func TestCursor_EmptyBucket(t *testing.T) {
db := NewTestDB() db := MustOpenDB()
defer db.Close() defer db.MustClose()
db.Update(func(tx *bolt.Tx) error { if err := db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucket([]byte("widgets")) _, err := tx.CreateBucket([]byte("widgets"))
return err return err
}) }); err != nil {
db.View(func(tx *bolt.Tx) error { t.Fatal(err)
}
if err := db.View(func(tx *bolt.Tx) error {
c := tx.Bucket([]byte("widgets")).Cursor() c := tx.Bucket([]byte("widgets")).Cursor()
k, v := c.First() k, v := c.First()
assert(t, k == nil, "") if k != nil {
assert(t, v == nil, "") t.Fatalf("unexpected key: %v", k)
} else if v != nil {
t.Fatalf("unexpected value: %v", v)
}
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
} }
// Ensure that a Tx cursor can reverse iterate over an empty bucket without error. // Ensure that a Tx cursor can reverse iterate over an empty bucket without error.
func TestCursor_EmptyBucketReverse(t *testing.T) { func TestCursor_EmptyBucketReverse(t *testing.T) {
db := NewTestDB() db := MustOpenDB()
defer db.Close() defer db.MustClose()
db.Update(func(tx *bolt.Tx) error { if err := db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucket([]byte("widgets")) _, err := tx.CreateBucket([]byte("widgets"))
return err return err
}) }); err != nil {
db.View(func(tx *bolt.Tx) error { t.Fatal(err)
}
if err := db.View(func(tx *bolt.Tx) error {
c := tx.Bucket([]byte("widgets")).Cursor() c := tx.Bucket([]byte("widgets")).Cursor()
k, v := c.Last() k, v := c.Last()
assert(t, k == nil, "") if k != nil {
assert(t, v == nil, "") t.Fatalf("unexpected key: %v", k)
} else if v != nil {
t.Fatalf("unexpected value: %v", v)
}
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
} }
// Ensure that a Tx cursor can iterate over a single root with a couple elements. // Ensure that a Tx cursor can iterate over a single root with a couple elements.
func TestCursor_Iterate_Leaf(t *testing.T) { func TestCursor_Iterate_Leaf(t *testing.T) {
db := NewTestDB() db := MustOpenDB()
defer db.Close() defer db.MustClose()
db.Update(func(tx *bolt.Tx) error { if err := db.Update(func(tx *bolt.Tx) error {
tx.CreateBucket([]byte("widgets")) b, err := tx.CreateBucket([]byte("widgets"))
tx.Bucket([]byte("widgets")).Put([]byte("baz"), []byte{}) if err != nil {
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte{0}) t.Fatal(err)
tx.Bucket([]byte("widgets")).Put([]byte("bar"), []byte{1}) }
if err := b.Put([]byte("baz"), []byte{}); err != nil {
t.Fatal(err)
}
if err := b.Put([]byte("foo"), []byte{0}); err != nil {
t.Fatal(err)
}
if err := b.Put([]byte("bar"), []byte{1}); err != nil {
t.Fatal(err)
}
return nil return nil
}) }); err != nil {
tx, _ := db.Begin(false) t.Fatal(err)
}
tx, err := db.Begin(false)
if err != nil {
t.Fatal(err)
}
defer func() { _ = tx.Rollback() }()
c := tx.Bucket([]byte("widgets")).Cursor() c := tx.Bucket([]byte("widgets")).Cursor()
k, v := c.First() k, v := c.First()
equals(t, string(k), "bar") if !bytes.Equal(k, []byte("bar")) {
equals(t, v, []byte{1}) t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, []byte{1}) {
t.Fatalf("unexpected value: %v", v)
}
k, v = c.Next() k, v = c.Next()
equals(t, string(k), "baz") if !bytes.Equal(k, []byte("baz")) {
equals(t, v, []byte{}) t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, []byte{}) {
t.Fatalf("unexpected value: %v", v)
}
k, v = c.Next() k, v = c.Next()
equals(t, string(k), "foo") if !bytes.Equal(k, []byte("foo")) {
equals(t, v, []byte{0}) t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, []byte{0}) {
t.Fatalf("unexpected value: %v", v)
}
k, v = c.Next() k, v = c.Next()
assert(t, k == nil, "") if k != nil {
assert(t, v == nil, "") t.Fatalf("expected nil key: %v", k)
} else if v != nil {
t.Fatalf("expected nil value: %v", v)
}
k, v = c.Next() k, v = c.Next()
assert(t, k == nil, "") if k != nil {
assert(t, v == nil, "") t.Fatalf("expected nil key: %v", k)
} else if v != nil {
t.Fatalf("expected nil value: %v", v)
}
tx.Rollback() if err := tx.Rollback(); err != nil {
t.Fatal(err)
}
} }
// Ensure that a Tx cursor can iterate in reverse over a single root with a couple elements. // Ensure that a Tx cursor can iterate in reverse over a single root with a couple elements.
func TestCursor_LeafRootReverse(t *testing.T) { func TestCursor_LeafRootReverse(t *testing.T) {
db := NewTestDB() db := MustOpenDB()
defer db.Close() defer db.MustClose()
db.Update(func(tx *bolt.Tx) error { if err := db.Update(func(tx *bolt.Tx) error {
tx.CreateBucket([]byte("widgets")) b, err := tx.CreateBucket([]byte("widgets"))
tx.Bucket([]byte("widgets")).Put([]byte("baz"), []byte{}) if err != nil {
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte{0}) t.Fatal(err)
tx.Bucket([]byte("widgets")).Put([]byte("bar"), []byte{1}) }
if err := b.Put([]byte("baz"), []byte{}); err != nil {
t.Fatal(err)
}
if err := b.Put([]byte("foo"), []byte{0}); err != nil {
t.Fatal(err)
}
if err := b.Put([]byte("bar"), []byte{1}); err != nil {
t.Fatal(err)
}
return nil return nil
}) }); err != nil {
tx, _ := db.Begin(false) t.Fatal(err)
}
tx, err := db.Begin(false)
if err != nil {
t.Fatal(err)
}
c := tx.Bucket([]byte("widgets")).Cursor() c := tx.Bucket([]byte("widgets")).Cursor()
k, v := c.Last() if k, v := c.Last(); !bytes.Equal(k, []byte("foo")) {
equals(t, string(k), "foo") t.Fatalf("unexpected key: %v", k)
equals(t, v, []byte{0}) } else if !bytes.Equal(v, []byte{0}) {
t.Fatalf("unexpected value: %v", v)
}
k, v = c.Prev() if k, v := c.Prev(); !bytes.Equal(k, []byte("baz")) {
equals(t, string(k), "baz") t.Fatalf("unexpected key: %v", k)
equals(t, v, []byte{}) } else if !bytes.Equal(v, []byte{}) {
t.Fatalf("unexpected value: %v", v)
}
k, v = c.Prev() if k, v := c.Prev(); !bytes.Equal(k, []byte("bar")) {
equals(t, string(k), "bar") t.Fatalf("unexpected key: %v", k)
equals(t, v, []byte{1}) } else if !bytes.Equal(v, []byte{1}) {
t.Fatalf("unexpected value: %v", v)
}
k, v = c.Prev() if k, v := c.Prev(); k != nil {
assert(t, k == nil, "") t.Fatalf("expected nil key: %v", k)
assert(t, v == nil, "") } else if v != nil {
t.Fatalf("expected nil value: %v", v)
}
k, v = c.Prev() if k, v := c.Prev(); k != nil {
assert(t, k == nil, "") t.Fatalf("expected nil key: %v", k)
assert(t, v == nil, "") } else if v != nil {
t.Fatalf("expected nil value: %v", v)
}
tx.Rollback() if err := tx.Rollback(); err != nil {
t.Fatal(err)
}
} }
// Ensure that a Tx cursor can restart from the beginning. // Ensure that a Tx cursor can restart from the beginning.
func TestCursor_Restart(t *testing.T) { func TestCursor_Restart(t *testing.T) {
db := NewTestDB() db := MustOpenDB()
defer db.Close() defer db.MustClose()
db.Update(func(tx *bolt.Tx) error { if err := db.Update(func(tx *bolt.Tx) error {
tx.CreateBucket([]byte("widgets")) b, err := tx.CreateBucket([]byte("widgets"))
tx.Bucket([]byte("widgets")).Put([]byte("bar"), []byte{}) if err != nil {
tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte{}) t.Fatal(err)
}
if err := b.Put([]byte("bar"), []byte{}); err != nil {
t.Fatal(err)
}
if err := b.Put([]byte("foo"), []byte{}); err != nil {
t.Fatal(err)
}
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
tx, _ := db.Begin(false) tx, err := db.Begin(false)
if err != nil {
t.Fatal(err)
}
c := tx.Bucket([]byte("widgets")).Cursor() c := tx.Bucket([]byte("widgets")).Cursor()
k, _ := c.First() if k, _ := c.First(); !bytes.Equal(k, []byte("bar")) {
equals(t, string(k), "bar") t.Fatalf("unexpected key: %v", k)
}
if k, _ := c.Next(); !bytes.Equal(k, []byte("foo")) {
t.Fatalf("unexpected key: %v", k)
}
k, _ = c.Next() if k, _ := c.First(); !bytes.Equal(k, []byte("bar")) {
equals(t, string(k), "foo") t.Fatalf("unexpected key: %v", k)
}
if k, _ := c.Next(); !bytes.Equal(k, []byte("foo")) {
t.Fatalf("unexpected key: %v", k)
}
k, _ = c.First() if err := tx.Rollback(); err != nil {
equals(t, string(k), "bar") t.Fatal(err)
}
k, _ = c.Next()
equals(t, string(k), "foo")
tx.Rollback()
} }
// Ensure that a cursor can skip over empty pages that have been deleted. // Ensure that a cursor can skip over empty pages that have been deleted.
func TestCursor_First_EmptyPages(t *testing.T) { func TestCursor_First_EmptyPages(t *testing.T) {
db := NewTestDB() db := MustOpenDB()
defer db.Close() defer db.MustClose()
// Create 1000 keys in the "widgets" bucket. // Create 1000 keys in the "widgets" bucket.
db.Update(func(tx *bolt.Tx) error { if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets")) b, err := tx.CreateBucket([]byte("widgets"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -322,10 +479,12 @@ func TestCursor_First_EmptyPages(t *testing.T) {
} }
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
// Delete half the keys and then try to iterate. // Delete half the keys and then try to iterate.
db.Update(func(tx *bolt.Tx) error { if err := db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("widgets")) b := tx.Bucket([]byte("widgets"))
for i := 0; i < 600; i++ { for i := 0; i < 600; i++ {
if err := b.Delete(u64tob(uint64(i))); err != nil { if err := b.Delete(u64tob(uint64(i))); err != nil {
@ -343,38 +502,61 @@ func TestCursor_First_EmptyPages(t *testing.T) {
} }
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
} }
// Ensure that a Tx can iterate over all elements in a bucket. // Ensure that a Tx can iterate over all elements in a bucket.
func TestCursor_QuickCheck(t *testing.T) { func TestCursor_QuickCheck(t *testing.T) {
f := func(items testdata) bool { f := func(items testdata) bool {
db := NewTestDB() db := MustOpenDB()
defer db.Close() defer db.MustClose()
// Bulk insert all values. // Bulk insert all values.
tx, _ := db.Begin(true) tx, err := db.Begin(true)
tx.CreateBucket([]byte("widgets")) if err != nil {
b := tx.Bucket([]byte("widgets")) t.Fatal(err)
for _, item := range items { }
ok(t, b.Put(item.Key, item.Value)) b, err := tx.CreateBucket([]byte("widgets"))
if err != nil {
t.Fatal(err)
}
for _, item := range items {
if err := b.Put(item.Key, item.Value); err != nil {
t.Fatal(err)
}
}
if err := tx.Commit(); err != nil {
t.Fatal(err)
} }
ok(t, tx.Commit())
// Sort test data. // Sort test data.
sort.Sort(items) sort.Sort(items)
// Iterate over all items and check consistency. // Iterate over all items and check consistency.
var index = 0 var index = 0
tx, _ = db.Begin(false) tx, err = db.Begin(false)
if err != nil {
t.Fatal(err)
}
c := tx.Bucket([]byte("widgets")).Cursor() c := tx.Bucket([]byte("widgets")).Cursor()
for k, v := c.First(); k != nil && index < len(items); k, v = c.Next() { for k, v := c.First(); k != nil && index < len(items); k, v = c.Next() {
equals(t, k, items[index].Key) if !bytes.Equal(k, items[index].Key) {
equals(t, v, items[index].Value) t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, items[index].Value) {
t.Fatalf("unexpected value: %v", v)
}
index++ index++
} }
equals(t, len(items), index) if len(items) != index {
tx.Rollback() t.Fatalf("unexpected item count: %v, expected %v", len(items), index)
}
if err := tx.Rollback(); err != nil {
t.Fatal(err)
}
return true return true
} }
@ -386,32 +568,52 @@ func TestCursor_QuickCheck(t *testing.T) {
// Ensure that a transaction can iterate over all elements in a bucket in reverse. // Ensure that a transaction can iterate over all elements in a bucket in reverse.
func TestCursor_QuickCheck_Reverse(t *testing.T) { func TestCursor_QuickCheck_Reverse(t *testing.T) {
f := func(items testdata) bool { f := func(items testdata) bool {
db := NewTestDB() db := MustOpenDB()
defer db.Close() defer db.MustClose()
// Bulk insert all values. // Bulk insert all values.
tx, _ := db.Begin(true) tx, err := db.Begin(true)
tx.CreateBucket([]byte("widgets")) if err != nil {
b := tx.Bucket([]byte("widgets")) t.Fatal(err)
for _, item := range items { }
ok(t, b.Put(item.Key, item.Value)) b, err := tx.CreateBucket([]byte("widgets"))
if err != nil {
t.Fatal(err)
}
for _, item := range items {
if err := b.Put(item.Key, item.Value); err != nil {
t.Fatal(err)
}
}
if err := tx.Commit(); err != nil {
t.Fatal(err)
} }
ok(t, tx.Commit())
// Sort test data. // Sort test data.
sort.Sort(revtestdata(items)) sort.Sort(revtestdata(items))
// Iterate over all items and check consistency. // Iterate over all items and check consistency.
var index = 0 var index = 0
tx, _ = db.Begin(false) tx, err = db.Begin(false)
if err != nil {
t.Fatal(err)
}
c := tx.Bucket([]byte("widgets")).Cursor() c := tx.Bucket([]byte("widgets")).Cursor()
for k, v := c.Last(); k != nil && index < len(items); k, v = c.Prev() { for k, v := c.Last(); k != nil && index < len(items); k, v = c.Prev() {
equals(t, k, items[index].Key) if !bytes.Equal(k, items[index].Key) {
equals(t, v, items[index].Value) t.Fatalf("unexpected key: %v", k)
} else if !bytes.Equal(v, items[index].Value) {
t.Fatalf("unexpected value: %v", v)
}
index++ index++
} }
equals(t, len(items), index) if len(items) != index {
tx.Rollback() t.Fatalf("unexpected item count: %v, expected %v", len(items), index)
}
if err := tx.Rollback(); err != nil {
t.Fatal(err)
}
return true return true
} }
@ -422,76 +624,114 @@ func TestCursor_QuickCheck_Reverse(t *testing.T) {
// Ensure that a Tx cursor can iterate over subbuckets. // Ensure that a Tx cursor can iterate over subbuckets.
func TestCursor_QuickCheck_BucketsOnly(t *testing.T) { func TestCursor_QuickCheck_BucketsOnly(t *testing.T) {
db := NewTestDB() db := MustOpenDB()
defer db.Close() defer db.MustClose()
db.Update(func(tx *bolt.Tx) error { if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets")) b, err := tx.CreateBucket([]byte("widgets"))
ok(t, err) if err != nil {
_, err = b.CreateBucket([]byte("foo")) t.Fatal(err)
ok(t, err) }
_, err = b.CreateBucket([]byte("bar")) if _, err := b.CreateBucket([]byte("foo")); err != nil {
ok(t, err) t.Fatal(err)
_, err = b.CreateBucket([]byte("baz")) }
ok(t, err) if _, err := b.CreateBucket([]byte("bar")); err != nil {
t.Fatal(err)
}
if _, err := b.CreateBucket([]byte("baz")); err != nil {
t.Fatal(err)
}
return nil return nil
}) }); err != nil {
db.View(func(tx *bolt.Tx) error { t.Fatal(err)
}
if err := db.View(func(tx *bolt.Tx) error {
var names []string var names []string
c := tx.Bucket([]byte("widgets")).Cursor() c := tx.Bucket([]byte("widgets")).Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() { for k, v := c.First(); k != nil; k, v = c.Next() {
names = append(names, string(k)) names = append(names, string(k))
assert(t, v == nil, "") if v != nil {
t.Fatalf("unexpected value: %v", v)
}
}
if !reflect.DeepEqual(names, []string{"bar", "baz", "foo"}) {
t.Fatalf("unexpected names: %+v", names)
} }
equals(t, names, []string{"bar", "baz", "foo"})
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
} }
// Ensure that a Tx cursor can reverse iterate over subbuckets. // Ensure that a Tx cursor can reverse iterate over subbuckets.
func TestCursor_QuickCheck_BucketsOnly_Reverse(t *testing.T) { func TestCursor_QuickCheck_BucketsOnly_Reverse(t *testing.T) {
db := NewTestDB() db := MustOpenDB()
defer db.Close() defer db.MustClose()
db.Update(func(tx *bolt.Tx) error { if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets")) b, err := tx.CreateBucket([]byte("widgets"))
ok(t, err) if err != nil {
_, err = b.CreateBucket([]byte("foo")) t.Fatal(err)
ok(t, err) }
_, err = b.CreateBucket([]byte("bar")) if _, err := b.CreateBucket([]byte("foo")); err != nil {
ok(t, err) t.Fatal(err)
_, err = b.CreateBucket([]byte("baz")) }
ok(t, err) if _, err := b.CreateBucket([]byte("bar")); err != nil {
t.Fatal(err)
}
if _, err := b.CreateBucket([]byte("baz")); err != nil {
t.Fatal(err)
}
return nil return nil
}) }); err != nil {
db.View(func(tx *bolt.Tx) error { t.Fatal(err)
}
if err := db.View(func(tx *bolt.Tx) error {
var names []string var names []string
c := tx.Bucket([]byte("widgets")).Cursor() c := tx.Bucket([]byte("widgets")).Cursor()
for k, v := c.Last(); k != nil; k, v = c.Prev() { for k, v := c.Last(); k != nil; k, v = c.Prev() {
names = append(names, string(k)) names = append(names, string(k))
assert(t, v == nil, "") if v != nil {
t.Fatalf("unexpected value: %v", v)
}
}
if !reflect.DeepEqual(names, []string{"foo", "baz", "bar"}) {
t.Fatalf("unexpected names: %+v", names)
} }
equals(t, names, []string{"foo", "baz", "bar"})
return nil return nil
}) }); err != nil {
t.Fatal(err)
}
} }
func ExampleCursor() { func ExampleCursor() {
// Open the database. // Open the database.
db, _ := bolt.Open(tempfile(), 0666, nil) db, err := bolt.Open(tempfile(), 0666, nil)
if err != nil {
log.Fatal(err)
}
defer os.Remove(db.Path()) defer os.Remove(db.Path())
defer db.Close()
// Start a read-write transaction. // Start a read-write transaction.
db.Update(func(tx *bolt.Tx) error { if err := db.Update(func(tx *bolt.Tx) error {
// Create a new bucket. // Create a new bucket.
tx.CreateBucket([]byte("animals")) b, err := tx.CreateBucket([]byte("animals"))
if err != nil {
return err
}
// Insert data into a bucket. // Insert data into a bucket.
b := tx.Bucket([]byte("animals")) if err := b.Put([]byte("dog"), []byte("fun")); err != nil {
b.Put([]byte("dog"), []byte("fun")) log.Fatal(err)
b.Put([]byte("cat"), []byte("lame")) }
b.Put([]byte("liger"), []byte("awesome")) if err := b.Put([]byte("cat"), []byte("lame")); err != nil {
log.Fatal(err)
}
if err := b.Put([]byte("liger"), []byte("awesome")); err != nil {
log.Fatal(err)
}
// Create a cursor for iteration. // Create a cursor for iteration.
c := b.Cursor() c := b.Cursor()
@ -506,7 +746,13 @@ func ExampleCursor() {
} }
return nil return nil
}) }); err != nil {
log.Fatal(err)
}
if err := db.Close(); err != nil {
log.Fatal(err)
}
// Output: // Output:
// A cat is lame. // A cat is lame.
@ -516,20 +762,30 @@ func ExampleCursor() {
func ExampleCursor_reverse() { func ExampleCursor_reverse() {
// Open the database. // Open the database.
db, _ := bolt.Open(tempfile(), 0666, nil) db, err := bolt.Open(tempfile(), 0666, nil)
if err != nil {
log.Fatal(err)
}
defer os.Remove(db.Path()) defer os.Remove(db.Path())
defer db.Close()
// Start a read-write transaction. // Start a read-write transaction.
db.Update(func(tx *bolt.Tx) error { if err := db.Update(func(tx *bolt.Tx) error {
// Create a new bucket. // Create a new bucket.
tx.CreateBucket([]byte("animals")) b, err := tx.CreateBucket([]byte("animals"))
if err != nil {
return err
}
// Insert data into a bucket. // Insert data into a bucket.
b := tx.Bucket([]byte("animals")) if err := b.Put([]byte("dog"), []byte("fun")); err != nil {
b.Put([]byte("dog"), []byte("fun")) log.Fatal(err)
b.Put([]byte("cat"), []byte("lame")) }
b.Put([]byte("liger"), []byte("awesome")) if err := b.Put([]byte("cat"), []byte("lame")); err != nil {
log.Fatal(err)
}
if err := b.Put([]byte("liger"), []byte("awesome")); err != nil {
log.Fatal(err)
}
// Create a cursor for iteration. // Create a cursor for iteration.
c := b.Cursor() c := b.Cursor()
@ -545,7 +801,14 @@ func ExampleCursor_reverse() {
} }
return nil return nil
}) }); err != nil {
log.Fatal(err)
}
// Close the database to release the file lock.
if err := db.Close(); err != nil {
log.Fatal(err)
}
// Output: // Output:
// A liger is awesome. // A liger is awesome.

136
db.go
View File

@ -1,8 +1,10 @@
package bolt package bolt
import ( import (
"errors"
"fmt" "fmt"
"hash/fnv" "hash/fnv"
"log"
"os" "os"
"runtime" "runtime"
"runtime/debug" "runtime/debug"
@ -387,7 +389,9 @@ func (db *DB) close() error {
// No need to unlock read-only file. // No need to unlock read-only file.
if !db.readOnly { if !db.readOnly {
// Unlock the file. // Unlock the file.
_ = funlock(db.file) if err := funlock(db.file); err != nil {
log.Printf("bolt.Close(): funlock error: %s", err)
}
} }
// Close the file descriptor. // Close the file descriptor.
@ -598,6 +602,136 @@ func (db *DB) View(fn func(*Tx) error) error {
return nil return nil
} }
// Batch calls fn as part of a batch. It behaves similar to Update,
// except:
//
// 1. concurrent Batch calls can be combined into a single Bolt
// transaction.
//
// 2. the function passed to Batch may be called multiple times,
// regardless of whether it returns error or not.
//
// This means that Batch function side effects must be idempotent and
// take permanent effect only after a successful return is seen in
// caller.
//
// The maximum batch size and delay can be adjusted with DB.MaxBatchSize
// and DB.MaxBatchDelay, respectively.
//
// Batch is only useful when there are multiple goroutines calling it.
func (db *DB) Batch(fn func(*Tx) error) error {
errCh := make(chan error, 1)
db.batchMu.Lock()
if (db.batch == nil) || (db.batch != nil && len(db.batch.calls) >= db.MaxBatchSize) {
// There is no existing batch, or the existing batch is full; start a new one.
db.batch = &batch{
db: db,
}
db.batch.timer = time.AfterFunc(db.MaxBatchDelay, db.batch.trigger)
}
db.batch.calls = append(db.batch.calls, call{fn: fn, err: errCh})
if len(db.batch.calls) >= db.MaxBatchSize {
// wake up batch, it's ready to run
go db.batch.trigger()
}
db.batchMu.Unlock()
err := <-errCh
if err == trySolo {
err = db.Update(fn)
}
return err
}
type call struct {
fn func(*Tx) error
err chan<- error
}
type batch struct {
db *DB
timer *time.Timer
start sync.Once
calls []call
}
// trigger runs the batch if it hasn't already been run.
func (b *batch) trigger() {
b.start.Do(b.run)
}
// run performs the transactions in the batch and communicates results
// back to DB.Batch.
func (b *batch) run() {
b.db.batchMu.Lock()
b.timer.Stop()
// Make sure no new work is added to this batch, but don't break
// other batches.
if b.db.batch == b {
b.db.batch = nil
}
b.db.batchMu.Unlock()
retry:
for len(b.calls) > 0 {
var failIdx = -1
err := b.db.Update(func(tx *Tx) error {
for i, c := range b.calls {
if err := safelyCall(c.fn, tx); err != nil {
failIdx = i
return err
}
}
return nil
})
if failIdx >= 0 {
// take the failing transaction out of the batch. it's
// safe to shorten b.calls here because db.batch no longer
// points to us, and we hold the mutex anyway.
c := b.calls[failIdx]
b.calls[failIdx], b.calls = b.calls[len(b.calls)-1], b.calls[:len(b.calls)-1]
// tell the submitter re-run it solo, continue with the rest of the batch
c.err <- trySolo
continue retry
}
// pass success, or bolt internal errors, to all callers
for _, c := range b.calls {
if c.err != nil {
c.err <- err
}
}
break retry
}
}
// trySolo is a special sentinel error value used for signaling that a
// transaction function should be re-run. It should never be seen by
// callers.
var trySolo = errors.New("batch function returned an error and should be re-run solo")
type panicked struct {
reason interface{}
}
func (p panicked) Error() string {
if err, ok := p.reason.(error); ok {
return err.Error()
}
return fmt.Sprintf("panic: %v", p.reason)
}
func safelyCall(fn func(*Tx) error, tx *Tx) (err error) {
defer func() {
if p := recover(); p != nil {
err = panicked{p}
}
}()
return fn(tx)
}
// Sync executes fdatasync() against the database file handle. // Sync executes fdatasync() against the database file handle.
// //
// This is not necessary under normal operation, however, if you use NoSync // This is not necessary under normal operation, however, if you use NoSync

1314
db_test.go

File diff suppressed because it is too large Load Diff

View File

@ -117,7 +117,9 @@ func TestFreelist_write(t *testing.T) {
f.pending[100] = []pgid{28, 11} f.pending[100] = []pgid{28, 11}
f.pending[101] = []pgid{3} f.pending[101] = []pgid{3}
p := (*page)(unsafe.Pointer(&buf[0])) p := (*page)(unsafe.Pointer(&buf[0]))
f.write(p) if err := f.write(p); err != nil {
t.Fatal(err)
}
// Read the page back out. // Read the page back out.
f2 := newFreelist() f2 := newFreelist()

View File

@ -42,8 +42,8 @@ func testSimulate(t *testing.T, threadCount, parallelism int) {
var versions = make(map[int]*QuickDB) var versions = make(map[int]*QuickDB)
versions[1] = NewQuickDB() versions[1] = NewQuickDB()
db := NewTestDB() db := MustOpenDB()
defer db.Close() defer db.MustClose()
var mutex sync.Mutex var mutex sync.Mutex
@ -89,10 +89,12 @@ func testSimulate(t *testing.T, threadCount, parallelism int) {
versions[tx.ID()] = qdb versions[tx.ID()] = qdb
mutex.Unlock() mutex.Unlock()
ok(t, tx.Commit()) if err := tx.Commit(); err != nil {
t.Fatal(err)
}
}() }()
} else { } else {
defer tx.Rollback() defer func() { _ = tx.Rollback() }()
} }
// Ignore operation if we don't have data yet. // Ignore operation if we don't have data yet.

2
tx.go
View File

@ -285,7 +285,7 @@ func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) {
if err != nil { if err != nil {
return 0, err return 0, err
} }
defer f.Close() defer func() { _ = f.Close() }()
// Copy the meta pages. // Copy the meta pages.
tx.db.metalock.Lock() tx.db.metalock.Lock()

View File

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