mirror of https://github.com/etcd-io/bbolt.git
Rename sys ☞ buckets.
parent
4820312de2
commit
0ed3dc3071
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
13
db.go
|
@ -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
|
||||
}
|
||||
|
|
4
meta.go
4
meta.go
|
@ -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.
|
||||
|
|
6
page.go
6
page.go
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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
10
stat.go
|
@ -1,10 +0,0 @@
|
|||
package bolt
|
||||
|
||||
type Stat struct {
|
||||
PageSize int
|
||||
Depth int
|
||||
BranchPageCount int
|
||||
LeafPageCount int
|
||||
OverflowPageCount int
|
||||
EntryCount int
|
||||
}
|
70
sys_test.go
70
sys_test.go
|
@ -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))
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue