Rename sys ☞ buckets.

pull/34/head
Ben Johnson 2014-02-05 22:15:47 -07:00
parent 4820312de2
commit 0ed3dc3071
9 changed files with 136 additions and 135 deletions

View File

@ -5,51 +5,51 @@ import (
"unsafe"
)
// sys represents a in-memory system page.
type sys struct {
// buckets represents a in-memory buckets page.
type buckets struct {
pgid pgid
buckets map[string]*bucket
items map[string]*bucket
}
// size returns the size of the page after serialization.
func (s *sys) size() int {
func (b *buckets) size() int {
var size int = pageHeaderSize
for key, _ := range s.buckets {
for key, _ := range b.items {
size += int(unsafe.Sizeof(bucket{})) + len(key)
}
return size
}
// get retrieves a bucket by name.
func (s *sys) get(key string) *bucket {
return s.buckets[key]
func (b *buckets) get(key string) *bucket {
return b.items[key]
}
// put sets a new value for a bucket.
func (s *sys) put(key string, b *bucket) {
s.buckets[key] = b
func (b *buckets) put(key string, item *bucket) {
b.items[key] = item
}
// del deletes a bucket by name.
func (s *sys) del(key string) {
if b := s.buckets[key]; b != nil {
delete(s.buckets, key)
func (b *buckets) del(key string) {
if item := b.items[key]; item != nil {
delete(b.items, key)
}
}
// read initializes the data from an on-disk page.
func (s *sys) read(p *page) {
s.pgid = p.id
s.buckets = make(map[string]*bucket)
func (b *buckets) read(p *page) {
b.pgid = p.id
b.items = make(map[string]*bucket)
var buckets []*bucket
var items []*bucket
var keys []string
// Read buckets.
// Read items.
nodes := (*[maxNodesPerPage]bucket)(unsafe.Pointer(&p.ptr))
for i := 0; i < int(p.count); i++ {
node := &nodes[i]
buckets = append(buckets, node)
items = append(items, node)
}
// Read keys.
@ -61,34 +61,33 @@ func (s *sys) read(p *page) {
buf = buf[size:]
}
// Associate keys and buckets.
// Associate keys and items.
for index, key := range keys {
b := &bucket{buckets[index].root}
s.buckets[key] = b
b.items[key] = &bucket{items[index].root}
}
}
// write writes the items onto a page.
func (s *sys) write(p *page) {
func (b *buckets) write(p *page) {
// Initialize page.
p.flags |= p_sys
p.count = uint16(len(s.buckets))
p.flags |= p_buckets
p.count = uint16(len(b.items))
// Sort keys.
var keys []string
for key, _ := range s.buckets {
for key, _ := range b.items {
keys = append(keys, key)
}
sort.StringSlice(keys).Sort()
// Write each bucket to the page.
buckets := (*[maxNodesPerPage]bucket)(unsafe.Pointer(&p.ptr))
items := (*[maxNodesPerPage]bucket)(unsafe.Pointer(&p.ptr))
for index, key := range keys {
buckets[index] = *s.buckets[key]
items[index] = *b.items[key]
}
// Write each key to the page.
buf := (*[maxAllocSize]byte)(unsafe.Pointer(&buckets[p.count]))[:]
buf := (*[maxAllocSize]byte)(unsafe.Pointer(&items[p.count]))[:]
for _, key := range keys {
buf[0] = byte(len(key))
buf = buf[1:]
@ -96,3 +95,13 @@ func (s *sys) write(p *page) {
buf = buf[len(key):]
}
}
// updateRoot finds a bucket by root id and then updates it to point to a new root.
func (b *buckets) updateRoot(oldid, newid pgid) {
for _, b := range b.items {
if b.root == oldid {
b.root = newid
return
}
}
}

70
buckets_test.go Normal file
View File

@ -0,0 +1,70 @@
package bolt
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
)
// Ensure that a buckets page can set a bucket.
func TestBucketsPut(t *testing.T) {
b := &buckets{items: make(map[string]*bucket)}
b.put("foo", &bucket{root: 2})
b.put("bar", &bucket{root: 3})
b.put("foo", &bucket{root: 4})
assert.Equal(t, len(b.items), 2)
assert.Equal(t, b.get("foo").root, pgid(4))
assert.Equal(t, b.get("bar").root, pgid(3))
assert.Nil(t, b.get("no_such_bucket"))
}
// Ensure that a buckets page can deserialize from a page.
func TestBucketsRead(t *testing.T) {
// Create a page.
var buf [4096]byte
page := (*page)(unsafe.Pointer(&buf[0]))
page.count = 2
// Insert 2 items at the beginning.
s := (*[3]bucket)(unsafe.Pointer(&page.ptr))
s[0] = bucket{root: 3}
s[1] = bucket{root: 4}
// Write data for the nodes at the end.
data := (*[4096]byte)(unsafe.Pointer(&s[2]))
data[0] = 3
copy(data[1:], []byte("bar"))
data[4] = 10
copy(data[5:], []byte("helloworld"))
// Deserialize page into a buckets page.
b := &buckets{items: make(map[string]*bucket)}
b.read(page)
// Check that there are two items with correct data.
assert.Equal(t, len(b.items), 2)
assert.Equal(t, b.get("bar").root, pgid(3))
assert.Equal(t, b.get("helloworld").root, pgid(4))
}
// Ensure that a buckets page can serialize itself.
func TestBucketsWrite(t *testing.T) {
b := &buckets{items: make(map[string]*bucket)}
b.put("foo", &bucket{root: 2})
b.put("bar", &bucket{root: 3})
// Write it to a page.
var buf [4096]byte
p := (*page)(unsafe.Pointer(&buf[0]))
b.write(p)
// Read the page back in.
b2 := &buckets{items: make(map[string]*bucket)}
b2.read(p)
// Check that the two pages are the same.
assert.Equal(t, len(b.items), 2)
assert.Equal(t, b.get("foo").root, pgid(2))
assert.Equal(t, b.get("bar").root, pgid(3))
}

13
db.go
View File

@ -156,7 +156,7 @@ func (db *DB) init() error {
m.pageSize = uint32(db.pageSize)
m.version = version
m.free = 2
m.sys = 3
m.buckets = 3
m.pgid = 4
m.txnid = txnid(i)
}
@ -170,7 +170,7 @@ func (db *DB) init() error {
// Write an empty leaf page at page 4.
p = db.pageInBuffer(buf[:], pgid(3))
p.id = pgid(3)
p.flags = p_sys
p.flags = p_buckets
p.count = 0
// Write the buffer to our data file.
@ -385,3 +385,12 @@ func (db *DB) Stat() *Stat {
// TODO: Calculate size, depth, page count (by type), entry count, readers, etc.
return nil
}
type Stat struct {
PageSize int
Depth int
BranchPageCount int
LeafPageCount int
OverflowPageCount int
EntryCount int
}

View File

@ -7,7 +7,7 @@ type meta struct {
version uint32
pageSize uint32
flags uint32
sys pgid
buckets pgid
free pgid
pgid pgid
txnid txnid
@ -31,7 +31,7 @@ func (m *meta) copy(dest *meta) {
dest.pgid = m.pgid
dest.free = m.free
dest.txnid = m.txnid
dest.sys = m.sys
dest.buckets = m.buckets
}
// write writes the meta onto a page.

View File

@ -19,7 +19,7 @@ const (
p_branch = 0x01
p_leaf = 0x02
p_meta = 0x04
p_sys = 0x08
p_buckets = 0x08
p_freelist = 0x10
)
@ -41,8 +41,8 @@ func (p *page) typ() string {
return "leaf"
} else if (p.flags & p_meta) != 0 {
return "meta"
} else if (p.flags & p_sys) != 0 {
return "system"
} else if (p.flags & p_buckets) != 0 {
return "buckets"
} else if (p.flags & p_freelist) != 0 {
return "freelist"
}

View File

@ -38,18 +38,17 @@ func (t *RWTransaction) CreateBucket(name string) error {
p := t.allocate(1)
p.flags = p_leaf
// Add bucket to system page.
t.sys.put(name, &bucket{root: p.id})
// Add bucket to buckets page.
t.buckets.put(name, &bucket{root: p.id})
return nil
}
// DropBucket deletes a bucket.
func (t *RWTransaction) DeleteBucket(name string) error {
// Remove from system page.
t.sys.del(name)
// Remove from buckets page.
t.buckets.del(name)
// TODO: Delete entry from system bucket.
// TODO: Free all pages.
// TODO: Remove cursor.
return nil
@ -105,9 +104,9 @@ func (t *RWTransaction) Commit() error {
// Spill data onto dirty pages.
t.spill()
// Spill system page.
p := t.allocate((t.sys.size() / t.db.pageSize) + 1)
t.sys.write(p)
// Spill buckets page.
p := t.allocate((t.buckets.size() / t.db.pageSize) + 1)
t.buckets.write(p)
// Write dirty pages to disk.
if err := t.write(); err != nil {
@ -115,7 +114,7 @@ func (t *RWTransaction) Commit() error {
}
// Update the meta.
t.meta.sys = p.id
t.meta.buckets = p.id
// Write meta to disk.
if err := t.writeMeta(); err != nil {
@ -223,12 +222,7 @@ func (t *RWTransaction) spill() {
// Update roots with new roots.
for _, root := range roots {
for _, b := range t.sys.buckets {
if b.root == root.pgid {
b.root = root.node.root().pgid
break
}
}
t.buckets.updateRoot(root.pgid, root.node.root().pgid)
}
}

10
stat.go
View File

@ -1,10 +0,0 @@
package bolt
type Stat struct {
PageSize int
Depth int
BranchPageCount int
LeafPageCount int
OverflowPageCount int
EntryCount int
}

View File

@ -1,70 +0,0 @@
package bolt
import (
"testing"
"unsafe"
"github.com/stretchr/testify/assert"
)
// Ensure that a system page can set a bucket.
func TestSysPut(t *testing.T) {
s := &sys{buckets: make(map[string]*bucket)}
s.put("foo", &bucket{root: 2})
s.put("bar", &bucket{root: 3})
s.put("foo", &bucket{root: 4})
assert.Equal(t, len(s.buckets), 2)
assert.Equal(t, s.get("foo").root, pgid(4))
assert.Equal(t, s.get("bar").root, pgid(3))
assert.Nil(t, s.get("no_such_bucket"))
}
// Ensure that a system page can deserialize from a page.
func TestSysRead(t *testing.T) {
// Create a page.
var buf [4096]byte
page := (*page)(unsafe.Pointer(&buf[0]))
page.count = 2
// Insert 2 buckets at the beginning.
buckets := (*[3]bucket)(unsafe.Pointer(&page.ptr))
buckets[0] = bucket{root: 3}
buckets[1] = bucket{root: 4}
// Write data for the nodes at the end.
data := (*[4096]byte)(unsafe.Pointer(&buckets[2]))
data[0] = 3
copy(data[1:], []byte("bar"))
data[4] = 10
copy(data[5:], []byte("helloworld"))
// Deserialize page into a system page.
s := &sys{buckets: make(map[string]*bucket)}
s.read(page)
// Check that there are two items with correct data.
assert.Equal(t, len(s.buckets), 2)
assert.Equal(t, s.get("bar").root, pgid(3))
assert.Equal(t, s.get("helloworld").root, pgid(4))
}
// Ensure that a system page can serialize itself.
func TestSysWrite(t *testing.T) {
s := &sys{buckets: make(map[string]*bucket)}
s.put("foo", &bucket{root: 2})
s.put("bar", &bucket{root: 3})
// Write it to a page.
var buf [4096]byte
p := (*page)(unsafe.Pointer(&buf[0]))
s.write(p)
// Read the page back in.
s2 := &sys{buckets: make(map[string]*bucket)}
s2.read(p)
// Check that the two pages are the same.
assert.Equal(t, len(s.buckets), 2)
assert.Equal(t, s.get("foo").root, pgid(2))
assert.Equal(t, s.get("bar").root, pgid(3))
}

View File

@ -13,7 +13,7 @@ type Transaction struct {
id int
db *DB
meta *meta
sys *sys
buckets *buckets
pages map[pgid]*page
}
@ -23,8 +23,8 @@ func (t *Transaction) init(db *DB) {
t.meta = db.meta()
t.pages = nil
t.sys = &sys{}
t.sys.read(t.page(t.meta.sys))
t.buckets = &buckets{}
t.buckets.read(t.page(t.meta.buckets))
}
func (t *Transaction) Close() {
@ -37,8 +37,7 @@ func (t *Transaction) DB() *DB {
// Bucket retrieves a bucket by name.
func (t *Transaction) Bucket(name string) *Bucket {
// Lookup bucket from the system page.
b := t.sys.get(name)
b := t.buckets.get(name)
if b == nil {
return nil
}