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" "unsafe"
) )
// sys represents a in-memory system page. // buckets represents a in-memory buckets page.
type sys struct { type buckets struct {
pgid pgid pgid pgid
buckets map[string]*bucket items map[string]*bucket
} }
// size returns the size of the page after serialization. // size returns the size of the page after serialization.
func (s *sys) size() int { func (b *buckets) size() int {
var size int = pageHeaderSize var size int = pageHeaderSize
for key, _ := range s.buckets { for key, _ := range b.items {
size += int(unsafe.Sizeof(bucket{})) + len(key) size += int(unsafe.Sizeof(bucket{})) + len(key)
} }
return size return size
} }
// get retrieves a bucket by name. // get retrieves a bucket by name.
func (s *sys) get(key string) *bucket { func (b *buckets) get(key string) *bucket {
return s.buckets[key] return b.items[key]
} }
// put sets a new value for a bucket. // put sets a new value for a bucket.
func (s *sys) put(key string, b *bucket) { func (b *buckets) put(key string, item *bucket) {
s.buckets[key] = b b.items[key] = item
} }
// del deletes a bucket by name. // del deletes a bucket by name.
func (s *sys) del(key string) { func (b *buckets) del(key string) {
if b := s.buckets[key]; b != nil { if item := b.items[key]; item != nil {
delete(s.buckets, key) delete(b.items, key)
} }
} }
// read initializes the data from an on-disk page. // read initializes the data from an on-disk page.
func (s *sys) read(p *page) { func (b *buckets) read(p *page) {
s.pgid = p.id b.pgid = p.id
s.buckets = make(map[string]*bucket) b.items = make(map[string]*bucket)
var buckets []*bucket var items []*bucket
var keys []string var keys []string
// Read buckets. // Read items.
nodes := (*[maxNodesPerPage]bucket)(unsafe.Pointer(&p.ptr)) nodes := (*[maxNodesPerPage]bucket)(unsafe.Pointer(&p.ptr))
for i := 0; i < int(p.count); i++ { for i := 0; i < int(p.count); i++ {
node := &nodes[i] node := &nodes[i]
buckets = append(buckets, node) items = append(items, node)
} }
// Read keys. // Read keys.
@ -61,34 +61,33 @@ func (s *sys) read(p *page) {
buf = buf[size:] buf = buf[size:]
} }
// Associate keys and buckets. // Associate keys and items.
for index, key := range keys { for index, key := range keys {
b := &bucket{buckets[index].root} b.items[key] = &bucket{items[index].root}
s.buckets[key] = b
} }
} }
// write writes the items onto a page. // write writes the items onto a page.
func (s *sys) write(p *page) { func (b *buckets) write(p *page) {
// Initialize page. // Initialize page.
p.flags |= p_sys p.flags |= p_buckets
p.count = uint16(len(s.buckets)) p.count = uint16(len(b.items))
// Sort keys. // Sort keys.
var keys []string var keys []string
for key, _ := range s.buckets { for key, _ := range b.items {
keys = append(keys, key) keys = append(keys, key)
} }
sort.StringSlice(keys).Sort() sort.StringSlice(keys).Sort()
// Write each bucket to the page. // 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 { for index, key := range keys {
buckets[index] = *s.buckets[key] items[index] = *b.items[key]
} }
// Write each key to the page. // 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 { for _, key := range keys {
buf[0] = byte(len(key)) buf[0] = byte(len(key))
buf = buf[1:] buf = buf[1:]
@ -96,3 +95,13 @@ func (s *sys) write(p *page) {
buf = buf[len(key):] 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.pageSize = uint32(db.pageSize)
m.version = version m.version = version
m.free = 2 m.free = 2
m.sys = 3 m.buckets = 3
m.pgid = 4 m.pgid = 4
m.txnid = txnid(i) m.txnid = txnid(i)
} }
@ -170,7 +170,7 @@ func (db *DB) init() error {
// 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(3) p.id = pgid(3)
p.flags = p_sys p.flags = p_buckets
p.count = 0 p.count = 0
// Write the buffer to our data file. // 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. // TODO: Calculate size, depth, page count (by type), entry count, readers, etc.
return nil 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 version uint32
pageSize uint32 pageSize uint32
flags uint32 flags uint32
sys pgid buckets pgid
free pgid free pgid
pgid pgid pgid pgid
txnid txnid txnid txnid
@ -31,7 +31,7 @@ func (m *meta) copy(dest *meta) {
dest.pgid = m.pgid dest.pgid = m.pgid
dest.free = m.free dest.free = m.free
dest.txnid = m.txnid dest.txnid = m.txnid
dest.sys = m.sys dest.buckets = m.buckets
} }
// write writes the meta onto a page. // write writes the meta onto a page.

View File

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

View File

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

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