Add tpage.put() test.

pull/34/head
Ben Johnson 2014-01-28 15:16:22 -05:00
parent 044d7b7893
commit a942c1d168
6 changed files with 64 additions and 29 deletions

View File

@ -145,7 +145,6 @@ func TestDBCorruptMeta0(t *testing.T) {
}) })
} }
//-------------------------------------- //--------------------------------------
// Transaction() // Transaction()
//-------------------------------------- //--------------------------------------

View File

@ -8,7 +8,7 @@ import (
// Only one read/write transaction can be active for a DB at a time. // Only one read/write transaction can be active for a DB at a time.
type RWTransaction struct { type RWTransaction struct {
Transaction Transaction
mpages map[pgid]*mpage tpages map[pgid]*tpage
} }
// TODO: Allocate scratch meta page. // TODO: Allocate scratch meta page.
@ -61,7 +61,7 @@ func (t *RWTransaction) CreateBucket(name string) error {
// Insert new node. // Insert new node.
c := t.sys.cursor() c := t.sys.cursor()
c.Goto([]byte(name)) c.Goto([]byte(name))
t.mpage(c.page().id).put([]byte(name), buf[:]) t.tpage(c.page().id).put([]byte(name), buf[:])
return nil return nil
} }
@ -104,7 +104,7 @@ func (t *RWTransaction) Put(name string, key []byte, value []byte) error {
// Insert a new node. // Insert a new node.
c := b.cursor() c := b.cursor()
c.Goto(key) c.Goto(key)
t.mpage(c.page().id).put(key, value) t.tpage(c.page().id).put(key, value)
return nil return nil
} }
@ -124,18 +124,18 @@ func (t *RWTransaction) allocate(size int) (*page, error) {
return nil, nil return nil, nil
} }
// mpage returns a deserialized leaf page. // tpage returns a deserialized leaf page.
func (t *RWTransaction) mpage(id pgid) *mpage { func (t *RWTransaction) tpage(id pgid) *tpage {
if t.mpages != nil { if t.tpages != nil {
if p := t.mpages[id]; p != nil { if p := t.tpages[id]; p != nil {
return p return p
} }
} }
// Read raw page and deserialize. // Read raw page and deserialize.
p := &mpage{} p := &tpage{}
p.read(t.page(id)) p.read(t.page(id))
t.mpages[id] = p t.tpages[id] = p
return p return p
} }

View File

@ -1,9 +1,8 @@
package bolt package bolt
type mnodes []mnode type tnodes []tnode
type mnode struct { type tnode struct {
key []byte key []byte
value []byte value []byte
} }

View File

