mirror of https://github.com/etcd-io/bbolt.git
Fix leaf/branch deserialization.
parent
4ad445aa85
commit
149d48fb9e
18
bucket.go
18
bucket.go
|
@ -4,29 +4,27 @@ type Bucket struct {
|
||||||
*bucket
|
*bucket
|
||||||
name string
|
name string
|
||||||
transaction *Transaction
|
transaction *Transaction
|
||||||
cursors []*Cursor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type bucket struct {
|
type bucket struct {
|
||||||
root pgid
|
root pgid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the bucket.
|
||||||
|
func (b *Bucket) Name() string {
|
||||||
|
return b.name
|
||||||
|
}
|
||||||
|
|
||||||
// Get retrieves the value for a key in the bucket.
|
// Get retrieves the value for a key in the bucket.
|
||||||
func (b *Bucket) Get(key []byte) []byte {
|
func (b *Bucket) Get(key []byte) []byte {
|
||||||
return b.cursor().Get(key)
|
return b.Cursor().Get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cursor creates a new cursor for this bucket.
|
// Cursor creates a new cursor for this bucket.
|
||||||
func (b *Bucket) Cursor() *Cursor {
|
func (b *Bucket) Cursor() *Cursor {
|
||||||
c := b.cursor()
|
|
||||||
b.cursors = append(b.cursors, c)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// cursor creates a new untracked cursor for this bucket.
|
|
||||||
func (b *Bucket) cursor() *Cursor {
|
|
||||||
return &Cursor{
|
return &Cursor{
|
||||||
bucket: b,
|
transaction: b.transaction,
|
||||||
|
root: b.root,
|
||||||
stack: make([]elem, 0),
|
stack: make([]elem, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
45
cursor.go
45
cursor.go
|
@ -1,7 +1,13 @@
|
||||||
package bolt
|
package bolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
type Cursor struct {
|
type Cursor struct {
|
||||||
bucket *Bucket
|
transaction *Transaction
|
||||||
|
root pgid
|
||||||
stack []elem
|
stack []elem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,10 +17,6 @@ type elem struct {
|
||||||
index int
|
index int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cursor) Bucket() *Bucket {
|
|
||||||
return c.bucket
|
|
||||||
}
|
|
||||||
|
|
||||||
// First moves the cursor to the first item in the bucket and returns its key and data.
|
// First moves the cursor to the first item in the bucket and returns its key and data.
|
||||||
func (c *Cursor) First() ([]byte, []byte) {
|
func (c *Cursor) First() ([]byte, []byte) {
|
||||||
// TODO: Traverse to the first key.
|
// TODO: Traverse to the first key.
|
||||||
|
@ -39,11 +41,42 @@ func (c *Cursor) Get(key []byte) []byte {
|
||||||
func (c *Cursor) Goto(key []byte) bool {
|
func (c *Cursor) Goto(key []byte) bool {
|
||||||
// TODO(benbjohnson): Optimize for specific use cases.
|
// TODO(benbjohnson): Optimize for specific use cases.
|
||||||
|
|
||||||
// TODO: Start from root page and traverse to correct page.
|
// Start from root page and traverse to correct page.
|
||||||
|
c.stack = c.stack[:0]
|
||||||
|
c.search(key, c.transaction.page(c.root))
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Cursor) search(key []byte, p *page) {
|
||||||
|
e := elem{page: p}
|
||||||
|
c.stack = append(c.stack, e)
|
||||||
|
|
||||||
|
// If we're on a leaf page then find the specific node.
|
||||||
|
if (p.flags & p_leaf) != 0 {
|
||||||
|
c.nsearch(key, p)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary search for the correct branch node.
|
||||||
|
nodes := p.bnodes()
|
||||||
|
e.index = sort.Search(int(p.count)-1, func(i int) bool { return bytes.Compare(nodes[i+1].key(), key) != -1 })
|
||||||
|
|
||||||
|
// Recursively search to the next page.
|
||||||
|
c.search(key, c.transaction.page(nodes[e.index].pgid))
|
||||||
|
}
|
||||||
|
|
||||||
|
// nsearch searches a leaf node for the index of the node that matches key.
|
||||||
|
func (c *Cursor) nsearch(key []byte, p *page) {
|
||||||
|
e := &c.stack[len(c.stack)-1]
|
||||||
|
|
||||||
|
// Binary search for the correct leaf node index.
|
||||||
|
nodes := p.lnodes()
|
||||||
|
e.index = sort.Search(int(p.count), func(i int) bool {
|
||||||
|
return bytes.Compare(nodes[i].key(), key) != -1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// top returns the page and leaf node that the cursor is currently pointing at.
|
// top returns the page and leaf node that the cursor is currently pointing at.
|
||||||
func (c *Cursor) top() (*page, *lnode) {
|
func (c *Cursor) top() (*page, *lnode) {
|
||||||
elem := c.stack[len(c.stack)-1]
|
elem := c.stack[len(c.stack)-1]
|
||||||
|
|
13
db.go
13
db.go
|
@ -158,19 +158,19 @@ func (db *DB) init() error {
|
||||||
m.version = Version
|
m.version = Version
|
||||||
m.pageSize = uint32(db.pageSize)
|
m.pageSize = uint32(db.pageSize)
|
||||||
m.version = Version
|
m.version = Version
|
||||||
m.free = 3
|
m.free = 2
|
||||||
m.sys.root = 4
|
m.sys.root = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write an empty freelist at page 3.
|
// Write an empty freelist at page 3.
|
||||||
p := db.pageInBuffer(buf[:], pgid(2))
|
p := db.pageInBuffer(buf[:], pgid(2))
|
||||||
p.id = pgid(3)
|
p.id = pgid(2)
|
||||||
p.flags = p_freelist
|
p.flags = p_freelist
|
||||||
p.count = 0
|
p.count = 0
|
||||||
|
|
||||||
// Write an empty leaf page at page 4.
|
// Write an empty leaf page at page 4.
|
||||||
p = db.pageInBuffer(buf[:], pgid(3))
|
p = db.pageInBuffer(buf[:], pgid(3))
|
||||||
p.id = pgid(4)
|
p.id = pgid(3)
|
||||||
p.flags = p_leaf
|
p.flags = p_leaf
|
||||||
p.count = 0
|
p.count = 0
|
||||||
|
|
||||||
|
@ -226,7 +226,10 @@ func (db *DB) RWTransaction() (*RWTransaction, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a transaction associated with the database.
|
// Create a transaction associated with the database.
|
||||||
t := &RWTransaction{}
|
t := &RWTransaction{
|
||||||
|
branches: make(map[pgid]*branch),
|
||||||
|
leafs: make(map[pgid]*leaf),
|
||||||
|
}
|
||||||
t.init(db, db.meta())
|
t.init(db, db.meta())
|
||||||
|
|
||||||
return t, nil
|
return t, nil
|
||||||
|
|
|
@ -146,7 +146,6 @@ func TestDBCorruptMeta0(t *testing.T) {
|
||||||
|
|
||||||
// Open the database.
|
// Open the database.
|
||||||
err := db.Open(path, 0666)
|
err := db.Open(path, 0666)
|
||||||
warn(err)
|
|
||||||
assert.Equal(t, err, &Error{"meta error", InvalidError})
|
assert.Equal(t, err, &Error{"meta error", InvalidError})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
4
meta.go
4
meta.go
|
@ -5,7 +5,7 @@ var (
|
||||||
VersionMismatchError = &Error{"version mismatch", nil}
|
VersionMismatchError = &Error{"version mismatch", nil}
|
||||||
)
|
)
|
||||||
|
|
||||||
const magic uint32 = 0xC0DEC0DE
|
const magic uint32 = 0xDEADC0DE
|
||||||
const version uint32 = 1
|
const version uint32 = 1
|
||||||
|
|
||||||
type meta struct {
|
type meta struct {
|
||||||
|
@ -13,8 +13,8 @@ type meta struct {
|
||||||
version uint32
|
version uint32
|
||||||
pageSize uint32
|
pageSize uint32
|
||||||
pgid pgid
|
pgid pgid
|
||||||
txnid txnid
|
|
||||||
free pgid
|
free pgid
|
||||||
|
txnid txnid
|
||||||
sys bucket
|
sys bucket
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
10
page.go
10
page.go
|
@ -37,11 +37,21 @@ func (p *page) lnode(index int) *lnode {
|
||||||
return &((*[maxNodesPerPage]lnode)(unsafe.Pointer(&p.ptr)))[index]
|
return &((*[maxNodesPerPage]lnode)(unsafe.Pointer(&p.ptr)))[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// lnodes retrieves a list of leaf nodes.
|
||||||
|
func (p *page) lnodes() []lnode {
|
||||||
|
return ((*[maxNodesPerPage]lnode)(unsafe.Pointer(&p.ptr)))[:]
|
||||||
|
}
|
||||||
|
|
||||||
// bnode retrieves the branch node by index
|
// bnode retrieves the branch node by index
|
||||||
func (p *page) bnode(index int) *bnode {
|
func (p *page) bnode(index int) *bnode {
|
||||||
return &((*[maxNodesPerPage]bnode)(unsafe.Pointer(&p.ptr)))[index]
|
return &((*[maxNodesPerPage]bnode)(unsafe.Pointer(&p.ptr)))[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bnodes retrieves a list of branch nodes.
|
||||||
|
func (p *page) bnodes() []bnode {
|
||||||
|
return ((*[maxNodesPerPage]bnode)(unsafe.Pointer(&p.ptr)))[:]
|
||||||
|
}
|
||||||
|
|
||||||
// freelist retrieves a list of page ids from a freelist page.
|
// freelist retrieves a list of page ids from a freelist page.
|
||||||
func (p *page) freelist() []pgid {
|
func (p *page) freelist() []pgid {
|
||||||
return ((*[maxNodesPerPage]pgid)(unsafe.Pointer(&p.ptr)))[0:p.count]
|
return ((*[maxNodesPerPage]pgid)(unsafe.Pointer(&p.ptr)))[0:p.count]
|
||||||
|
|
|
@ -24,10 +24,12 @@ func (t *RWTransaction) CreateBucket(name string) error {
|
||||||
var raw = (*bucket)(unsafe.Pointer(&buf[0]))
|
var raw = (*bucket)(unsafe.Pointer(&buf[0]))
|
||||||
raw.root = 0
|
raw.root = 0
|
||||||
|
|
||||||
// Insert new node.
|
// Move cursor to insertion location.
|
||||||
c := t.sys.cursor()
|
c := t.sys.Cursor()
|
||||||
c.Goto([]byte(name))
|
c.Goto([]byte(name))
|
||||||
t.leaf(c.page().id).put([]byte(name), buf[:])
|
|
||||||
|
// Load the leaf node from the cursor and insert the key/value.
|
||||||
|
t.leaf(c).put([]byte(name), buf[:])
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -57,9 +59,9 @@ func (t *RWTransaction) Put(name string, key []byte, value []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert a new node.
|
// Insert a new node.
|
||||||
c := b.cursor()
|
c := b.Cursor()
|
||||||
c.Goto(key)
|
c.Goto(key)
|
||||||
t.leaf(c.page().id).put(key, value)
|
t.leaf(c).put(key, value)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -75,7 +77,6 @@ func (t *RWTransaction) Delete(key []byte) error {
|
||||||
func (t *RWTransaction) Commit() error {
|
func (t *RWTransaction) Commit() error {
|
||||||
// TODO(benbjohnson): Use vectorized I/O to write out dirty pages.
|
// TODO(benbjohnson): Use vectorized I/O to write out dirty pages.
|
||||||
|
|
||||||
|
|
||||||
// TODO: Flush data.
|
// TODO: Flush data.
|
||||||
|
|
||||||
// TODO: Update meta.
|
// TODO: Update meta.
|
||||||
|
@ -104,18 +105,44 @@ func (t *RWTransaction) allocate(size int) (*page, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// leaf returns a deserialized leaf page.
|
// leaf retrieves a leaf object based on the current position of a cursor.
|
||||||
func (t *RWTransaction) leaf(id pgid) *leaf {
|
func (t *RWTransaction) leaf(c *Cursor) *leaf {
|
||||||
if t.leafs != nil {
|
e := c.stack[len(c.stack)-1]
|
||||||
|
id := e.page.id
|
||||||
|
|
||||||
|
// Retrieve leaf if it has already been fetched.
|
||||||
if l := t.leafs[id]; l != nil {
|
if l := t.leafs[id]; l != nil {
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Read raw page and deserialize.
|
// Otherwise create a leaf and cache it.
|
||||||
l := &leaf{}
|
l := &leaf{}
|
||||||
l.read(t.page(id))
|
l.read(t.page(id))
|
||||||
|
l.parent = t.branch(c.stack[:len(c.stack)-1])
|
||||||
t.leafs[id] = l
|
t.leafs[id] = l
|
||||||
|
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// branch retrieves a branch object based a cursor stack.
|
||||||
|
// This should only be called from leaf().
|
||||||
|
func (t *RWTransaction) branch(stack []elem) *branch {
|
||||||
|
if len(stack) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve branch if it has already been fetched.
|
||||||
|
e := &stack[len(stack)-1]
|
||||||
|
id := e.page.id
|
||||||
|
if b := t.branches[id]; b != nil {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise create a branch and cache it.
|
||||||
|
b := &branch{}
|
||||||
|
b.read(t.page(id))
|
||||||
|
b.parent = t.branch(stack[:len(stack)-1])
|
||||||
|
t.branches[id] = b
|
||||||
|
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
package bolt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ensure that a RWTransaction can be retrieved.
|
||||||
|
func TestRWTransaction(t *testing.T) {
|
||||||
|
withOpenDB(func(db *DB, path string) {
|
||||||
|
txn, err := db.RWTransaction()
|
||||||
|
assert.NotNil(t, txn)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that a bucket can be created and retrieved.
|
||||||
|
func TestTransactionCreateBucket(t *testing.T) {
|
||||||
|
withOpenDB(func(db *DB, path string) {
|
||||||
|
// Create a bucket.
|
||||||
|
txn, _ := db.RWTransaction()
|
||||||
|
err := txn.CreateBucket("widgets")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Commit the transaction.
|
||||||
|
err = txn.Commit()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Open a separate read-only transaction.
|
||||||
|
rtxn, err := db.Transaction()
|
||||||
|
assert.NotNil(t, txn)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
b, err := rtxn.Bucket("widgets")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if assert.NotNil(t, b) {
|
||||||
|
assert.Equal(t, b.Name(), "widgets")
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that an existing bucket cannot be created.
|
||||||
|
func TestTransactionCreateExistingBucket(t *testing.T) {
|
||||||
|
t.Skip("pending")
|
||||||
|
}
|
|
@ -54,7 +54,7 @@ func (t *Transaction) Bucket(name string) *Bucket {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve bucket data from the system bucket.
|
// Retrieve bucket data from the system bucket.
|
||||||
value := t.sys.cursor().Get([]byte(name))
|
value := t.sys.Cursor().Get([]byte(name))
|
||||||
if value == nil {
|
if value == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
package bolt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ensure that a bucket can be created and retrieved.
|
|
||||||
func TestTransactionCreateBucket(t *testing.T) {
|
|
||||||
t.Skip("pending")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that an existing bucket cannot be created.
|
|
||||||
func TestTransactionCreateExistingBucket(t *testing.T) {
|
|
||||||
t.Skip("pending")
|
|
||||||
}
|
|
Loading…
Reference in New Issue