Rename Transaction to Tx.

I changed the Transaction/RWTransaction types to Tx/RWTx, respectively. This makes the naming
more consistent with other packages such as database/sql. The txnid is changed to txid as well.
pull/34/head
Ben Johnson 2014-03-08 17:01:49 -07:00
parent 64fcacedfa
commit 57376f0905
17 changed files with 259 additions and 273 deletions

13
TODO
View File

@ -1,13 +0,0 @@
TODO
====
X Open DB.
X Initialize transaction.
- Cursor First, Get(key), Next
- RWTransaction.insert()
- rebalance
- adjust cursors
- RWTransaction Commmit

View File

@ -8,8 +8,8 @@ import (
type Bucket struct { type Bucket struct {
*bucket *bucket
name string name string
transaction *Transaction tx *Tx
rwtransaction *RWTransaction rwtx *RWTx
} }
// bucket represents the on-file representation of a bucket. // bucket represents the on-file representation of a bucket.
@ -25,15 +25,15 @@ func (b *Bucket) Name() string {
// Writable returns whether the bucket is writable. // Writable returns whether the bucket is writable.
func (b *Bucket) Writable() bool { func (b *Bucket) Writable() bool {
return (b.rwtransaction != nil) return (b.rwtx != nil)
} }
// Cursor creates a cursor associated with the bucket. // Cursor creates a cursor associated with the bucket.
// The cursor is only valid as long as the Transaction is open. // The cursor is only valid as long as the transaction is open.
// Do not use a cursor after the transaction is closed. // Do not use a cursor after the transaction is closed.
func (b *Bucket) Cursor() *Cursor { func (b *Bucket) Cursor() *Cursor {
return &Cursor{ return &Cursor{
transaction: b.transaction, tx: b.tx,
root: b.root, root: b.root,
stack: make([]elemRef, 0), stack: make([]elemRef, 0),
} }
@ -74,7 +74,7 @@ func (b *Bucket) Put(key []byte, value []byte) error {
c.Seek(key) c.Seek(key)
// Insert the key/value. // Insert the key/value.
c.node(b.rwtransaction).put(key, key, value, 0) c.node(b.rwtx).put(key, key, value, 0)
return nil return nil
} }
@ -92,7 +92,7 @@ func (b *Bucket) Delete(key []byte) error {
c.Seek(key) c.Seek(key)
// Delete the node if we have a matching key. // Delete the node if we have a matching key.
c.node(b.rwtransaction).del(key) c.node(b.rwtx).del(key)
return nil return nil
} }
@ -130,7 +130,7 @@ func (b *Bucket) ForEach(fn func(k, v []byte) error) error {
// Stat returns stats on a bucket. // Stat returns stats on a bucket.
func (b *Bucket) Stat() *BucketStat { func (b *Bucket) Stat() *BucketStat {
s := &BucketStat{} s := &BucketStat{}
b.transaction.forEachPage(b.root, 0, func(p *page, depth int) { b.tx.forEachPage(b.root, 0, func(p *page, depth int) {
if (p.flags & leafPageFlag) != 0 { if (p.flags & leafPageFlag) != 0 {
s.LeafPageCount++ s.LeafPageCount++
s.KeyCount += int(p.count) s.KeyCount += int(p.count)

View File

@ -27,7 +27,7 @@ func TestBucketGetNonExistent(t *testing.T) {
func TestBucketGetFromNode(t *testing.T) { func TestBucketGetFromNode(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
db.CreateBucket("widgets") db.CreateBucket("widgets")
db.Do(func(txn *RWTransaction) error { db.Do(func(txn *RWTx) error {
b := txn.Bucket("widgets") b := txn.Bucket("widgets")
b.Put([]byte("foo"), []byte("bar")) b.Put([]byte("foo"), []byte("bar"))
value := b.Get([]byte("foo")) value := b.Get([]byte("foo"))
@ -54,7 +54,7 @@ func TestBucketPut(t *testing.T) {
func TestBucketPutReadOnly(t *testing.T) { func TestBucketPutReadOnly(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
db.CreateBucket("widgets") db.CreateBucket("widgets")
db.With(func(txn *Transaction) error { db.With(func(txn *Tx) error {
b := txn.Bucket("widgets") b := txn.Bucket("widgets")
err := b.Put([]byte("foo"), []byte("bar")) err := b.Put([]byte("foo"), []byte("bar"))
assert.Equal(t, err, ErrBucketNotWritable) assert.Equal(t, err, ErrBucketNotWritable)
@ -81,7 +81,7 @@ func TestBucketDelete(t *testing.T) {
func TestBucketDeleteReadOnly(t *testing.T) { func TestBucketDeleteReadOnly(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
db.CreateBucket("widgets") db.CreateBucket("widgets")
db.With(func(txn *Transaction) error { db.With(func(txn *Tx) error {
b := txn.Bucket("widgets") b := txn.Bucket("widgets")
err := b.Delete([]byte("foo")) err := b.Delete([]byte("foo"))
assert.Equal(t, err, ErrBucketNotWritable) assert.Equal(t, err, ErrBucketNotWritable)
@ -120,7 +120,7 @@ func TestBucketNextSequence(t *testing.T) {
func TestBucketNextSequenceReadOnly(t *testing.T) { func TestBucketNextSequenceReadOnly(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
db.CreateBucket("widgets") db.CreateBucket("widgets")
db.With(func(txn *Transaction) error { db.With(func(txn *Tx) error {
b := txn.Bucket("widgets") b := txn.Bucket("widgets")
i, err := b.NextSequence() i, err := b.NextSequence()
assert.Equal(t, i, 0) assert.Equal(t, i, 0)
@ -134,7 +134,7 @@ func TestBucketNextSequenceReadOnly(t *testing.T) {
func TestBucketNextSequenceOverflow(t *testing.T) { func TestBucketNextSequenceOverflow(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
db.CreateBucket("widgets") db.CreateBucket("widgets")
db.Do(func(txn *RWTransaction) error { db.Do(func(txn *RWTx) error {
b := txn.Bucket("widgets") b := txn.Bucket("widgets")
b.bucket.sequence = uint64(maxInt) b.bucket.sequence = uint64(maxInt)
seq, err := b.NextSequence() seq, err := b.NextSequence()
@ -218,7 +218,7 @@ func TestBucketPutKeyTooLarge(t *testing.T) {
// Ensure a bucket can calculate stats. // Ensure a bucket can calculate stats.
func TestBucketStat(t *testing.T) { func TestBucketStat(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
db.Do(func(txn *RWTransaction) error { db.Do(func(txn *RWTx) error {
// Add bucket with lots of keys. // Add bucket with lots of keys.
txn.CreateBucket("widgets") txn.CreateBucket("widgets")
b := txn.Bucket("widgets") b := txn.Bucket("widgets")
@ -241,7 +241,7 @@ func TestBucketStat(t *testing.T) {
return nil return nil
}) })
db.With(func(txn *Transaction) error { db.With(func(txn *Tx) error {
b := txn.Bucket("widgets") b := txn.Bucket("widgets")
stat := b.Stat() stat := b.Stat()
assert.Equal(t, stat.BranchPageCount, 15) assert.Equal(t, stat.BranchPageCount, 15)
@ -317,7 +317,7 @@ func TestBucketPutMultiple(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
// Bulk insert all values. // Bulk insert all values.
db.CreateBucket("widgets") db.CreateBucket("widgets")
rwtxn, _ := db.RWTransaction() rwtxn, _ := db.RWTx()
b := rwtxn.Bucket("widgets") b := rwtxn.Bucket("widgets")
for _, item := range items { for _, item := range items {
assert.NoError(t, b.Put(item.Key, item.Value)) assert.NoError(t, b.Put(item.Key, item.Value))
@ -325,7 +325,7 @@ func TestBucketPutMultiple(t *testing.T) {
assert.NoError(t, rwtxn.Commit()) assert.NoError(t, rwtxn.Commit())
// Verify all items exist. // Verify all items exist.
txn, _ := db.Transaction() txn, _ := db.Tx()
b = txn.Bucket("widgets") b = txn.Bucket("widgets")
for _, item := range items { for _, item := range items {
value := b.Get(item.Key) value := b.Get(item.Key)
@ -351,7 +351,7 @@ func TestBucketDeleteQuick(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
// Bulk insert all values. // Bulk insert all values.
db.CreateBucket("widgets") db.CreateBucket("widgets")
rwtxn, _ := db.RWTransaction() rwtxn, _ := db.RWTx()
b := rwtxn.Bucket("widgets") b := rwtxn.Bucket("widgets")
for _, item := range items { for _, item := range items {
assert.NoError(t, b.Put(item.Key, item.Value)) assert.NoError(t, b.Put(item.Key, item.Value))
@ -363,7 +363,7 @@ func TestBucketDeleteQuick(t *testing.T) {
assert.NoError(t, db.Delete("widgets", item.Key)) assert.NoError(t, db.Delete("widgets", item.Key))
// Anything before our deletion index should be nil. // Anything before our deletion index should be nil.
txn, _ := db.Transaction() txn, _ := db.Tx()
b := txn.Bucket("widgets") b := txn.Bucket("widgets")
for j, exp := range items { for j, exp := range items {
if j > i { if j > i {

View File

@ -6,9 +6,9 @@ import (
) )
// Cursor represents an iterator that can traverse over all key/value pairs in a bucket in sorted order. // Cursor represents an iterator that can traverse over all key/value pairs in a bucket in sorted order.
// Cursors can be obtained from a Transaction and are valid as long as the Transaction is open. // Cursors can be obtained from a transaction and are valid as long as the transaction is open.
type Cursor struct { type Cursor struct {
transaction *Transaction tx *Tx
root pgid root pgid
stack []elemRef stack []elemRef
} }
@ -17,7 +17,7 @@ type Cursor struct {
// If the bucket is empty then a nil key and value are returned. // If the bucket is empty then a nil key and value are returned.
func (c *Cursor) First() (key []byte, value []byte) { func (c *Cursor) First() (key []byte, value []byte) {
c.stack = c.stack[:0] c.stack = c.stack[:0]
p, n := c.transaction.pageNode(c.root) p, n := c.tx.pageNode(c.root)
c.stack = append(c.stack, elemRef{page: p, node: n, index: 0}) c.stack = append(c.stack, elemRef{page: p, node: n, index: 0})
c.first() c.first()
return c.keyValue() return c.keyValue()
@ -27,7 +27,7 @@ func (c *Cursor) First() (key []byte, value []byte) {
// If the bucket is empty then a nil key and value are returned. // If the bucket is empty then a nil key and value are returned.
func (c *Cursor) Last() (key []byte, value []byte) { func (c *Cursor) Last() (key []byte, value []byte) {
c.stack = c.stack[:0] c.stack = c.stack[:0]
p, n := c.transaction.pageNode(c.root) p, n := c.tx.pageNode(c.root)
ref := elemRef{page: p, node: n} ref := elemRef{page: p, node: n}
ref.index = ref.count() - 1 ref.index = ref.count() - 1
c.stack = append(c.stack, ref) c.stack = append(c.stack, ref)
@ -116,7 +116,7 @@ func (c *Cursor) first() {
} else { } else {
pgid = ref.page.branchPageElement(uint16(ref.index)).pgid pgid = ref.page.branchPageElement(uint16(ref.index)).pgid
} }
p, n := c.transaction.pageNode(pgid) p, n := c.tx.pageNode(pgid)
c.stack = append(c.stack, elemRef{page: p, node: n, index: 0}) c.stack = append(c.stack, elemRef{page: p, node: n, index: 0})
} }
} }
@ -137,7 +137,7 @@ func (c *Cursor) last() {
} else { } else {
pgid = ref.page.branchPageElement(uint16(ref.index)).pgid pgid = ref.page.branchPageElement(uint16(ref.index)).pgid
} }
p, n := c.transaction.pageNode(pgid) p, n := c.tx.pageNode(pgid)
var nextRef = elemRef{page: p, node: n} var nextRef = elemRef{page: p, node: n}
nextRef.index = nextRef.count() - 1 nextRef.index = nextRef.count() - 1
@ -147,7 +147,7 @@ func (c *Cursor) last() {
// search recursively performs a binary search against a given page/node until it finds a given key. // search recursively performs a binary search against a given page/node until it finds a given key.
func (c *Cursor) search(key []byte, pgid pgid) { func (c *Cursor) search(key []byte, pgid pgid) {
p, n := c.transaction.pageNode(pgid) p, n := c.tx.pageNode(pgid)
if p != nil { if p != nil {
_assert((p.flags&(branchPageFlag|leafPageFlag)) != 0, "invalid page type: "+p.typ()) _assert((p.flags&(branchPageFlag|leafPageFlag)) != 0, "invalid page type: "+p.typ())
} }
@ -251,7 +251,7 @@ func (c *Cursor) keyValue() ([]byte, []byte) {
} }
// node returns the node that the cursor is currently positioned on. // node returns the node that the cursor is currently positioned on.
func (c *Cursor) node(t *RWTransaction) *node { func (c *Cursor) node(t *RWTx) *node {
_assert(len(c.stack) > 0, "accessing a node with a zero-length cursor stack") _assert(len(c.stack) > 0, "accessing a node with a zero-length cursor stack")
// If the top of the stack is a leaf node then just return it. // If the top of the stack is a leaf node then just return it.

90
db.go
View File

@ -29,8 +29,8 @@ type DB struct {
meta1 *meta meta1 *meta
pageSize int pageSize int
opened bool opened bool
rwtransaction *RWTransaction rwtx *RWTx
transactions []*Transaction txs []*Tx
freelist *freelist freelist *freelist
rwlock sync.Mutex // Allows only one writer at a time. rwlock sync.Mutex // Allows only one writer at a time.
@ -112,7 +112,7 @@ func (db *DB) Open(path string, mode os.FileMode) error {
} }
// Read in the freelist. // Read in the freelist.
db.freelist = &freelist{pending: make(map[txnid][]pgid)} db.freelist = &freelist{pending: make(map[txid][]pgid)}
db.freelist.read(db.page(db.meta().freelist)) db.freelist.read(db.page(db.meta().freelist))
// Mark the database as opened and return. // Mark the database as opened and return.
@ -127,8 +127,8 @@ func (db *DB) mmap(minsz int) error {
defer db.mmaplock.Unlock() defer db.mmaplock.Unlock()
// Dereference all mmap references before unmapping. // Dereference all mmap references before unmapping.
if db.rwtransaction != nil { if db.rwtx != nil {
db.rwtransaction.dereference() db.rwtx.dereference()
} }
// Unmap existing data before continuing. // Unmap existing data before continuing.
@ -218,7 +218,7 @@ func (db *DB) init() error {
m.freelist = 2 m.freelist = 2
m.buckets = 3 m.buckets = 3
m.pgid = 4 m.pgid = 4
m.txnid = txnid(i) m.txid = txid(i)
} }
// Write an empty freelist at page 3. // Write an empty freelist at page 3.
@ -259,11 +259,11 @@ func (db *DB) close() {
db.munmap() db.munmap()
} }
// Transaction creates a read-only transaction. // Tx creates a read-only transaction.
// Multiple read-only transactions can be used concurrently. // Multiple read-only transactions can be used concurrently.
// //
// IMPORTANT: You must close the transaction after you are finished or else the database will not reclaim old pages. // IMPORTANT: You must close the transaction after you are finished or else the database will not reclaim old pages.
func (db *DB) Transaction() (*Transaction, error) { func (db *DB) Tx() (*Tx, error) {
db.metalock.Lock() db.metalock.Lock()
defer db.metalock.Unlock() defer db.metalock.Unlock()
@ -278,23 +278,23 @@ func (db *DB) Transaction() (*Transaction, error) {
} }
// Create a transaction associated with the database. // Create a transaction associated with the database.
t := &Transaction{} t := &Tx{}
t.init(db) t.init(db)
// Keep track of transaction until it closes. // Keep track of transaction until it closes.
db.transactions = append(db.transactions, t) db.txs = append(db.txs, t)
return t, nil return t, nil
} }
// RWTransaction creates a read/write transaction. // RWTx creates a read/write transaction.
// Only one read/write transaction is allowed at a time. // Only one read/write transaction is allowed at a time.
// You must call Commit() or Rollback() on the transaction to close it. // You must call Commit() or Rollback() on the transaction to close it.
func (db *DB) RWTransaction() (*RWTransaction, error) { func (db *DB) RWTx() (*RWTx, error) {
db.metalock.Lock() db.metalock.Lock()
defer db.metalock.Unlock() defer db.metalock.Unlock()
// Obtain writer lock. This is released by the RWTransaction when it closes. // Obtain writer lock. This is released by the RWTx when it closes.
db.rwlock.Lock() db.rwlock.Lock()
// Exit if the database is not open yet. // Exit if the database is not open yet.
@ -304,13 +304,13 @@ func (db *DB) RWTransaction() (*RWTransaction, error) {
} }
// Create a transaction associated with the database. // Create a transaction associated with the database.
t := &RWTransaction{} t := &RWTx{}
t.init(db) t.init(db)
db.rwtransaction = t db.rwtx = t
// Free any pages associated with closed read-only transactions. // Free any pages associated with closed read-only transactions.
var minid txnid = 0xFFFFFFFFFFFFFFFF var minid txid = 0xFFFFFFFFFFFFFFFF
for _, t := range db.transactions { for _, t := range db.txs {
if t.id() < minid { if t.id() < minid {
minid = t.id() minid = t.id()
} }
@ -322,8 +322,8 @@ func (db *DB) RWTransaction() (*RWTransaction, error) {
return t, nil return t, nil
} }
// removeTransaction removes a transaction from the database. // removeTx removes a transaction from the database.
func (db *DB) removeTransaction(t *Transaction) { func (db *DB) removeTx(t *Tx) {
db.metalock.Lock() db.metalock.Lock()
defer db.metalock.Unlock() defer db.metalock.Unlock()
@ -331,21 +331,21 @@ func (db *DB) removeTransaction(t *Transaction) {
db.mmaplock.RUnlock() db.mmaplock.RUnlock()
// Remove the transaction. // Remove the transaction.
for i, txn := range db.transactions { for i, txn := range db.txs {
if txn == t { if txn == t {
db.transactions = append(db.transactions[:i], db.transactions[i+1:]...) db.txs = append(db.txs[:i], db.txs[i+1:]...)
break break
} }
} }
} }
// Do executes a function within the context of a RWTransaction. // Do executes a function within the context of a RWTx.
// If no error is returned from the function then the transaction is committed. // If no error is returned from the function then the transaction is committed.
// If an error is returned then the entire transaction is rolled back. // If an error is returned then the entire transaction is rolled back.
// Any error that is returned from the function or returned from the commit is // Any error that is returned from the function or returned from the commit is
// returned from the Do() method. // returned from the Do() method.
func (db *DB) Do(fn func(*RWTransaction) error) error { func (db *DB) Do(fn func(*RWTx) error) error {
t, err := db.RWTransaction() t, err := db.RWTx()
if err != nil { if err != nil {
return err return err
} }
@ -359,10 +359,10 @@ func (db *DB) Do(fn func(*RWTransaction) error) error {
return t.Commit() return t.Commit()
} }
// With executes a function within the context of a Transaction. // With executes a function within the context of a transaction.
// Any error that is returned from the function is returned from the With() method. // Any error that is returned from the function is returned from the With() method.
func (db *DB) With(fn func(*Transaction) error) error { func (db *DB) With(fn func(*Tx) error) error {
t, err := db.Transaction() t, err := db.Tx()
if err != nil { if err != nil {
return err return err
} }
@ -375,7 +375,7 @@ func (db *DB) With(fn func(*Transaction) error) error {
// ForEach executes a function for each key/value pair in a bucket. // ForEach executes a function for each key/value pair in a bucket.
// An error is returned if the bucket cannot be found. // An error is returned if the bucket cannot be found.
func (db *DB) ForEach(name string, fn func(k, v []byte) error) error { func (db *DB) ForEach(name string, fn func(k, v []byte) error) error {
return db.With(func(t *Transaction) error { return db.With(func(t *Tx) error {
b := t.Bucket(name) b := t.Bucket(name)
if b == nil { if b == nil {
return ErrBucketNotFound return ErrBucketNotFound
@ -387,7 +387,7 @@ func (db *DB) ForEach(name string, fn func(k, v []byte) error) error {
// Bucket retrieves a reference to a bucket. // Bucket retrieves a reference to a bucket.
// This is typically useful for checking the existence of a bucket. // This is typically useful for checking the existence of a bucket.
func (db *DB) Bucket(name string) (*Bucket, error) { func (db *DB) Bucket(name string) (*Bucket, error) {
t, err := db.Transaction() t, err := db.Tx()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -397,7 +397,7 @@ func (db *DB) Bucket(name string) (*Bucket, error) {
// Buckets retrieves a list of all buckets in the database. // Buckets retrieves a list of all buckets in the database.
func (db *DB) Buckets() ([]*Bucket, error) { func (db *DB) Buckets() ([]*Bucket, error) {
t, err := db.Transaction() t, err := db.Tx()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -409,7 +409,7 @@ func (db *DB) Buckets() ([]*Bucket, error) {
// This function can return an error if the bucket already exists, if the name // This function can return an error if the bucket already exists, if the name
// is blank, or the bucket name is too long. // is blank, or the bucket name is too long.
func (db *DB) CreateBucket(name string) error { func (db *DB) CreateBucket(name string) error {
return db.Do(func(t *RWTransaction) error { return db.Do(func(t *RWTx) error {
return t.CreateBucket(name) return t.CreateBucket(name)
}) })
} }
@ -417,7 +417,7 @@ func (db *DB) CreateBucket(name string) error {
// CreateBucketIfNotExists creates a new bucket with the given name if it doesn't already exist. // CreateBucketIfNotExists creates a new bucket with the given name if it doesn't already exist.
// This function can return an error if the name is blank, or the bucket name is too long. // This function can return an error if the name is blank, or the bucket name is too long.
func (db *DB) CreateBucketIfNotExists(name string) error { func (db *DB) CreateBucketIfNotExists(name string) error {
return db.Do(func(t *RWTransaction) error { return db.Do(func(t *RWTx) error {
return t.CreateBucketIfNotExists(name) return t.CreateBucketIfNotExists(name)
}) })
} }
@ -425,7 +425,7 @@ func (db *DB) CreateBucketIfNotExists(name string) error {
// DeleteBucket removes a bucket from the database. // DeleteBucket removes a bucket from the database.
// Returns an error if the bucket does not exist. // Returns an error if the bucket does not exist.
func (db *DB) DeleteBucket(name string) error { func (db *DB) DeleteBucket(name string) error {
return db.Do(func(t *RWTransaction) error { return db.Do(func(t *RWTx) error {
return t.DeleteBucket(name) return t.DeleteBucket(name)
}) })
} }
@ -434,7 +434,7 @@ func (db *DB) DeleteBucket(name string) error {
// This function can return an error if the bucket does not exist. // This function can return an error if the bucket does not exist.
func (db *DB) NextSequence(name string) (int, error) { func (db *DB) NextSequence(name string) (int, error) {
var seq int var seq int
err := db.Do(func(t *RWTransaction) error { err := db.Do(func(t *RWTx) error {
b := t.Bucket(name) b := t.Bucket(name)
if b == nil { if b == nil {
return ErrBucketNotFound return ErrBucketNotFound
@ -453,7 +453,7 @@ func (db *DB) NextSequence(name string) (int, error) {
// Get retrieves the value for a key in a bucket. // Get retrieves the value for a key in a bucket.
// Returns an error if the key does not exist. // Returns an error if the key does not exist.
func (db *DB) Get(name string, key []byte) ([]byte, error) { func (db *DB) Get(name string, key []byte) ([]byte, error) {
t, err := db.Transaction() t, err := db.Tx()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -482,7 +482,7 @@ func (db *DB) Get(name string, key []byte) ([]byte, error) {
// Put sets the value for a key in a bucket. // Put sets the value for a key in a bucket.
// Returns an error if the bucket is not found, if key is blank, if the key is too large, or if the value is too large. // Returns an error if the bucket is not found, if key is blank, if the key is too large, or if the value is too large.
func (db *DB) Put(name string, key []byte, value []byte) error { func (db *DB) Put(name string, key []byte, value []byte) error {
return db.Do(func(t *RWTransaction) error { return db.Do(func(t *RWTx) error {
b := t.Bucket(name) b := t.Bucket(name)
if b == nil { if b == nil {
return ErrBucketNotFound return ErrBucketNotFound
@ -494,7 +494,7 @@ func (db *DB) Put(name string, key []byte, value []byte) error {
// Delete removes a key from a bucket. // Delete removes a key from a bucket.
// Returns an error if the bucket cannot be found. // Returns an error if the bucket cannot be found.
func (db *DB) Delete(name string, key []byte) error { func (db *DB) Delete(name string, key []byte) error {
return db.Do(func(t *RWTransaction) error { return db.Do(func(t *RWTx) error {
b := t.Bucket(name) b := t.Bucket(name)
if b == nil { if b == nil {
return ErrBucketNotFound return ErrBucketNotFound
@ -508,7 +508,7 @@ func (db *DB) Delete(name string, key []byte) error {
// using the database while a copy is in progress. // using the database while a copy is in progress.
func (db *DB) Copy(w io.Writer) error { func (db *DB) Copy(w io.Writer) error {
// Maintain a reader transaction so pages don't get reclaimed. // Maintain a reader transaction so pages don't get reclaimed.
t, err := db.Transaction() t, err := db.Tx()
if err != nil { if err != nil {
return err return err
} }
@ -550,14 +550,14 @@ func (db *DB) Stat() (*Stat, error) {
var s = &Stat{ var s = &Stat{
MmapSize: len(db.data), MmapSize: len(db.data),
TransactionCount: len(db.transactions), TxCount: len(db.txs),
} }
// Release locks. // Release locks.
db.mmaplock.RUnlock() db.mmaplock.RUnlock()
db.metalock.Unlock() db.metalock.Unlock()
err := db.Do(func(t *RWTransaction) error { err := db.Do(func(t *RWTx) error {
s.PageCount = int(t.meta.pgid) s.PageCount = int(t.meta.pgid)
s.FreePageCount = len(db.freelist.all()) s.FreePageCount = len(db.freelist.all())
s.PageSize = db.pageSize s.PageSize = db.pageSize
@ -582,7 +582,7 @@ func (db *DB) pageInBuffer(b []byte, id pgid) *page {
// meta retrieves the current meta page reference. // meta retrieves the current meta page reference.
func (db *DB) meta() *meta { func (db *DB) meta() *meta {
if db.meta0.txnid > db.meta1.txnid { if db.meta0.txid > db.meta1.txid {
return db.meta0 return db.meta0
} }
return db.meta1 return db.meta1
@ -601,7 +601,7 @@ func (db *DB) allocate(count int) (*page, error) {
} }
// Resize mmap() if we're at the end. // Resize mmap() if we're at the end.
p.id = db.rwtransaction.meta.pgid p.id = db.rwtx.meta.pgid
var minsz = int((p.id+pgid(count))+1) * db.pageSize var minsz = int((p.id+pgid(count))+1) * db.pageSize
if minsz >= len(db.data) { if minsz >= len(db.data) {
if err := db.mmap(minsz); err != nil { if err := db.mmap(minsz); err != nil {
@ -610,7 +610,7 @@ func (db *DB) allocate(count int) (*page, error) {
} }
// Move the page id high water mark. // Move the page id high water mark.
db.rwtransaction.meta.pgid += pgid(count) db.rwtx.meta.pgid += pgid(count)
return p, nil return p, nil
} }
@ -634,6 +634,6 @@ type Stat struct {
// resize it. // resize it.
MmapSize int MmapSize int
// TransactionCount is the total number of reader transactions. // TxCount is the total number of reader transactions.
TransactionCount int TxCount int
} }

View File

@ -156,9 +156,9 @@ func TestDBCorruptMeta0(t *testing.T) {
} }
// Ensure that a database cannot open a transaction when it's not open. // Ensure that a database cannot open a transaction when it's not open.
func TestDBTransactionErrDatabaseNotOpen(t *testing.T) { func TestDBTxErrDatabaseNotOpen(t *testing.T) {
withDB(func(db *DB, path string) { withDB(func(db *DB, path string) {
txn, err := db.Transaction() txn, err := db.Tx()
assert.Nil(t, txn) assert.Nil(t, txn)
assert.Equal(t, err, ErrDatabaseNotOpen) assert.Equal(t, err, ErrDatabaseNotOpen)
}) })
@ -173,9 +173,9 @@ func TestDBDeleteFromMissingBucket(t *testing.T) {
} }
// Ensure a database can provide a transactional block. // Ensure a database can provide a transactional block.
func TestDBTransactionBlock(t *testing.T) { func TestDBTxBlock(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
err := db.Do(func(txn *RWTransaction) error { err := db.Do(func(txn *RWTx) error {
txn.CreateBucket("widgets") txn.CreateBucket("widgets")
b := txn.Bucket("widgets") b := txn.Bucket("widgets")
b.Put([]byte("foo"), []byte("bar")) b.Put([]byte("foo"), []byte("bar"))
@ -192,9 +192,9 @@ func TestDBTransactionBlock(t *testing.T) {
} }
// Ensure a closed database returns an error while running a transaction block // Ensure a closed database returns an error while running a transaction block
func TestDBTransactionBlockWhileClosed(t *testing.T) { func TestDBTxBlockWhileClosed(t *testing.T) {
withDB(func(db *DB, path string) { withDB(func(db *DB, path string) {
err := db.Do(func(txn *RWTransaction) error { err := db.Do(func(txn *RWTx) error {
txn.CreateBucket("widgets") txn.CreateBucket("widgets")
return nil return nil
}) })
@ -276,7 +276,7 @@ func TestDBCopyFile(t *testing.T) {
// Ensure the database can return stats about itself. // Ensure the database can return stats about itself.
func TestDBStat(t *testing.T) { func TestDBStat(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
db.Do(func(txn *RWTransaction) error { db.Do(func(txn *RWTx) error {
txn.CreateBucket("widgets") txn.CreateBucket("widgets")
b := txn.Bucket("widgets") b := txn.Bucket("widgets")
for i := 0; i < 10000; i++ { for i := 0; i < 10000; i++ {
@ -290,9 +290,9 @@ func TestDBStat(t *testing.T) {
db.Delete("widgets", []byte("1000")) db.Delete("widgets", []byte("1000"))
// Open some readers. // Open some readers.
t0, _ := db.Transaction() t0, _ := db.Tx()
t1, _ := db.Transaction() t1, _ := db.Tx()
t2, _ := db.Transaction() t2, _ := db.Tx()
t2.Close() t2.Close()
// Obtain stats. // Obtain stats.
@ -302,7 +302,7 @@ func TestDBStat(t *testing.T) {
assert.Equal(t, stat.FreePageCount, 2) assert.Equal(t, stat.FreePageCount, 2)
assert.Equal(t, stat.PageSize, 4096) assert.Equal(t, stat.PageSize, 4096)
assert.Equal(t, stat.MmapSize, 4194304) assert.Equal(t, stat.MmapSize, 4194304)
assert.Equal(t, stat.TransactionCount, 2) assert.Equal(t, stat.TxCount, 2)
// Close readers. // Close readers.
t0.Close() t0.Close()

15
doc.go
View File

@ -14,15 +14,14 @@ The design of Bolt is based on Howard Chu's LMDB database project.
Basics Basics
There are only a few types in Bolt: DB, Bucket, Transaction, RWTransaction, and There are only a few types in Bolt: DB, Bucket, Tx, RWTx, and Cursor. The DB is
Cursor. The DB is a collection of buckets and is represented by a single file a collection of buckets and is represented by a single file on disk. A bucket is
on disk. A bucket is a collection of unique keys that are associated with values. a collection of unique keys that are associated with values.
Transactions provide read-only access to data inside the database. They can Txs provide read-only access to data inside the database. They can retrieve
retrieve key/value pairs and can use Cursors to iterate over the entire dataset. key/value pairs and can use Cursors to iterate over the entire dataset. RWTxs
RWTransactions provide read-write access to the database. They can create and provide read-write access to the database. They can create and delete buckets
delete buckets and they can insert and remove keys. Only one RWTransaction is and they can insert and remove keys. Only one RWTx is allowed at a time.
allowed at a time.
Caveats Caveats

View File

@ -67,7 +67,7 @@ func ExampleDB_Do() {
defer db.Close() defer db.Close()
// Execute several commands within a write transaction. // Execute several commands within a write transaction.
err := db.Do(func(t *RWTransaction) error { err := db.Do(func(t *RWTx) error {
if err := t.CreateBucket("widgets"); err != nil { if err := t.CreateBucket("widgets"); err != nil {
return err return err
} }
@ -100,7 +100,7 @@ func ExampleDB_With() {
db.Put("people", []byte("susy"), []byte("que")) db.Put("people", []byte("susy"), []byte("que"))
// Access data from within a read-only transactional block. // Access data from within a read-only transactional block.
db.With(func(t *Transaction) error { db.With(func(t *Tx) error {
v := t.Bucket("people").Get([]byte("john")) v := t.Bucket("people").Get([]byte("john"))
fmt.Printf("John's last name is %s.\n", string(v)) fmt.Printf("John's last name is %s.\n", string(v))
return nil return nil
@ -134,17 +134,17 @@ func ExampleDB_ForEach() {
// A liger is awesome. // A liger is awesome.
} }
func ExampleRWTransaction() { func ExampleRWTx() {
// Open the database. // Open the database.
var db DB var db DB
db.Open("/tmp/bolt/rwtransaction.db", 0666) db.Open("/tmp/bolt/rwtx.db", 0666)
defer db.Close() defer db.Close()
// Create a bucket. // Create a bucket.
db.CreateBucket("widgets") db.CreateBucket("widgets")
// Create several keys in a transaction. // Create several keys in a transaction.
rwtxn, _ := db.RWTransaction() rwtxn, _ := db.RWTx()
b := rwtxn.Bucket("widgets") b := rwtxn.Bucket("widgets")
b.Put([]byte("john"), []byte("blue")) b.Put([]byte("john"), []byte("blue"))
b.Put([]byte("abby"), []byte("red")) b.Put([]byte("abby"), []byte("red"))
@ -152,7 +152,7 @@ func ExampleRWTransaction() {
rwtxn.Commit() rwtxn.Commit()
// Iterate over the values in sorted key order. // Iterate over the values in sorted key order.
txn, _ := db.Transaction() txn, _ := db.Tx()
c := txn.Bucket("widgets").Cursor() c := txn.Bucket("widgets").Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() { for k, v := c.First(); k != nil; k, v = c.Next() {
fmt.Printf("%s likes %s\n", string(k), string(v)) fmt.Printf("%s likes %s\n", string(k), string(v))
@ -165,10 +165,10 @@ func ExampleRWTransaction() {
// zephyr likes purple // zephyr likes purple
} }
func ExampleRWTransaction_rollback() { func ExampleRWTx_rollback() {
// Open the database. // Open the database.
var db DB var db DB
db.Open("/tmp/bolt/rwtransaction_rollback.db", 0666) db.Open("/tmp/bolt/rwtx_rollback.db", 0666)
defer db.Close() defer db.Close()
// Create a bucket. // Create a bucket.
@ -178,7 +178,7 @@ func ExampleRWTransaction_rollback() {
db.Put("widgets", []byte("foo"), []byte("bar")) db.Put("widgets", []byte("foo"), []byte("bar"))
// Update the key but rollback the transaction so it never saves. // Update the key but rollback the transaction so it never saves.
rwtxn, _ := db.RWTransaction() rwtxn, _ := db.RWTx()
b := rwtxn.Bucket("widgets") b := rwtxn.Bucket("widgets")
b.Put([]byte("foo"), []byte("baz")) b.Put([]byte("foo"), []byte("baz"))
rwtxn.Rollback() rwtxn.Rollback()

View File

@ -9,7 +9,7 @@ import (
// It also tracks pages that have been freed but are still in use by open transactions. // It also tracks pages that have been freed but are still in use by open transactions.
type freelist struct { type freelist struct {
ids []pgid ids []pgid
pending map[txnid][]pgid pending map[txid][]pgid
} }
// all returns a list of all free ids and all pending ids in one sorted list. // all returns a list of all free ids and all pending ids in one sorted list.
@ -50,19 +50,19 @@ func (f *freelist) allocate(n int) pgid {
} }
// free releases a page and its overflow for a given transaction id. // free releases a page and its overflow for a given transaction id.
func (f *freelist) free(txnid txnid, p *page) { func (f *freelist) free(txid txid, p *page) {
var ids = f.pending[txnid] var ids = f.pending[txid]
_assert(p.id > 1, "cannot free page 0 or 1: %d", p.id) _assert(p.id > 1, "cannot free page 0 or 1: %d", p.id)
for i := 0; i < int(p.overflow+1); i++ { for i := 0; i < int(p.overflow+1); i++ {
ids = append(ids, p.id+pgid(i)) ids = append(ids, p.id+pgid(i))
} }
f.pending[txnid] = ids f.pending[txid] = ids
} }
// release moves all page ids for a transaction id (or older) to the freelist. // release moves all page ids for a transaction id (or older) to the freelist.
func (f *freelist) release(txnid txnid) { func (f *freelist) release(txid txid) {
for tid, ids := range f.pending { for tid, ids := range f.pending {
if tid <= txnid { if tid <= txid {
f.ids = append(f.ids, ids...) f.ids = append(f.ids, ids...)
delete(f.pending, tid) delete(f.pending, tid)
} }

View File

@ -9,21 +9,21 @@ import (
// Ensure that a page is added to a transaction's freelist. // Ensure that a page is added to a transaction's freelist.
func TestFreelistFree(t *testing.T) { func TestFreelistFree(t *testing.T) {
f := &freelist{pending: make(map[txnid][]pgid)} f := &freelist{pending: make(map[txid][]pgid)}
f.free(100, &page{id: 12}) f.free(100, &page{id: 12})
assert.Equal(t, f.pending[100], []pgid{12}) assert.Equal(t, f.pending[100], []pgid{12})
} }
// Ensure that a page and its overflow is added to a transaction's freelist. // Ensure that a page and its overflow is added to a transaction's freelist.
func TestFreelistFreeOverflow(t *testing.T) { func TestFreelistFreeOverflow(t *testing.T) {
f := &freelist{pending: make(map[txnid][]pgid)} f := &freelist{pending: make(map[txid][]pgid)}
f.free(100, &page{id: 12, overflow: 3}) f.free(100, &page{id: 12, overflow: 3})
assert.Equal(t, f.pending[100], []pgid{12, 13, 14, 15}) assert.Equal(t, f.pending[100], []pgid{12, 13, 14, 15})
} }
// Ensure that a transaction's free pages can be released. // Ensure that a transaction's free pages can be released.
func TestFreelistRelease(t *testing.T) { func TestFreelistRelease(t *testing.T) {
f := &freelist{pending: make(map[txnid][]pgid)} f := &freelist{pending: make(map[txid][]pgid)}
f.free(100, &page{id: 12, overflow: 1}) f.free(100, &page{id: 12, overflow: 1})
f.free(100, &page{id: 9}) f.free(100, &page{id: 9})
f.free(102, &page{id: 39}) f.free(102, &page{id: 39})
@ -61,7 +61,7 @@ func TestFreelistRead(t *testing.T) {
ids[1] = 50 ids[1] = 50
// Deserialize page into a freelist. // Deserialize page into a freelist.
f := &freelist{pending: make(map[txnid][]pgid)} f := &freelist{pending: make(map[txid][]pgid)}
f.read(page) f.read(page)
// Ensure that there are two page ids in the freelist. // Ensure that there are two page ids in the freelist.
@ -74,14 +74,14 @@ func TestFreelistRead(t *testing.T) {
func TestFreelistWrite(t *testing.T) { func TestFreelistWrite(t *testing.T) {
// Create a freelist and write it to a page. // Create a freelist and write it to a page.
var buf [4096]byte var buf [4096]byte
f := &freelist{ids: []pgid{12, 39}, pending: make(map[txnid][]pgid)} f := &freelist{ids: []pgid{12, 39}, pending: make(map[txid][]pgid)}
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) f.write(p)
// Read the page back out. // Read the page back out.
f2 := &freelist{pending: make(map[txnid][]pgid)} f2 := &freelist{pending: make(map[txid][]pgid)}
f2.read(p) f2.read(p)
// Ensure that the freelist is correct. // Ensure that the freelist is correct.

View File

@ -12,7 +12,7 @@ import (
) )
// Ensure that multiple threads can use the DB without race detector errors. // Ensure that multiple threads can use the DB without race detector errors.
func TestParallelTransactions(t *testing.T) { func TestParallelTxs(t *testing.T) {
var mutex sync.RWMutex var mutex sync.RWMutex
err := quick.Check(func(numReaders, batchSize uint, items testdata) bool { err := quick.Check(func(numReaders, batchSize uint, items testdata) bool {
@ -45,7 +45,7 @@ func TestParallelTransactions(t *testing.T) {
go func() { go func() {
mutex.RLock() mutex.RLock()
local := current local := current
txn, err := db.Transaction() txn, err := db.Tx()
mutex.RUnlock() mutex.RUnlock()
if err == ErrDatabaseNotOpen { if err == ErrDatabaseNotOpen {
wg.Done() wg.Done()
@ -83,7 +83,7 @@ func TestParallelTransactions(t *testing.T) {
pending = pending[currentBatchSize:] pending = pending[currentBatchSize:]
// Start write transaction. // Start write transaction.
txn, err := db.RWTransaction() txn, err := db.RWTx()
if !assert.NoError(t, err) { if !assert.NoError(t, err) {
t.FailNow() t.FailNow()
} }

View File

@ -10,7 +10,7 @@ type meta struct {
buckets pgid buckets pgid
freelist pgid freelist pgid
pgid pgid pgid pgid
txnid txnid txid txid
} }
// validate checks the marker bytes and version of the meta page to ensure it matches this binary. // validate checks the marker bytes and version of the meta page to ensure it matches this binary.
@ -31,13 +31,13 @@ func (m *meta) copy(dest *meta) {
dest.buckets = m.buckets dest.buckets = m.buckets
dest.freelist = m.freelist dest.freelist = m.freelist
dest.pgid = m.pgid dest.pgid = m.pgid
dest.txnid = m.txnid dest.txid = m.txid
} }
// write writes the meta onto a page. // write writes the meta onto a page.
func (m *meta) write(p *page) { func (m *meta) write(p *page) {
// Page id is either going to be 0 or 1 which we can determine by the Txn ID. // Page id is either going to be 0 or 1 which we can determine by the Txn ID.
p.id = pgid(m.txnid % 2) p.id = pgid(m.txid % 2)
p.flags |= metaPageFlag p.flags |= metaPageFlag
m.copy(p.meta()) m.copy(p.meta())

26
node.go
View File

@ -8,7 +8,7 @@ import (
// node represents an in-memory, deserialized page. // node represents an in-memory, deserialized page.
type node struct { type node struct {
transaction *RWTransaction tx *RWTx
isLeaf bool isLeaf bool
unbalanced bool unbalanced bool
key []byte key []byte
@ -56,7 +56,7 @@ func (n *node) root() *node {
// childAt returns the child node at a given index. // childAt returns the child node at a given index.
func (n *node) childAt(index int) *node { func (n *node) childAt(index int) *node {
_assert(!n.isLeaf, "invalid childAt(%d) on a leaf node", index) _assert(!n.isLeaf, "invalid childAt(%d) on a leaf node", index)
return n.transaction.node(n.inodes[index].pgid, n) return n.tx.node(n.inodes[index].pgid, n)
} }
// childIndex returns the index of a given child node. // childIndex returns the index of a given child node.
@ -214,7 +214,7 @@ func (n *node) split(pageSize int) []*node {
if len(current.inodes) >= minKeysPerPage && i < len(inodes)-minKeysPerPage && size+elemSize > threshold { if len(current.inodes) >= minKeysPerPage && i < len(inodes)-minKeysPerPage && size+elemSize > threshold {
size = pageHeaderSize size = pageHeaderSize
nodes = append(nodes, current) nodes = append(nodes, current)
current = &node{transaction: n.transaction, isLeaf: n.isLeaf} current = &node{tx: n.tx, isLeaf: n.isLeaf}
} }
size += elemSize size += elemSize
@ -234,7 +234,7 @@ func (n *node) rebalance() {
n.unbalanced = false n.unbalanced = false
// Ignore if node is above threshold (25%) and has enough keys. // Ignore if node is above threshold (25%) and has enough keys.
var threshold = n.transaction.db.pageSize / 4 var threshold = n.tx.db.pageSize / 4
if n.size() > threshold && len(n.inodes) > n.minKeys() { if n.size() > threshold && len(n.inodes) > n.minKeys() {
return return
} }
@ -244,20 +244,20 @@ func (n *node) rebalance() {
// If root node is a branch and only has one node then collapse it. // If root node is a branch and only has one node then collapse it.
if !n.isLeaf && len(n.inodes) == 1 { if !n.isLeaf && len(n.inodes) == 1 {
// Move child's children up. // Move child's children up.
child := n.transaction.nodes[n.inodes[0].pgid] child := n.tx.nodes[n.inodes[0].pgid]
n.isLeaf = child.isLeaf n.isLeaf = child.isLeaf
n.inodes = child.inodes[:] n.inodes = child.inodes[:]
// Reparent all child nodes being moved. // Reparent all child nodes being moved.
for _, inode := range n.inodes { for _, inode := range n.inodes {
if child, ok := n.transaction.nodes[inode.pgid]; ok { if child, ok := n.tx.nodes[inode.pgid]; ok {
child.parent = n child.parent = n
} }
} }
// Remove old child. // Remove old child.
child.parent = nil child.parent = nil
delete(n.transaction.nodes, child.pgid) delete(n.tx.nodes, child.pgid)
} }
return return
@ -278,7 +278,7 @@ func (n *node) rebalance() {
if target.numChildren() > target.minKeys() { if target.numChildren() > target.minKeys() {
if useNextSibling { if useNextSibling {
// Reparent and move node. // Reparent and move node.
if child, ok := n.transaction.nodes[target.inodes[0].pgid]; ok { if child, ok := n.tx.nodes[target.inodes[0].pgid]; ok {
child.parent = n child.parent = n
} }
n.inodes = append(n.inodes, target.inodes[0]) n.inodes = append(n.inodes, target.inodes[0])
@ -289,7 +289,7 @@ func (n *node) rebalance() {
target.key = target.inodes[0].key target.key = target.inodes[0].key
} else { } else {
// Reparent and move node. // Reparent and move node.
if child, ok := n.transaction.nodes[target.inodes[len(target.inodes)-1].pgid]; ok { if child, ok := n.tx.nodes[target.inodes[len(target.inodes)-1].pgid]; ok {
child.parent = n child.parent = n
} }
n.inodes = append(n.inodes, inode{}) n.inodes = append(n.inodes, inode{})
@ -309,7 +309,7 @@ func (n *node) rebalance() {
if useNextSibling { if useNextSibling {
// Reparent all child nodes being moved. // Reparent all child nodes being moved.
for _, inode := range target.inodes { for _, inode := range target.inodes {
if child, ok := n.transaction.nodes[inode.pgid]; ok { if child, ok := n.tx.nodes[inode.pgid]; ok {
child.parent = n child.parent = n
} }
} }
@ -317,11 +317,11 @@ func (n *node) rebalance() {
// Copy over inodes from target and remove target. // Copy over inodes from target and remove target.
n.inodes = append(n.inodes, target.inodes...) n.inodes = append(n.inodes, target.inodes...)
n.parent.del(target.key) n.parent.del(target.key)
delete(n.transaction.nodes, target.pgid) delete(n.tx.nodes, target.pgid)
} else { } else {
// Reparent all child nodes being moved. // Reparent all child nodes being moved.
for _, inode := range n.inodes { for _, inode := range n.inodes {
if child, ok := n.transaction.nodes[inode.pgid]; ok { if child, ok := n.tx.nodes[inode.pgid]; ok {
child.parent = target child.parent = target
} }
} }
@ -330,7 +330,7 @@ func (n *node) rebalance() {
target.inodes = append(target.inodes, n.inodes...) target.inodes = append(target.inodes, n.inodes...)
n.parent.del(n.key) n.parent.del(n.key)
n.parent.put(target.key, target.inodes[0].key, nil, target.pgid) n.parent.put(target.key, target.inodes[0].key, nil, target.pgid)
delete(n.transaction.nodes, n.pgid) delete(n.tx.nodes, n.pgid)
} }
// Either this node or the target node was deleted from the parent so rebalance it. // Either this node or the target node was deleted from the parent so rebalance it.

View File

@ -5,29 +5,29 @@ import (
"unsafe" "unsafe"
) )
// RWTransaction represents a transaction that can read and write data. // RWTx represents a transaction that can read and write data.
// Only one read/write transaction can be active for a database at a time. // Only one read/write transaction can be active for a database at a time.
// RWTransaction is composed of a read-only Transaction so it can also use // RWTx is composed of a read-only transaction so it can also use
// functions provided by Transaction. // functions provided by Tx.
type RWTransaction struct { type RWTx struct {
Transaction Tx
pending []*node pending []*node
} }
// init initializes the transaction. // init initializes the transaction.
func (t *RWTransaction) init(db *DB) { func (t *RWTx) init(db *DB) {
t.Transaction.init(db) t.Tx.init(db)
t.Transaction.rwtransaction = t t.Tx.rwtx = t
t.pages = make(map[pgid]*page) t.pages = make(map[pgid]*page)
t.nodes = make(map[pgid]*node) t.nodes = make(map[pgid]*node)
// Increment the transaction id. // Increment the transaction id.
t.meta.txnid += txnid(1) t.meta.txid += txid(1)
} }
// CreateBucket creates a new bucket. // CreateBucket creates a new bucket.
// Returns an error if the bucket already exists, if the bucket name is blank, or if the bucket name is too long. // Returns an error if the bucket already exists, if the bucket name is blank, or if the bucket name is too long.
func (t *RWTransaction) CreateBucket(name string) error { func (t *RWTx) CreateBucket(name string) error {
// Check if bucket already exists. // Check if bucket already exists.
if b := t.Bucket(name); b != nil { if b := t.Bucket(name); b != nil {
return ErrBucketExists return ErrBucketExists
@ -52,7 +52,7 @@ func (t *RWTransaction) CreateBucket(name string) error {
// CreateBucketIfNotExists creates a new bucket if it doesn't already exist. // CreateBucketIfNotExists creates a new bucket if it doesn't already exist.
// Returns an error if the bucket name is blank, or if the bucket name is too long. // Returns an error if the bucket name is blank, or if the bucket name is too long.
func (t *RWTransaction) CreateBucketIfNotExists(name string) error { func (t *RWTx) CreateBucketIfNotExists(name string) error {
err := t.CreateBucket(name) err := t.CreateBucket(name)
if err != nil && err != ErrBucketExists { if err != nil && err != ErrBucketExists {
return err return err
@ -62,7 +62,7 @@ func (t *RWTransaction) CreateBucketIfNotExists(name string) error {
// DeleteBucket deletes a bucket. // DeleteBucket deletes a bucket.
// Returns an error if the bucket cannot be found. // Returns an error if the bucket cannot be found.
func (t *RWTransaction) DeleteBucket(name string) error { func (t *RWTx) DeleteBucket(name string) error {
b := t.Bucket(name) b := t.Bucket(name)
if b == nil { if b == nil {
return ErrBucketNotFound return ErrBucketNotFound
@ -81,7 +81,7 @@ func (t *RWTransaction) DeleteBucket(name string) error {
// Commit writes all changes to disk and updates the meta page. // Commit writes all changes to disk and updates the meta page.
// Returns an error if a disk write error occurs. // Returns an error if a disk write error occurs.
func (t *RWTransaction) Commit() error { func (t *RWTx) Commit() error {
if t.db == nil { if t.db == nil {
return nil return nil
} }
@ -118,11 +118,11 @@ func (t *RWTransaction) Commit() error {
} }
// Rollback closes the transaction and ignores all previous updates. // Rollback closes the transaction and ignores all previous updates.
func (t *RWTransaction) Rollback() { func (t *RWTx) Rollback() {
t.close() t.close()
} }
func (t *RWTransaction) close() { func (t *RWTx) close() {
if t.db != nil { if t.db != nil {
t.db.rwlock.Unlock() t.db.rwlock.Unlock()
t.db = nil t.db = nil
@ -130,7 +130,7 @@ func (t *RWTransaction) close() {
} }
// allocate returns a contiguous block of memory starting at a given page. // allocate returns a contiguous block of memory starting at a given page.
func (t *RWTransaction) allocate(count int) (*page, error) { func (t *RWTx) allocate(count int) (*page, error) {
p, err := t.db.allocate(count) p, err := t.db.allocate(count)
if err != nil { if err != nil {
return nil, err return nil, err
@ -143,14 +143,14 @@ func (t *RWTransaction) allocate(count int) (*page, error) {
} }
// rebalance attempts to balance all nodes. // rebalance attempts to balance all nodes.
func (t *RWTransaction) rebalance() { func (t *RWTx) rebalance() {
for _, n := range t.nodes { for _, n := range t.nodes {
n.rebalance() n.rebalance()
} }
} }
// spill writes all the nodes to dirty pages. // spill writes all the nodes to dirty pages.
func (t *RWTransaction) spill() error { func (t *RWTx) spill() error {
// Keep track of the current root nodes. // Keep track of the current root nodes.
// We will update this at the end once all nodes are created. // We will update this at the end once all nodes are created.
type root struct { type root struct {
@ -182,7 +182,7 @@ func (t *RWTransaction) spill() error {
// If this is a root node that split then create a parent node. // If this is a root node that split then create a parent node.
if n.parent == nil && len(newNodes) > 1 { if n.parent == nil && len(newNodes) > 1 {
n.parent = &node{transaction: t, isLeaf: false} n.parent = &node{tx: t, isLeaf: false}
nodes = append(nodes, n.parent) nodes = append(nodes, n.parent)
} }
@ -233,7 +233,7 @@ func (t *RWTransaction) spill() error {
} }
// write writes any dirty pages to disk. // write writes any dirty pages to disk.
func (t *RWTransaction) write() error { func (t *RWTx) write() error {
// Sort pages by id. // Sort pages by id.
pages := make(pages, 0, len(t.pages)) pages := make(pages, 0, len(t.pages))
for _, p := range t.pages { for _, p := range t.pages {
@ -258,7 +258,7 @@ func (t *RWTransaction) write() error {
} }
// writeMeta writes the meta to the disk. // writeMeta writes the meta to the disk.
func (t *RWTransaction) writeMeta() error { func (t *RWTx) writeMeta() error {
// Create a temporary buffer for the meta page. // Create a temporary buffer for the meta page.
buf := make([]byte, t.db.pageSize) buf := make([]byte, t.db.pageSize)
p := t.db.pageInBuffer(buf, 0) p := t.db.pageInBuffer(buf, 0)
@ -271,14 +271,14 @@ func (t *RWTransaction) writeMeta() error {
} }
// node creates a node from a page and associates it with a given parent. // node creates a node from a page and associates it with a given parent.
func (t *RWTransaction) node(pgid pgid, parent *node) *node { func (t *RWTx) node(pgid pgid, parent *node) *node {
// Retrieve node if it has already been fetched. // Retrieve node if it has already been fetched.
if n := t.Transaction.node(pgid); n != nil { if n := t.Tx.node(pgid); n != nil {
return n return n
} }
// Otherwise create a branch and cache it. // Otherwise create a branch and cache it.
n := &node{transaction: t, parent: parent} n := &node{tx: t, parent: parent}
if n.parent != nil { if n.parent != nil {
n.depth = n.parent.depth + 1 n.depth = n.parent.depth + 1
} }
@ -289,7 +289,7 @@ func (t *RWTransaction) node(pgid pgid, parent *node) *node {
} }
// dereference removes all references to the old mmap. // dereference removes all references to the old mmap.
func (t *RWTransaction) dereference() { func (t *RWTx) dereference() {
for _, n := range t.nodes { for _, n := range t.nodes {
n.dereference() n.dereference()
} }

View File

@ -9,31 +9,31 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
// Ensure that a RWTransaction can be retrieved. // Ensure that a RWTx can be retrieved.
func TestRWTransaction(t *testing.T) { func TestRWTx(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
txn, err := db.RWTransaction() txn, err := db.RWTx()
assert.NotNil(t, txn) assert.NotNil(t, txn)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, txn.DB(), db) assert.Equal(t, txn.DB(), db)
}) })
} }
// Ensure that opening a RWTransaction while the DB is closed returns an error. // Ensure that opening a RWTx while the DB is closed returns an error.
func TestRWTransactionOpenWithClosedDB(t *testing.T) { func TestRWTxOpenWithClosedDB(t *testing.T) {
withDB(func(db *DB, path string) { withDB(func(db *DB, path string) {
txn, err := db.RWTransaction() txn, err := db.RWTx()
assert.Equal(t, err, ErrDatabaseNotOpen) assert.Equal(t, err, ErrDatabaseNotOpen)
assert.Nil(t, txn) assert.Nil(t, txn)
}) })
} }
// Ensure that retrieving all buckets returns writable buckets. // Ensure that retrieving all buckets returns writable buckets.
func TestRWTransactionBuckets(t *testing.T) { func TestRWTxBuckets(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
db.CreateBucket("widgets") db.CreateBucket("widgets")
db.CreateBucket("woojits") db.CreateBucket("woojits")
db.Do(func(txn *RWTransaction) error { db.Do(func(txn *RWTx) error {
buckets := txn.Buckets() buckets := txn.Buckets()
assert.Equal(t, len(buckets), 2) assert.Equal(t, len(buckets), 2)
assert.Equal(t, buckets[0].Name(), "widgets") assert.Equal(t, buckets[0].Name(), "widgets")
@ -50,7 +50,7 @@ func TestRWTransactionBuckets(t *testing.T) {
} }
// Ensure that a bucket can be created and retrieved. // Ensure that a bucket can be created and retrieved.
func TestRWTransactionCreateBucket(t *testing.T) { func TestRWTxCreateBucket(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
// Create a bucket. // Create a bucket.
err := db.CreateBucket("widgets") err := db.CreateBucket("widgets")
@ -64,7 +64,7 @@ func TestRWTransactionCreateBucket(t *testing.T) {
} }
// 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 TestRWTransactionCreateBucketIfNotExists(t *testing.T) { func TestRWTxCreateBucketIfNotExists(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
assert.NoError(t, db.CreateBucketIfNotExists("widgets")) assert.NoError(t, db.CreateBucketIfNotExists("widgets"))
assert.NoError(t, db.CreateBucketIfNotExists("widgets")) assert.NoError(t, db.CreateBucketIfNotExists("widgets"))
@ -78,7 +78,7 @@ func TestRWTransactionCreateBucketIfNotExists(t *testing.T) {
} }
// Ensure that a bucket cannot be created twice. // Ensure that a bucket cannot be created twice.
func TestRWTransactionRecreateBucket(t *testing.T) { func TestRWTxRecreateBucket(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
// Create a bucket. // Create a bucket.
err := db.CreateBucket("widgets") err := db.CreateBucket("widgets")
@ -91,7 +91,7 @@ func TestRWTransactionRecreateBucket(t *testing.T) {
} }
// Ensure that a bucket is created with a non-blank name. // Ensure that a bucket is created with a non-blank name.
func TestRWTransactionCreateBucketWithoutName(t *testing.T) { func TestRWTxCreateBucketWithoutName(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
err := db.CreateBucket("") err := db.CreateBucket("")
assert.Equal(t, err, ErrBucketNameRequired) assert.Equal(t, err, ErrBucketNameRequired)
@ -99,7 +99,7 @@ func TestRWTransactionCreateBucketWithoutName(t *testing.T) {
} }
// Ensure that a bucket name is not too long. // Ensure that a bucket name is not too long.
func TestRWTransactionCreateBucketWithLongName(t *testing.T) { func TestRWTxCreateBucketWithLongName(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
err := db.CreateBucket(strings.Repeat("X", 255)) err := db.CreateBucket(strings.Repeat("X", 255))
assert.NoError(t, err) assert.NoError(t, err)
@ -110,7 +110,7 @@ func TestRWTransactionCreateBucketWithLongName(t *testing.T) {
} }
// Ensure that a bucket can be deleted. // Ensure that a bucket can be deleted.
func TestRWTransactionDeleteBucket(t *testing.T) { func TestRWTxDeleteBucket(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
// Create a bucket and add a value. // Create a bucket and add a value.
db.CreateBucket("widgets") db.CreateBucket("widgets")
@ -136,7 +136,7 @@ func TestRWTransactionDeleteBucket(t *testing.T) {
} }
// Ensure that an error is returned when deleting from a bucket that doesn't exist. // Ensure that an error is returned when deleting from a bucket that doesn't exist.
func TestRWTransactionDeleteBucketNotFound(t *testing.T) { func TestRWTxDeleteBucketNotFound(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
err := db.DeleteBucket("widgets") err := db.DeleteBucket("widgets")
assert.Equal(t, err, ErrBucketNotFound) assert.Equal(t, err, ErrBucketNotFound)
@ -144,19 +144,19 @@ func TestRWTransactionDeleteBucketNotFound(t *testing.T) {
} }
// Benchmark the performance of bulk put transactions in random order. // Benchmark the performance of bulk put transactions in random order.
func BenchmarkRWTransactionPutRandom(b *testing.B) { func BenchmarkRWTxPutRandom(b *testing.B) {
indexes := rand.Perm(b.N) indexes := rand.Perm(b.N)
value := []byte(strings.Repeat("0", 64)) value := []byte(strings.Repeat("0", 64))
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
db.CreateBucket("widgets") db.CreateBucket("widgets")
var txn *RWTransaction var txn *RWTx
var bucket *Bucket var bucket *Bucket
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
if i%1000 == 0 { if i%1000 == 0 {
if txn != nil { if txn != nil {
txn.Commit() txn.Commit()
} }
txn, _ = db.RWTransaction() txn, _ = db.RWTx()
bucket = txn.Bucket("widgets") bucket = txn.Bucket("widgets")
} }
bucket.Put([]byte(strconv.Itoa(indexes[i])), value) bucket.Put([]byte(strconv.Itoa(indexes[i])), value)
@ -166,11 +166,11 @@ func BenchmarkRWTransactionPutRandom(b *testing.B) {
} }
// Benchmark the performance of bulk put transactions in sequential order. // Benchmark the performance of bulk put transactions in sequential order.
func BenchmarkRWTransactionPutSequential(b *testing.B) { func BenchmarkRWTxPutSequential(b *testing.B) {
value := []byte(strings.Repeat("0", 64)) value := []byte(strings.Repeat("0", 64))
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
db.CreateBucket("widgets") db.CreateBucket("widgets")
db.Do(func(txn *RWTransaction) error { db.Do(func(txn *RWTx) error {
bucket := txn.Bucket("widgets") bucket := txn.Bucket("widgets")
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
bucket.Put([]byte(strconv.Itoa(i)), value) bucket.Put([]byte(strconv.Itoa(i)), value)

View File

@ -1,26 +1,26 @@
package bolt package bolt
// Transaction represents a read-only transaction on the database. // Tx represents a read-only transaction on the database.
// It can be used for retrieving values for keys as well as creating cursors for // It can be used for retrieving values for keys as well as creating cursors for
// iterating over the data. // iterating over the data.
// //
// IMPORTANT: You must close transactions when you are done with them. Pages // IMPORTANT: You must close transactions when you are done with them. Pages
// can not be reclaimed by the writer until no more transactions are using them. // can not be reclaimed by the writer until no more transactions are using them.
// A long running read transaction can cause the database to quickly grow. // A long running read transaction can cause the database to quickly grow.
type Transaction struct { type Tx struct {
db *DB db *DB
rwtransaction *RWTransaction rwtx *RWTx
meta *meta meta *meta
buckets *buckets buckets *buckets
nodes map[pgid]*node nodes map[pgid]*node
pages map[pgid]*page pages map[pgid]*page
} }
// txnid represents the internal transaction identifier. // txid represents the internal transaction identifier.
type txnid uint64 type txid uint64
// init initializes the transaction and associates it with a database. // init initializes the transaction and associates it with a database.
func (t *Transaction) init(db *DB) { func (t *Tx) init(db *DB) {
t.db = db t.db = db
t.pages = nil t.pages = nil
@ -34,30 +34,30 @@ func (t *Transaction) init(db *DB) {
} }
// id returns the transaction id. // id returns the transaction id.
func (t *Transaction) id() txnid { func (t *Tx) id() txid {
return t.meta.txnid return t.meta.txid
} }
// Close closes the transaction and releases any pages it is using. // Close closes the transaction and releases any pages it is using.
func (t *Transaction) Close() { func (t *Tx) Close() {
if t.db != nil { if t.db != nil {
if t.rwtransaction != nil { if t.rwtx != nil {
t.rwtransaction.Rollback() t.rwtx.Rollback()
} else { } else {
t.db.removeTransaction(t) t.db.removeTx(t)
t.db = nil t.db = nil
} }
} }
} }
// DB returns a reference to the database that created the transaction. // DB returns a reference to the database that created the transaction.
func (t *Transaction) DB() *DB { func (t *Tx) DB() *DB {
return t.db return t.db
} }
// Bucket retrieves a bucket by name. // Bucket retrieves a bucket by name.
// Returns nil if the bucket does not exist. // Returns nil if the bucket does not exist.
func (t *Transaction) Bucket(name string) *Bucket { func (t *Tx) Bucket(name string) *Bucket {
b := t.buckets.get(name) b := t.buckets.get(name)
if b == nil { if b == nil {
return nil return nil
@ -66,20 +66,20 @@ func (t *Transaction) Bucket(name string) *Bucket {
return &Bucket{ return &Bucket{
bucket: b, bucket: b,
name: name, name: name,
transaction: t, tx: t,
rwtransaction: t.rwtransaction, rwtx: t.rwtx,
} }
} }
// Buckets retrieves a list of all buckets. // Buckets retrieves a list of all buckets.
func (t *Transaction) Buckets() []*Bucket { func (t *Tx) Buckets() []*Bucket {
buckets := make([]*Bucket, 0, len(t.buckets.items)) buckets := make([]*Bucket, 0, len(t.buckets.items))
for name, b := range t.buckets.items { for name, b := range t.buckets.items {
bucket := &Bucket{ bucket := &Bucket{
bucket: b, bucket: b,
name: name, name: name,
transaction: t, tx: t,
rwtransaction: t.rwtransaction, rwtx: t.rwtx,
} }
buckets = append(buckets, bucket) buckets = append(buckets, bucket)
} }
@ -88,7 +88,7 @@ func (t *Transaction) Buckets() []*Bucket {
// page returns a reference to the page with a given id. // page returns a reference to the page with a given id.
// If page has been written to then a temporary bufferred page is returned. // If page has been written to then a temporary bufferred page is returned.
func (t *Transaction) page(id pgid) *page { func (t *Tx) page(id pgid) *page {
// Check the dirty pages first. // Check the dirty pages first.
if t.pages != nil { if t.pages != nil {
if p, ok := t.pages[id]; ok { if p, ok := t.pages[id]; ok {
@ -101,7 +101,7 @@ func (t *Transaction) page(id pgid) *page {
} }
// node returns a reference to the in-memory node for a given page, if it exists. // node returns a reference to the in-memory node for a given page, if it exists.
func (t *Transaction) node(id pgid) *node { func (t *Tx) node(id pgid) *node {
if t.nodes == nil { if t.nodes == nil {
return nil return nil
} }
@ -110,7 +110,7 @@ func (t *Transaction) node(id pgid) *node {
// pageNode returns the in-memory node, if it exists. // pageNode returns the in-memory node, if it exists.
// Otherwise returns the underlying page. // Otherwise returns the underlying page.
func (t *Transaction) pageNode(id pgid) (*page, *node) { func (t *Tx) pageNode(id pgid) (*page, *node) {
if n := t.node(id); n != nil { if n := t.node(id); n != nil {
return nil, n return nil, n
} }
@ -118,7 +118,7 @@ func (t *Transaction) pageNode(id pgid) (*page, *node) {
} }
// forEachPage iterates over every page within a given page and executes a function. // forEachPage iterates over every page within a given page and executes a function.
func (t *Transaction) forEachPage(pgid pgid, depth int, fn func(*page, int)) { func (t *Tx) forEachPage(pgid pgid, depth int, fn func(*page, int)) {
p := t.page(pgid) p := t.page(pgid)
// Execute function. // Execute function.

View File

@ -14,7 +14,7 @@ import (
) )
// Ensure that the database can retrieve a list of buckets. // Ensure that the database can retrieve a list of buckets.
func TestTransactionBuckets(t *testing.T) { func TestTxBuckets(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
db.CreateBucket("foo") db.CreateBucket("foo")
db.CreateBucket("bar") db.CreateBucket("bar")
@ -28,8 +28,8 @@ func TestTransactionBuckets(t *testing.T) {
}) })
} }
// Ensure that a Transaction can retrieve a bucket. // Ensure that a Tx can retrieve a bucket.
func TestTransactionBucketMissing(t *testing.T) { func TestTxBucketMissing(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
db.CreateBucket("widgets") db.CreateBucket("widgets")
b, err := db.Bucket("widgets") b, err := db.Bucket("widgets")
@ -40,8 +40,8 @@ func TestTransactionBucketMissing(t *testing.T) {
}) })
} }
// Ensure that a Transaction retrieving a non-existent key returns nil. // Ensure that a Tx retrieving a non-existent key returns nil.
func TestTransactionGetMissing(t *testing.T) { func TestTxGetMissing(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
db.CreateBucket("widgets") db.CreateBucket("widgets")
db.Put("widgets", []byte("foo"), []byte("bar")) db.Put("widgets", []byte("foo"), []byte("bar"))
@ -51,11 +51,11 @@ func TestTransactionGetMissing(t *testing.T) {
}) })
} }
// Ensure that a Transaction cursor can iterate over an empty bucket without error. // Ensure that a Tx cursor can iterate over an empty bucket without error.
func TestTransactionCursorEmptyBucket(t *testing.T) { func TestTxCursorEmptyBucket(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
db.CreateBucket("widgets") db.CreateBucket("widgets")
txn, _ := db.Transaction() txn, _ := db.Tx()
c := txn.Bucket("widgets").Cursor() c := txn.Bucket("widgets").Cursor()
k, v := c.First() k, v := c.First()
assert.Nil(t, k) assert.Nil(t, k)
@ -64,14 +64,14 @@ func TestTransactionCursorEmptyBucket(t *testing.T) {
}) })
} }
// Ensure that a Transaction 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 TestTransactionCursorLeafRoot(t *testing.T) { func TestTxCursorLeafRoot(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
db.CreateBucket("widgets") db.CreateBucket("widgets")
db.Put("widgets", []byte("baz"), []byte{}) db.Put("widgets", []byte("baz"), []byte{})
db.Put("widgets", []byte("foo"), []byte{0}) db.Put("widgets", []byte("foo"), []byte{0})
db.Put("widgets", []byte("bar"), []byte{1}) db.Put("widgets", []byte("bar"), []byte{1})
txn, _ := db.Transaction() txn, _ := db.Tx()
c := txn.Bucket("widgets").Cursor() c := txn.Bucket("widgets").Cursor()
k, v := c.First() k, v := c.First()
@ -98,14 +98,14 @@ func TestTransactionCursorLeafRoot(t *testing.T) {
}) })
} }
// Ensure that a Transaction 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 TestTransactionCursorLeafRootReverse(t *testing.T) { func TestTxCursorLeafRootReverse(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
db.CreateBucket("widgets") db.CreateBucket("widgets")
db.Put("widgets", []byte("baz"), []byte{}) db.Put("widgets", []byte("baz"), []byte{})
db.Put("widgets", []byte("foo"), []byte{0}) db.Put("widgets", []byte("foo"), []byte{0})
db.Put("widgets", []byte("bar"), []byte{1}) db.Put("widgets", []byte("bar"), []byte{1})
txn, _ := db.Transaction() txn, _ := db.Tx()
c := txn.Bucket("widgets").Cursor() c := txn.Bucket("widgets").Cursor()
k, v := c.Last() k, v := c.Last()
@ -132,14 +132,14 @@ func TestTransactionCursorLeafRootReverse(t *testing.T) {
}) })
} }
// Ensure that a Transaction cursor can restart from the beginning. // Ensure that a Tx cursor can restart from the beginning.
func TestTransactionCursorRestart(t *testing.T) { func TestTxCursorRestart(t *testing.T) {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
db.CreateBucket("widgets") db.CreateBucket("widgets")
db.Put("widgets", []byte("bar"), []byte{}) db.Put("widgets", []byte("bar"), []byte{})
db.Put("widgets", []byte("foo"), []byte{}) db.Put("widgets", []byte("foo"), []byte{})
txn, _ := db.Transaction() txn, _ := db.Tx()
c := txn.Bucket("widgets").Cursor() c := txn.Bucket("widgets").Cursor()
k, _ := c.First() k, _ := c.First()
@ -158,13 +158,13 @@ func TestTransactionCursorRestart(t *testing.T) {
}) })
} }
// Ensure that a transaction can iterate over all elements in a bucket. // Ensure that a Tx can iterate over all elements in a bucket.
func TestTransactionCursorIterate(t *testing.T) { func TestTxCursorIterate(t *testing.T) {
f := func(items testdata) bool { f := func(items testdata) bool {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
// Bulk insert all values. // Bulk insert all values.
db.CreateBucket("widgets") db.CreateBucket("widgets")
rwtxn, _ := db.RWTransaction() rwtxn, _ := db.RWTx()
b := rwtxn.Bucket("widgets") b := rwtxn.Bucket("widgets")
for _, item := range items { for _, item := range items {
assert.NoError(t, b.Put(item.Key, item.Value)) assert.NoError(t, b.Put(item.Key, item.Value))
@ -176,7 +176,7 @@ func TestTransactionCursorIterate(t *testing.T) {
// Iterate over all items and check consistency. // Iterate over all items and check consistency.
var index = 0 var index = 0
txn, _ := db.Transaction() txn, _ := db.Tx()
c := txn.Bucket("widgets").Cursor() c := txn.Bucket("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() {
assert.Equal(t, k, items[index].Key) assert.Equal(t, k, items[index].Key)
@ -196,12 +196,12 @@ func TestTransactionCursorIterate(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 TestTransactionCursorIterateReverse(t *testing.T) { func TestTxCursorIterateReverse(t *testing.T) {
f := func(items testdata) bool { f := func(items testdata) bool {
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
// Bulk insert all values. // Bulk insert all values.
db.CreateBucket("widgets") db.CreateBucket("widgets")
rwtxn, _ := db.RWTransaction() rwtxn, _ := db.RWTx()
b := rwtxn.Bucket("widgets") b := rwtxn.Bucket("widgets")
for _, item := range items { for _, item := range items {
assert.NoError(t, b.Put(item.Key, item.Value)) assert.NoError(t, b.Put(item.Key, item.Value))
@ -213,7 +213,7 @@ func TestTransactionCursorIterateReverse(t *testing.T) {
// Iterate over all items and check consistency. // Iterate over all items and check consistency.
var index = 0 var index = 0
txn, _ := db.Transaction() txn, _ := db.Tx()
c := txn.Bucket("widgets").Cursor() c := txn.Bucket("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() {
assert.Equal(t, k, items[index].Key) assert.Equal(t, k, items[index].Key)
@ -233,14 +233,14 @@ func TestTransactionCursorIterateReverse(t *testing.T) {
} }
// Benchmark the performance iterating over a cursor. // Benchmark the performance iterating over a cursor.
func BenchmarkTransactionCursor(b *testing.B) { func BenchmarkTxCursor(b *testing.B) {
indexes := rand.Perm(b.N) indexes := rand.Perm(b.N)
value := []byte(strings.Repeat("0", 64)) value := []byte(strings.Repeat("0", 64))
withOpenDB(func(db *DB, path string) { withOpenDB(func(db *DB, path string) {
// Write data to bucket. // Write data to bucket.
db.CreateBucket("widgets") db.CreateBucket("widgets")
db.Do(func(txn *RWTransaction) error { db.Do(func(txn *RWTx) error {
bucket := txn.Bucket("widgets") bucket := txn.Bucket("widgets")
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
bucket.Put([]byte(strconv.Itoa(indexes[i])), value) bucket.Put([]byte(strconv.Itoa(indexes[i])), value)
@ -250,7 +250,7 @@ func BenchmarkTransactionCursor(b *testing.B) {
b.ResetTimer() b.ResetTimer()
// Iterate over bucket using cursor. // Iterate over bucket using cursor.
db.With(func(txn *Transaction) error { db.With(func(txn *Tx) error {
count := 0 count := 0
c := txn.Bucket("widgets").Cursor() c := txn.Bucket("widgets").Cursor()
for k, _ := c.First(); k != nil; k, _ = c.Next() { for k, _ := c.First(); k != nil; k, _ = c.Next() {