@ -6,23 +6,25 @@ import (
"unsafe" "unsafe"
) )
// mpage represents a deserialized leaf page. // tpage represents a temporary, in-memory leaf page.
// It is deserialized from an memory-mapped page and is not restricted by page size. // It is deserialized from an memory-mapped page and is not restricted by page size.
type mpage struct { type tpage struct {
nodes mnodes nodes tnodes
} }
// allocator is a function that returns a set of contiguous pages. // allocator is a function that returns a set of contiguous pages.
type allocator func(count int) (*page, error) type allocator func(count int) (*page, error)
// put inserts or replaces a key on a leaf page. // put inserts or replaces a key on a leaf page.
func (p *mpage) put(key []byte, value []byte) { func (p *tpage) put(key []byte, value []byte) {
// Find insertion index. // Find insertion index.
index := sort.Search(len(p.nodes), func(i int) bool { return bytes.Compare(p.nodes[i].key, key) != -1 }) index := sort.Search(len(p.nodes), func(i int) bool { return bytes.Compare(p.nodes[i].key, key) != -1 })
// If there is no existing key then add a new node. // If there is no existing key then add a new node.
if len(p.nodes) == 0 || !bytes.Equal(p.nodes[index].key, key) { if index == len(p.nodes) {
p.nodes = append(p.nodes, mnode{}) p.nodes = append(p.nodes, tnode{})
} else if len(p.nodes) == 0 || !bytes.Equal(p.nodes[index].key, key) {
p.nodes = append(p.nodes, tnode{})
copy(p.nodes[index+1:], p.nodes[index:]) copy(p.nodes[index+1:], p.nodes[index:])
} }
p.nodes[index].key = key p.nodes[index].key = key
@ -30,8 +32,8 @@ func (p *mpage) put(key []byte, value []byte) {
} }
// read initializes the node data from an on-disk page. // read initializes the node data from an on-disk page.
func (p *mpage) read(page *page) { func (p *tpage) read(page *page) {
p.nodes = make(mnodes, page.count) p.nodes = make(tnodes, page.count)
lnodes := (*[maxNodesPerPage]lnode)(unsafe.Pointer(&page.ptr)) lnodes := (*[maxNodesPerPage]lnode)(unsafe.Pointer(&page.ptr))
for i := 0; i < int(page.count); i++ { for i := 0; i < int(page.count); i++ {
lnode := lnodes[i] lnode := lnodes[i]
@ -42,7 +44,7 @@ func (p *mpage) read(page *page) {
} }
// write writes the nodes onto one or more leaf pages. // write writes the nodes onto one or more leaf pages.
func (p *mpage) write(pageSize int, allocate allocator) ([]*page, error) { func (p *tpage) write(pageSize int, allocate allocator) ([]*page, error) {
var pages []*page var pages []*page
for _, nodes := range p.split(pageSize) { for _, nodes := range p.split(pageSize) {
@ -84,10 +86,10 @@ func (p *mpage) write(pageSize int, allocate allocator) ([]*page, error) {
} }
// split divides up the noes in the page into appropriately sized groups. // split divides up the noes in the page into appropriately sized groups.
func (p *mpage) split(pageSize int) []mnodes { func (p *tpage) split(pageSize int) []tnodes {
// If we only have enough nodes for one page then just return the nodes. // If we only have enough nodes for one page then just return the nodes.
if len(p.nodes) <= minKeysPerPage { if len(p.nodes) <= minKeysPerPage {
return []mnodes{p.nodes} return []tnodes{p.nodes}
} }
// If we're not larger than one page then just return the nodes. // If we're not larger than one page then just return the nodes.
@ -96,13 +98,13 @@ func (p *mpage) split(pageSize int) []mnodes {
totalSize += lnodeSize + len(node.key) + len(node.value) totalSize += lnodeSize + len(node.key) + len(node.value)
} }
if totalSize < pageSize { if totalSize < pageSize {
return []mnodes{p.nodes} return []tnodes{p.nodes}
} }
// Otherwise group into smaller pages and target a given fill size. // Otherwise group into smaller pages and target a given fill size.
var size int var size int
var group mnodes var group tnodes
var groups []mnodes var groups []tnodes
// Set fill threshold to 25%. // Set fill threshold to 25%.
threshold := pageSize >> 4 threshold := pageSize >> 4
@ -113,7 +115,7 @@ func (p *mpage) split(pageSize int) []mnodes {
// TODO(benbjohnson): Don't create a new group for just the last node. // TODO(benbjohnson): Don't create a new group for just the last node.
if group == nil || (len(group) > minKeysPerPage && size+nodeSize > threshold) { if group == nil || (len(group) > minKeysPerPage && size+nodeSize > threshold) {
size = pageHeaderSize size = pageHeaderSize
group = make(mnodes, 0) group = make(tnodes, 0)
groups = append(groups, group) groups = append(groups, group)
} }

35
tpage_test.go Normal file
View File

@ -0,0 +1,35 @@
package bolt
import (
"testing"
"github.com/stretchr/testify/assert"
)
// Ensure that a temporary page can insert a key/value.
func TestTpagePut(t *testing.T) {
p := &tpage{nodes: make(tnodes, 0)}
p.put([]byte("baz"), []byte("2"))
p.put([]byte("foo"), []byte("0"))
p.put([]byte("bar"), []byte("1"))
p.put([]byte("foo"), []byte("3"))
assert.Equal(t, len(p.nodes), 3)
assert.Equal(t, p.nodes[0], tnode{[]byte("bar"), []byte("1")})
assert.Equal(t, p.nodes[1], tnode{[]byte("baz"), []byte("2")})
assert.Equal(t, p.nodes[2], tnode{[]byte("foo"), []byte("3")})
}
// Ensure that a temporary page can deserialize from a page.
func TestTpageRead(t *testing.T) {
t.Skip("pending")
}
// Ensure that a temporary page can serialize itself.
func TestTpageWrite(t *testing.T) {
t.Skip("pending")
}
// Ensure that a temporary page can split into appropriate subgroups.
func TestTpageSplit(t *testing.T) {
t.Skip("pending")
}