aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md14
-rw-r--r--appveyor.yml18
-rw-r--r--bolt_ppc.go9
-rw-r--r--bolt_ppc64.go9
-rw-r--r--bolt_unix.go8
-rw-r--r--bolt_unix_solaris.go8
-rw-r--r--bolt_windows.go22
-rw-r--r--bucket_test.go40
-rw-r--r--db.go19
-rw-r--r--node.go37
-rw-r--r--page.go4
-rw-r--r--tx.go46
-rw-r--r--tx_test.go2
13 files changed, 168 insertions, 68 deletions
diff --git a/README.md b/README.md
index 82e8574..4c9312c 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-Bolt [![Build Status](https://drone.io/github.com/boltdb/bolt/status.png)](https://drone.io/github.com/boltdb/bolt/latest) [![Coverage Status](https://coveralls.io/repos/boltdb/bolt/badge.svg?branch=master)](https://coveralls.io/r/boltdb/bolt?branch=master) [![GoDoc](https://godoc.org/github.com/boltdb/bolt?status.svg)](https://godoc.org/github.com/boltdb/bolt) ![Version](https://img.shields.io/badge/version-1.0-green.svg)
+Bolt [![Coverage Status](https://coveralls.io/repos/boltdb/bolt/badge.svg?branch=master)](https://coveralls.io/r/boltdb/bolt?branch=master) [![GoDoc](https://godoc.org/github.com/boltdb/bolt?status.svg)](https://godoc.org/github.com/boltdb/bolt) ![Version](https://img.shields.io/badge/version-1.0-green.svg)
====
Bolt is a pure Go key/value store inspired by [Howard Chu's][hyc_symas]
@@ -427,6 +427,8 @@ db.View(func(tx *bolt.Tx) error {
})
```
+Note that, while RFC3339 is sortable, the Golang implementation of RFC3339Nano does not use a fixed number of digits after the decimal point and is therefore not sortable.
+
#### ForEach()
@@ -437,7 +439,7 @@ all the keys in a bucket:
db.View(func(tx *bolt.Tx) error {
// Assume bucket exists and has keys
b := tx.Bucket([]byte("MyBucket"))
-
+
b.ForEach(func(k, v []byte) error {
fmt.Printf("key=%s, value=%s\n", k, v)
return nil
@@ -617,7 +619,7 @@ Boltmobiledemo.BoltDB boltDB = Boltmobiledemo.NewBoltDB(path)
{
NSURL* URL= [NSURL fileURLWithPath: filePathString];
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
-
+
NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
forKey: NSURLIsExcludedFromBackupKey error: &error];
@@ -835,6 +837,12 @@ Below is a list of public, open source projects that use Bolt:
backed by boltdb.
* [buckets](https://github.com/joyrexus/buckets) - a bolt wrapper streamlining
simple tx and key scans.
+* [mbuckets](https://github.com/abhigupta912/mbuckets) - A Bolt wrapper that allows easy operations on multi level (nested) buckets.
* [Request Baskets](https://github.com/darklynx/request-baskets) - A web service to collect arbitrary HTTP requests and inspect them via REST API or simple web UI, similar to [RequestBin](http://requestb.in/) service
+* [Go Report Card](https://goreportcard.com/) - Go code quality report cards as a (free and open source) service.
+* [Boltdb Boilerplate](https://github.com/bobintornado/boltdb-boilerplate) - Boilerplate wrapper around bolt aiming to make simple calls one-liners.
+* [lru](https://github.com/crowdriff/lru) - Easy to use Bolt-backed Least-Recently-Used (LRU) read-through cache with chainable remote stores.
+* [Storm](https://github.com/asdine/storm) - A simple ORM around BoltDB.
+* [GoWebApp](https://github.com/josephspurrier/gowebapp) - A basic MVC web application in Go using BoltDB.
If you are using Bolt in a project please send a pull request to add it to the list.
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..6e26e94
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,18 @@
+version: "{build}"
+
+os: Windows Server 2012 R2
+
+clone_folder: c:\gopath\src\github.com\boltdb\bolt
+
+environment:
+ GOPATH: c:\gopath
+
+install:
+ - echo %PATH%
+ - echo %GOPATH%
+ - go version
+ - go env
+ - go get -v -t ./...
+
+build_script:
+ - go test -v ./...
diff --git a/bolt_ppc.go b/bolt_ppc.go
new file mode 100644
index 0000000..645ddc3
--- /dev/null
+++ b/bolt_ppc.go
@@ -0,0 +1,9 @@
+// +build ppc
+
+package bolt
+
+// maxMapSize represents the largest mmap size supported by Bolt.
+const maxMapSize = 0x7FFFFFFF // 2GB
+
+// maxAllocSize is the size used when creating array pointers.
+const maxAllocSize = 0xFFFFFFF
diff --git a/bolt_ppc64.go b/bolt_ppc64.go
new file mode 100644
index 0000000..2dc6be0
--- /dev/null
+++ b/bolt_ppc64.go
@@ -0,0 +1,9 @@
+// +build ppc64
+
+package bolt
+
+// maxMapSize represents the largest mmap size supported by Bolt.
+const maxMapSize = 0xFFFFFFFFFFFF // 256TB
+
+// maxAllocSize is the size used when creating array pointers.
+const maxAllocSize = 0x7FFFFFFF
diff --git a/bolt_unix.go b/bolt_unix.go
index 4b0723a..cad62dd 100644
--- a/bolt_unix.go
+++ b/bolt_unix.go
@@ -11,7 +11,7 @@ import (
)
// flock acquires an advisory lock on a file descriptor.
-func flock(f *os.File, exclusive bool, timeout time.Duration) error {
+func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error {
var t time.Time
for {
// If we're beyond our timeout then return an error.
@@ -27,7 +27,7 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error {
}
// Otherwise attempt to obtain an exclusive lock.
- err := syscall.Flock(int(f.Fd()), flag|syscall.LOCK_NB)
+ err := syscall.Flock(int(db.file.Fd()), flag|syscall.LOCK_NB)
if err == nil {
return nil
} else if err != syscall.EWOULDBLOCK {
@@ -40,8 +40,8 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error {
}
// funlock releases an advisory lock on a file descriptor.
-func funlock(f *os.File) error {
- return syscall.Flock(int(f.Fd()), syscall.LOCK_UN)
+func funlock(db *DB) error {
+ return syscall.Flock(int(db.file.Fd()), syscall.LOCK_UN)
}
// mmap memory maps a DB's data file.
diff --git a/bolt_unix_solaris.go b/bolt_unix_solaris.go
index 1c4e48d..307bf2b 100644
--- a/bolt_unix_solaris.go
+++ b/bolt_unix_solaris.go
@@ -11,7 +11,7 @@ import (
)
// flock acquires an advisory lock on a file descriptor.
-func flock(f *os.File, exclusive bool, timeout time.Duration) error {
+func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error {
var t time.Time
for {
// If we're beyond our timeout then return an error.
@@ -32,7 +32,7 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error {
} else {
lock.Type = syscall.F_RDLCK
}
- err := syscall.FcntlFlock(f.Fd(), syscall.F_SETLK, &lock)
+ err := syscall.FcntlFlock(db.file.Fd(), syscall.F_SETLK, &lock)
if err == nil {
return nil
} else if err != syscall.EAGAIN {
@@ -45,13 +45,13 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error {
}
// funlock releases an advisory lock on a file descriptor.
-func funlock(f *os.File) error {
+func funlock(db *DB) error {
var lock syscall.Flock_t
lock.Start = 0
lock.Len = 0
lock.Type = syscall.F_UNLCK
lock.Whence = 0
- return syscall.FcntlFlock(uintptr(f.Fd()), syscall.F_SETLK, &lock)
+ return syscall.FcntlFlock(uintptr(db.file.Fd()), syscall.F_SETLK, &lock)
}
// mmap memory maps a DB's data file.
diff --git a/bolt_windows.go b/bolt_windows.go
index 91c4968..d538e6a 100644
--- a/bolt_windows.go
+++ b/bolt_windows.go
@@ -16,6 +16,8 @@ var (
)
const (
+ lockExt = ".lock"
+
// see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx
flagLockExclusive = 2
flagLockFailImmediately = 1
@@ -46,7 +48,16 @@ func fdatasync(db *DB) error {
}
// flock acquires an advisory lock on a file descriptor.
-func flock(f *os.File, exclusive bool, timeout time.Duration) error {
+func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error {
+ // Create a separate lock file on windows because a process
+ // cannot share an exclusive lock on the same file. This is
+ // needed during Tx.WriteTo().
+ f, err := os.OpenFile(db.path+lockExt, os.O_CREATE, mode)
+ if err != nil {
+ return err
+ }
+ db.lockfile = f
+
var t time.Time
for {
// If we're beyond our timeout then return an error.
@@ -62,7 +73,7 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error {
flag |= flagLockExclusive
}
- err := lockFileEx(syscall.Handle(f.Fd()), flag, 0, 1, 0, &syscall.Overlapped{})
+ err := lockFileEx(syscall.Handle(db.lockfile.Fd()), flag, 0, 1, 0, &syscall.Overlapped{})
if err == nil {
return nil
} else if err != errLockViolation {
@@ -75,8 +86,11 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error {
}
// funlock releases an advisory lock on a file descriptor.
-func funlock(f *os.File) error {
- return unlockFileEx(syscall.Handle(f.Fd()), 0, 1, 0, &syscall.Overlapped{})
+func funlock(db *DB) error {
+ err := unlockFileEx(syscall.Handle(db.lockfile.Fd()), 0, 1, 0, &syscall.Overlapped{})
+ db.lockfile.Close()
+ os.Remove(db.path+lockExt)
+ return err
}
// mmap memory maps a DB's data file.
diff --git a/bucket_test.go b/bucket_test.go
index a02c367..528fec2 100644
--- a/bucket_test.go
+++ b/bucket_test.go
@@ -80,6 +80,46 @@ func TestBucket_Get_IncompatibleValue(t *testing.T) {
}
}
+// Ensure that a slice returned from a bucket has a capacity equal to its length.
+// This also allows slices to be appended to since it will require a realloc by Go.
+//
+// https://github.com/boltdb/bolt/issues/544
+func TestBucket_Get_Capacity(t *testing.T) {
+ db := MustOpenDB()
+ defer db.MustClose()
+
+ // Write key to a bucket.
+ if err := db.Update(func(tx *bolt.Tx) error {
+ b, err := tx.CreateBucket([]byte("bucket"))
+ if err != nil {
+ return err
+ }
+ return b.Put([]byte("key"), []byte("val"))
+ }); err != nil {
+ t.Fatal(err)
+ }
+
+ // Retrieve value and attempt to append to it.
+ if err := db.Update(func(tx *bolt.Tx) error {
+ k, v := tx.Bucket([]byte("bucket")).Cursor().First()
+
+ // Verify capacity.
+ if len(k) != cap(k) {
+ t.Fatalf("unexpected key slice capacity: %d", cap(k))
+ } else if len(v) != cap(v) {
+ t.Fatalf("unexpected value slice capacity: %d", cap(v))
+ }
+
+ // Ensure slice can be appended to without a segfault.
+ k = append(k, []byte("123")...)
+ v = append(v, []byte("123")...)
+
+ return nil
+ }); err != nil {
+ t.Fatal(err)
+ }
+}
+
// Ensure that a bucket can write a key/value.
func TestBucket_Put(t *testing.T) {
db := MustOpenDB()
diff --git a/db.go b/db.go
index 5d48709..9f80681 100644
--- a/db.go
+++ b/db.go
@@ -96,7 +96,8 @@ type DB struct {
path string
file *os.File
- dataref []byte // mmap'ed readonly, write throws SEGV
+ lockfile *os.File // windows only
+ dataref []byte // mmap'ed readonly, write throws SEGV
data *[maxMapSize]byte
datasz int
filesz int // current on disk file size
@@ -180,7 +181,7 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
// if !options.ReadOnly.
// The database file is locked using the shared lock (more than one process may
// hold a lock at the same time) otherwise (options.ReadOnly is set).
- if err := flock(db.file, !db.readOnly, options.Timeout); err != nil {
+ if err := flock(db, mode, !db.readOnly, options.Timeout); err != nil {
_ = db.close()
return nil, err
}
@@ -382,10 +383,13 @@ func (db *DB) Close() error {
}
func (db *DB) close() error {
+ if !db.opened {
+ return nil
+ }
+
db.opened = false
db.freelist = nil
- db.path = ""
// Clear ops.
db.ops.writeAt = nil
@@ -400,7 +404,7 @@ func (db *DB) close() error {
// No need to unlock read-only file.
if !db.readOnly {
// Unlock the file.
- if err := funlock(db.file); err != nil {
+ if err := funlock(db); err != nil {
log.Printf("bolt.Close(): funlock error: %s", err)
}
}
@@ -412,6 +416,7 @@ func (db *DB) close() error {
db.file = nil
}
+ db.path = ""
return nil
}
@@ -838,8 +843,10 @@ func (db *DB) grow(sz int) error {
// Truncate and fsync to ensure file size metadata is flushed.
// https://github.com/boltdb/bolt/issues/284
if !db.NoGrowSync && !db.readOnly {
- if err := db.file.Truncate(int64(sz)); err != nil {
- return fmt.Errorf("file resize error: %s", err)
+ if runtime.GOOS != "windows" {
+ if err := db.file.Truncate(int64(sz)); err != nil {
+ return fmt.Errorf("file resize error: %s", err)
+ }
}
if err := db.file.Sync(); err != nil {
return fmt.Errorf("file sync error: %s", err)
diff --git a/node.go b/node.go
index c9fb21c..e9d64af 100644
--- a/node.go
+++ b/node.go
@@ -463,43 +463,6 @@ func (n *node) rebalance() {
target = n.prevSibling()
}
- // If target node has extra nodes then just move one over.
- if target.numChildren() > target.minKeys() {
- if useNextSibling {
- // Reparent and move node.
- if child, ok := n.bucket.nodes[target.inodes[0].pgid]; ok {
- child.parent.removeChild(child)
- child.parent = n
- child.parent.children = append(child.parent.children, child)
- }
- n.inodes = append(n.inodes, target.inodes[0])
- target.inodes = target.inodes[1:]
-
- // Update target key on parent.
- target.parent.put(target.key, target.inodes[0].key, nil, target.pgid, 0)
- target.key = target.inodes[0].key
- _assert(len(target.key) > 0, "rebalance(1): zero-length node key")
- } else {
- // Reparent and move node.
- if child, ok := n.bucket.nodes[target.inodes[len(target.inodes)-1].pgid]; ok {
- child.parent.removeChild(child)
- child.parent = n
- child.parent.children = append(child.parent.children, child)
- }
- n.inodes = append(n.inodes, inode{})
- copy(n.inodes[1:], n.inodes)
- n.inodes[0] = target.inodes[len(target.inodes)-1]
- target.inodes = target.inodes[:len(target.inodes)-1]
- }
-
- // Update parent key for node.
- n.parent.put(n.key, n.inodes[0].key, nil, n.pgid, 0)
- n.key = n.inodes[0].key
- _assert(len(n.key) > 0, "rebalance(2): zero-length node key")
-
- return
- }
-
// If both this node and the target node are too small then merge them.
if useNextSibling {
// Reparent all child nodes being moved.
diff --git a/page.go b/page.go
index 818aa1b..4a55528 100644
--- a/page.go
+++ b/page.go
@@ -111,13 +111,13 @@ type leafPageElement struct {
// key returns a byte slice of the node key.
func (n *leafPageElement) key() []byte {
buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
- return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos]))[:n.ksize]
+ return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos]))[:n.ksize:n.ksize]
}
// value returns a byte slice of the node value.
func (n *leafPageElement) value() []byte {
buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
- return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos+n.ksize]))[:n.vsize]
+ return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos+n.ksize]))[:n.vsize:n.vsize]
}
// PageInfo represents human readable information about a page.
diff --git a/tx.go b/tx.go
index 09a39fb..73538d9 100644
--- a/tx.go
+++ b/tx.go
@@ -5,6 +5,7 @@ import (
"io"
"os"
"sort"
+ "strings"
"time"
"unsafe"
)
@@ -202,8 +203,17 @@ func (tx *Tx) Commit() error {
// If strict mode is enabled then perform a consistency check.
// Only the first consistency error is reported in the panic.
if tx.db.StrictMode {
- if err, ok := <-tx.Check(); ok {
- panic("check fail: " + err.Error())
+ ch := tx.Check()
+ var errs []string
+ for {
+ err, ok := <-ch
+ if !ok {
+ break
+ }
+ errs = append(errs, err.Error())
+ }
+ if len(errs) > 0 {
+ panic("check fail: " + strings.Join(errs, "\n"))
}
}
@@ -297,12 +307,34 @@ func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) {
}
defer func() { _ = f.Close() }()
- // Copy the meta pages.
- tx.db.metalock.Lock()
- n, err = io.CopyN(w, f, int64(tx.db.pageSize*2))
- tx.db.metalock.Unlock()
+ // Generate a meta page. We use the same page data for both meta pages.
+ buf := make([]byte, tx.db.pageSize)
+ page := (*page)(unsafe.Pointer(&buf[0]))
+ page.flags = metaPageFlag
+ *page.meta() = *tx.meta
+
+ // Write meta 0.
+ page.id = 0
+ page.meta().checksum = page.meta().sum64()
+ nn, err := w.Write(buf)
+ n += int64(nn)
+ if err != nil {
+ return n, fmt.Errorf("meta 0 copy: %s", err)
+ }
+
+ // Write meta 1 with a lower transaction id.
+ page.id = 1
+ page.meta().txid -= 1
+ page.meta().checksum = page.meta().sum64()
+ nn, err = w.Write(buf)
+ n += int64(nn)
if err != nil {
- return n, fmt.Errorf("meta copy: %s", err)
+ return n, fmt.Errorf("meta 1 copy: %s", err)
+ }
+
+ // Move past the meta pages in the file.
+ if _, err := f.Seek(int64(tx.db.pageSize*2), os.SEEK_SET); err != nil {
+ return n, fmt.Errorf("seek: %s", err)
}
// Copy data pages.
diff --git a/tx_test.go b/tx_test.go
index 18ff166..2201e79 100644
--- a/tx_test.go
+++ b/tx_test.go
@@ -570,7 +570,7 @@ func TestTx_CopyFile_Error_Meta(t *testing.T) {
if err := db.View(func(tx *bolt.Tx) error {
return tx.Copy(&failWriter{})
- }); err == nil || err.Error() != "meta copy: error injected for tests" {
+ }); err == nil || err.Error() != "meta 0 copy: error injected for tests" {
t.Fatalf("unexpected error: %v", err)
}
}