Merge pull request #7 from benbjohnson/delete

RWTransaction.Delete()
master
Ben Johnson 2014-02-05 21:50:15 -07:00
commit 4820312de2
9 changed files with 143 additions and 82 deletions

View File

@ -41,7 +41,6 @@ func (c *Cursor) Get(key []byte) []byte {
}
// If our target node isn't the same key as what's passed in then return nil.
// c.page().hexdump(512)
if !bytes.Equal(key, c.node().key()) {
return nil
}

View File

@ -1,14 +1,11 @@
package bolt
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"syscall"
"testing"
"testing/quick"
"time"
"unsafe"
@ -156,39 +153,18 @@ func TestDBPut(t *testing.T) {
})
}
// Ensure that a bucket can write random keys and values across multiple txns.
func TestDBPutRandom(t *testing.T) {
f := func(items testKeyValuePairs) bool {
withOpenDB(func(db *DB, path string) {
db.CreateBucket("widgets")
for _, item := range items {
if len(item.Key) == 0 {
continue
}
if err := db.Put("widgets", item.Key, item.Value); err != nil {
panic("put error: " + err.Error())
}
}
for _, item := range items {
if len(item.Key) == 0 {
continue
}
value, err := db.Get("widgets", item.Key)
if err != nil {
panic("get error: " + err.Error())
}
if !bytes.Equal(value, []byte(item.Value)) {
// db.CopyFile("/tmp/bolt.random.db")
t.Fatalf("value mismatch:\n%x\n%x", item.Value, value)
}
}
fmt.Fprint(os.Stderr, ".")
})
return true
}
if err := quick.Check(f, qc()); err != nil {
t.Error(err)
}
// Ensure that a bucket can delete an existing key.
func TestDBDelete(t *testing.T) {
withOpenDB(func(db *DB, path string) {
db.CreateBucket("widgets")
db.Put("widgets", []byte("foo"), []byte("bar"))
err := db.Delete("widgets", []byte("foo"))
assert.NoError(t, err)
value, err := db.Get("widgets", []byte("foo"))
if assert.NoError(t, err) {
assert.Nil(t, value)
}
})
}
// withDB executes a function with a database reference.

View File

@ -1,6 +1,6 @@
package bolt
const magic uint32 = 0xDEADC0DE
const magic uint32 = 0xED0CDAED
type meta struct {
magic uint32

26
node.go
View File

@ -61,6 +61,20 @@ func (n *node) put(oldKey, newKey, value []byte, pgid pgid) {
inode.pgid = pgid
}
// del removes a key from the node.
func (n *node) del(key []byte) {
// Find index of key.
index := sort.Search(len(n.inodes), func(i int) bool { return bytes.Compare(n.inodes[i].key, key) != -1 })
// Exit if the key isn't found.
if !bytes.Equal(n.inodes[index].key, key) {
return
}
// Delete inode from the node.
n.inodes = append(n.inodes[:index], n.inodes[index+1:]...)
}
// read initializes the node from a page.
func (n *node) read(p *page) {
n.pgid = p.id
@ -134,14 +148,16 @@ func (n *node) split(pageSize int) []*node {
threshold := pageSize / 2
// Group into smaller pages and target a given fill size.
size := 0
current := &node{isLeaf: n.isLeaf}
nodes := make([]*node, 0)
size := pageHeaderSize
inodes := n.inodes
current := n
current.inodes = nil
var nodes []*node
for i, inode := range n.inodes {
for i, inode := range inodes {
elemSize := n.pageElementSize() + len(inode.key) + len(inode.value)
if len(current.inodes) >= minKeysPerPage && i < len(n.inodes)-minKeysPerPage && size+elemSize > threshold {
if len(current.inodes) >= minKeysPerPage && i < len(inodes)-minKeysPerPage && size+elemSize > threshold {
size = pageHeaderSize
nodes = append(nodes, current)
current = &node{isLeaf: n.isLeaf}

View File

@ -91,7 +91,7 @@ func TestNodeSplit(t *testing.T) {
n.put([]byte("00000004"), []byte("00000004"), []byte("0123456701234567"), 0)
n.put([]byte("00000005"), []byte("00000005"), []byte("0123456701234567"), 0)
// Split between 3 & 4.
// Split between 2 & 3.
nodes := n.split(100)
assert.Equal(t, len(nodes), 2)

View File

@ -1,9 +1,13 @@
package bolt
import (
"bytes"
"flag"
"fmt"
"math/rand"
"os"
"reflect"
"testing"
"testing/quick"
"time"
)
@ -18,45 +22,78 @@ import (
// -quick.maxvsize The maximum size of a value.
//
var seed, testMaxItemCount, testMaxKeySize, testMaxValueSize int
var qseed, qmaxitems, qmaxksize, qmaxvsize int
func init() {
flag.IntVar(&seed, "quick.seed", int(time.Now().UnixNano())%100000, "")
flag.IntVar(&testMaxItemCount, "quick.maxitems", 1024, "")
flag.IntVar(&testMaxKeySize, "quick.maxksize", 1024, "")
flag.IntVar(&testMaxValueSize, "quick.maxvsize", 1024, "")
warn("seed:", seed)
flag.IntVar(&qseed, "quick.seed", int(time.Now().UnixNano())%100000, "")
flag.IntVar(&qmaxitems, "quick.maxitems", 1000, "")
flag.IntVar(&qmaxksize, "quick.maxksize", 1024, "")
flag.IntVar(&qmaxvsize, "quick.maxvsize", 1024, "")
flag.Parse()
warn("seed:", qseed)
}
// qc creates a testing/quick configuration.
func qc() *quick.Config {
return &quick.Config{Rand: rand.New(rand.NewSource(int64(seed)))}
// Ensure that a bucket can write random keys and values across multiple txns.
func TestQuickPut(t *testing.T) {
index := 0
f := func(items testdata) bool {
withOpenDB(func(db *DB, path string) {
m := make(map[string][]byte)
db.CreateBucket("widgets")
for _, item := range items {
if err := db.Put("widgets", item.Key, item.Value); err != nil {
panic("put error: " + err.Error())
}
m[string(item.Key)] = item.Value
// Verify all key/values so far.
i := 0
for k, v := range m {
value, err := db.Get("widgets", []byte(k))
if err != nil {
panic("get error: " + err.Error())
}
if !bytes.Equal(value, v) {
db.CopyFile("/tmp/bolt.random.db")
t.Fatalf("value mismatch [run %d] (%d of %d):\nkey: %x\ngot: %x\nexp: %x", index, i, len(m), []byte(k), v, value)
}
i++
}
}
fmt.Fprint(os.Stderr, ".")
})
index++
return true
}
if err := quick.Check(f, &quick.Config{Rand: rand.New(rand.NewSource(int64(qseed)))}); err != nil {
t.Error(err)
}
fmt.Fprint(os.Stderr, "\n")
}
type testKeyValuePairs []testKeyValuePair
type testdata []testdataitem
func (t testKeyValuePairs) Generate(rand *rand.Rand, size int) reflect.Value {
n := rand.Intn(testMaxItemCount-1) + 1
items := make(testKeyValuePairs, n)
func (t testdata) Generate(rand *rand.Rand, size int) reflect.Value {
n := rand.Intn(qmaxitems-1) + 1
items := make(testdata, n)
for i := 0; i < n; i++ {
items[i].Generate(rand, size)
item := &items[i]
item.Key = randByteSlice(rand, 1, qmaxksize)
item.Value = randByteSlice(rand, 0, qmaxvsize)
}
return reflect.ValueOf(items)
}
type testKeyValuePair struct {
type testdataitem struct {
Key []byte
Value []byte
}
func (t testKeyValuePair) Generate(rand *rand.Rand, size int) reflect.Value {
t.Key = randByteSlice(rand, 1, testMaxKeySize)
t.Value = randByteSlice(rand, 0, testMaxValueSize)
return reflect.ValueOf(t)
}
func randByteSlice(rand *rand.Rand, minSize, maxSize int) []byte {
n := rand.Intn(maxSize - minSize) + minSize
n := rand.Intn(maxSize-minSize) + minSize
b := make([]byte, n)
for i := 0; i < n; i++ {
b[i] = byte(rand.Intn(255))

View File

@ -70,19 +70,29 @@ func (t *RWTransaction) Put(name string, key []byte, value []byte) error {
return &Error{"data too large", nil}
}
// Insert a new node.
// Move cursor to correct position.
c := b.cursor()
c.Get(key)
// Insert the key/value.
t.node(c.stack).put(key, key, value, 0)
return nil
}
func (t *RWTransaction) Delete(name string, key []byte) error {
// TODO: Traverse to the correct node.
// TODO: If missing, exit.
// TODO: Remove node from page.
// TODO: If page is empty then add it to the freelist.
b := t.Bucket(name)
if b == nil {
return &Error{"bucket not found", nil}
}
// Move cursor to correct position.
c := b.cursor()
c.Get(key)
// Delete the node if we have a matching key.
t.node(c.stack).del(key)
return nil
}
@ -237,7 +247,10 @@ func (t *RWTransaction) write() error {
for _, p := range pages {
size := (int(p.overflow) + 1) * t.db.pageSize
buf := (*[maxAllocSize]byte)(unsafe.Pointer(p))[:size]
t.db.file.WriteAt(buf, int64(p.id)*int64(t.db.pageSize))
offset := int64(p.id) * int64(t.db.pageSize)
if _, err := t.db.file.WriteAt(buf, offset); err != nil {
return err
}
}
return nil

10
sys.go
View File

@ -25,16 +25,6 @@ func (s *sys) get(key string) *bucket {
return s.buckets[key]
}
// getByRoot retrieves a bucket by root page id.
func (s *sys) getByRoot(pgid pgid) *bucket {
for _, b := range s.buckets {
if b.root == pgid {
return b
}
}
panic("root not found")
}
// put sets a new value for a bucket.
func (s *sys) put(key string, b *bucket) {
s.buckets[key] = b

30
transaction_test.go Normal file
View File

@ -0,0 +1,30 @@
package bolt
import (
"testing"
"github.com/stretchr/testify/assert"
)
// Ensure that a Transaction can retrieve a bucket.
func TestTransactionBucketMissing(t *testing.T) {
withOpenDB(func(db *DB, path string) {
db.CreateBucket("widgets")
b, err := db.Bucket("widgets")
assert.NoError(t, err)
if assert.NotNil(t, b) {
assert.Equal(t, "widgets", b.Name())
}
})
}
// Ensure that a Transaction retrieving a non-existent key returns nil.
func TestTransactionGetMising(t *testing.T) {
withOpenDB(func(db *DB, path string) {
db.CreateBucket("widgets")
db.Put("widgets", []byte("foo"), []byte("bar"))
value, err := db.Get("widgets", []byte("no_such_key"))
assert.NoError(t, err)
assert.Nil(t, value)
})
}