mirror of https://github.com/hak5/bolt.git
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
parent
2c56b2a28a
commit
8b08bd4a80
50
Makefile
50
Makefile
|
@ -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
138
batch.go
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
167
batch_test.go
167
batch_test.go
|
@ -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
|
|
||||||
})
|
|
||||||
}
|
|
36
bolt_test.go
36
bolt_test.go
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
1824
bucket_test.go
1824
bucket_test.go
File diff suppressed because it is too large
Load Diff
711
cursor_test.go
711
cursor_test.go
|
@ -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
136
db.go
|
@ -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
1314
db_test.go
File diff suppressed because it is too large
Load Diff
|
@ -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()
|
||||||
|
|
|
@ -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
2
tx.go
|
@ -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()
|
||||||
|
|
732
tx_test.go
732
tx_test.go
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue