From 0ed3dc3071d7ef0503f3fcbd015b63bbd6eae93e Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Wed, 5 Feb 2014 22:15:47 -0700 Subject: [PATCH] =?UTF-8?q?Rename=20sys=20=E2=98=9E=20buckets.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sys.go => buckets.go | 65 ++++++++++++++++++++++------------------ buckets_test.go | 70 ++++++++++++++++++++++++++++++++++++++++++++ db.go | 13 ++++++-- meta.go | 4 +-- page.go | 6 ++-- rwtransaction.go | 24 ++++++--------- stat.go | 10 ------- sys_test.go | 70 -------------------------------------------- transaction.go | 9 +++--- 9 files changed, 136 insertions(+), 135 deletions(-) rename sys.go => buckets.go (51%) create mode 100644 buckets_test.go delete mode 100644 stat.go delete mode 100644 sys_test.go diff --git a/sys.go b/buckets.go similarity index 51% rename from sys.go rename to buckets.go index ec1b858..59a82d8 100644 --- a/sys.go +++ b/buckets.go @@ -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 + } + } +} diff --git a/buckets_test.go b/buckets_test.go new file mode 100644 index 0000000..0fc6288 --- /dev/null +++ b/buckets_test.go @@ -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)) +} diff --git a/db.go b/db.go index 41fbc64..d62114a 100644 --- a/db.go +++ b/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 +} diff --git a/meta.go b/meta.go index 33f45d4..5c68fa7 100644 --- a/meta.go +++ b/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. diff --git a/page.go b/page.go index d05dce6..7c5a91c 100644 --- a/page.go +++ b/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" } diff --git a/rwtransaction.go b/rwtransaction.go index 93e544b..505d107 100644 --- a/rwtransaction.go +++ b/rwtransaction.go @@ -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) } } diff --git a/stat.go b/stat.go deleted file mode 100644 index b01fa99..0000000 --- a/stat.go +++ /dev/null @@ -1,10 +0,0 @@ -package bolt - -type Stat struct { - PageSize int - Depth int - BranchPageCount int - LeafPageCount int - OverflowPageCount int - EntryCount int -} diff --git a/sys_test.go b/sys_test.go deleted file mode 100644 index 0dcae66..0000000 --- a/sys_test.go +++ /dev/null @@ -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)) -} diff --git a/transaction.go b/transaction.go index 29337c6..5733e9e 100644 --- a/transaction.go +++ b/transaction.go @@ -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 }