mirror of https://github.com/etcd-io/bbolt.git
Rename sys ☞ buckets.
parent
4820312de2
commit
0ed3dc3071
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.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
|
||||||
|
}
|
||||||
|
|
4
meta.go
4
meta.go
|
@ -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.
|
||||||
|
|
6
page.go
6
page.go
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
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
|
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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue