mirror of
https://github.com/etcd-io/bbolt.git
synced 2025-04-27 21:23:15 +00:00
build c/cursor and running tests
This commit is contained in:
parent
4e01c9fd81
commit
b178373351
10
bucket.go
10
bucket.go
@ -63,6 +63,16 @@ func newBucket(tx *Tx) Bucket {
|
|||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tx returns the tx of the bucket.
|
||||||
|
func (b *Bucket) Tx() *Tx {
|
||||||
|
return b.tx
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root returns the root of the bucket.
|
||||||
|
func (b *Bucket) Root() pgid {
|
||||||
|
return b.root
|
||||||
|
}
|
||||||
|
|
||||||
// Writable returns whether the bucket is writable.
|
// Writable returns whether the bucket is writable.
|
||||||
func (b *Bucket) Writable() bool {
|
func (b *Bucket) Writable() bool {
|
||||||
return b.tx.writable
|
return b.tx.writable
|
||||||
|
19
c/cursor.go
19
c/cursor.go
@ -135,26 +135,29 @@ int bolt_cursor_next(bolt_cursor *c, bolt_val *key, bolt_val *value) {
|
|||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import "github.com/boltdb/bolt"
|
import (
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/boltdb/bolt"
|
||||||
|
)
|
||||||
|
|
||||||
type bolt_cursor *C.bolt_cursor
|
type bolt_cursor *C.bolt_cursor
|
||||||
|
|
||||||
func NewCursor(b *bolt.Bucket) bolt_cursor {
|
func NewCursor(b *bolt.Bucket) bolt_cursor {
|
||||||
data := (*C.void)(&b.tx.db.data[0])
|
data, pgsz := b.Tx().DB().RawData()
|
||||||
pgsz := (C.size_t)(b.tx.db.pageSize)
|
|
||||||
cursor := new(C.bolt_cursor)
|
cursor := new(C.bolt_cursor)
|
||||||
C.bolt_cursor_init(cursor, data, pgsz, (C.pgid)(b.root))
|
C.bolt_cursor_init(cursor, unsafe.Pointer(&data[0]), (C.size_t)(pgsz), (C.pgid)(b.Root()))
|
||||||
return cursor
|
return cursor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c bolt_cursor) first() (key, value []byte) {
|
func first(c bolt_cursor) (key, value []byte) {
|
||||||
var k, v C.bolt_val
|
var k, v C.bolt_val
|
||||||
C.bolt_cursor_first(c, &k, &v)
|
C.bolt_cursor_first(c, &k, &v)
|
||||||
return C.GoBytes(k.data, k.size), C.GoBytes(v.data, v.size)
|
return C.GoBytes(k.data, C.int(k.size)), C.GoBytes(v.data, C.int(v.size))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c bolt_cursor) next() (key, value []byte) {
|
func next(c bolt_cursor) (key, value []byte) {
|
||||||
var k, v C.bolt_val
|
var k, v C.bolt_val
|
||||||
C.bolt_cursor_next(c, &k, &v)
|
C.bolt_cursor_next(c, &k, &v)
|
||||||
return C.GoBytes(k.data, k.size), C.GoBytes(v.data, v.size)
|
return C.GoBytes(k.data, C.int(k.size)), C.GoBytes(v.data, C.int(v.size))
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,20 @@
|
|||||||
package c
|
package c
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/boltdb/bolt"
|
"fmt"
|
||||||
"github.com/stretchr/testify/assert"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/quick"
|
"testing/quick"
|
||||||
|
|
||||||
|
"github.com/boltdb/bolt"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Test when cursor hits the end
|
||||||
|
// Implement seek; binary search within the page (branch page and element page)
|
||||||
|
|
||||||
// Ensure that a cursor can iterate over all elements in a bucket.
|
// Ensure that a cursor can iterate over all elements in a bucket.
|
||||||
func TestIterate(t *testing.T) {
|
func TestIterate(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
@ -14,7 +22,7 @@ func TestIterate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
f := func(items testdata) bool {
|
f := func(items testdata) bool {
|
||||||
withOpenDB(func(db *DB, path string) {
|
withOpenDB(func(db *bolt.DB, path string) {
|
||||||
// Bulk insert all values.
|
// Bulk insert all values.
|
||||||
tx, _ := db.Begin(true)
|
tx, _ := db.Begin(true)
|
||||||
tx.CreateBucket("widgets")
|
tx.CreateBucket("widgets")
|
||||||
@ -31,7 +39,7 @@ func TestIterate(t *testing.T) {
|
|||||||
var index = 0
|
var index = 0
|
||||||
tx, _ = db.Begin(false)
|
tx, _ = db.Begin(false)
|
||||||
c := NewCursor(tx.Bucket("widgets"))
|
c := NewCursor(tx.Bucket("widgets"))
|
||||||
for key, value := c.first(); key != nil && index < len(items); key, value = c.next() {
|
for key, value := first(c); key != nil && index < len(items); key, value = next(c) {
|
||||||
assert.Equal(t, key, items[index].Key)
|
assert.Equal(t, key, items[index].Key)
|
||||||
assert.Equal(t, value, items[index].Value)
|
assert.Equal(t, value, items[index].Value)
|
||||||
index++
|
index++
|
||||||
@ -47,3 +55,58 @@ func TestIterate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
fmt.Fprint(os.Stderr, "\n")
|
fmt.Fprint(os.Stderr, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// withTempPath executes a function with a database reference.
|
||||||
|
func withTempPath(fn func(string)) {
|
||||||
|
f, _ := ioutil.TempFile("", "bolt-")
|
||||||
|
path := f.Name()
|
||||||
|
f.Close()
|
||||||
|
os.Remove(path)
|
||||||
|
defer os.RemoveAll(path)
|
||||||
|
|
||||||
|
fn(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// withOpenDB executes a function with an already opened database.
|
||||||
|
func withOpenDB(fn func(*bolt.DB, string)) {
|
||||||
|
withTempPath(func(path string) {
|
||||||
|
db, err := bolt.Open(path, 0666)
|
||||||
|
if err != nil {
|
||||||
|
panic("cannot open db: " + err.Error())
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
fn(db, path)
|
||||||
|
|
||||||
|
// Log statistics.
|
||||||
|
// if *statsFlag {
|
||||||
|
// logStats(db)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Check database consistency after every test.
|
||||||
|
mustCheck(db)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// mustCheck runs a consistency check on the database and panics if any errors are found.
|
||||||
|
func mustCheck(db *bolt.DB) {
|
||||||
|
if err := db.Check(); err != nil {
|
||||||
|
// Copy db off first.
|
||||||
|
db.CopyFile("/tmp/check.db", 0600)
|
||||||
|
|
||||||
|
if errors, ok := err.(bolt.ErrorList); ok {
|
||||||
|
for _, err := range errors {
|
||||||
|
warn(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
warn(err)
|
||||||
|
panic("check failure: see /tmp/check.db")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func warn(v ...interface{}) {
|
||||||
|
fmt.Fprintln(os.Stderr, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func warnf(msg string, v ...interface{}) {
|
||||||
|
fmt.Fprintf(os.Stderr, msg+"\n", v...)
|
||||||
|
}
|
||||||
|
77
c/quick_test.go
Normal file
77
c/quick_test.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package c
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"math/rand"
|
||||||
|
"reflect"
|
||||||
|
"testing/quick"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// testing/quick defaults to 5 iterations and a random seed.
|
||||||
|
// You can override these settings from the command line:
|
||||||
|
//
|
||||||
|
// -quick.count The number of iterations to perform.
|
||||||
|
// -quick.seed The seed to use for randomizing.
|
||||||
|
// -quick.maxitems The maximum number of items to insert into a DB.
|
||||||
|
// -quick.maxksize The maximum size of a key.
|
||||||
|
// -quick.maxvsize The maximum size of a value.
|
||||||
|
//
|
||||||
|
|
||||||
|
var qcount, qseed, qmaxitems, qmaxksize, qmaxvsize int
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.IntVar(&qcount, "quick.count", 5, "")
|
||||||
|
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)
|
||||||
|
warnf("quick settings: count=%v, items=%v, ksize=%v, vsize=%v", qcount, qmaxitems, qmaxksize, qmaxvsize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func qconfig() *quick.Config {
|
||||||
|
return &quick.Config{
|
||||||
|
MaxCount: qcount,
|
||||||
|
Rand: rand.New(rand.NewSource(int64(qseed))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testdata []testdataitem
|
||||||
|
|
||||||
|
func (t testdata) Len() int { return len(t) }
|
||||||
|
func (t testdata) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||||
|
func (t testdata) Less(i, j int) bool { return bytes.Compare(t[i].Key, t[j].Key) == -1 }
|
||||||
|
|
||||||
|
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++ {
|
||||||
|
item := &items[i]
|
||||||
|
item.Key = randByteSlice(rand, 1, qmaxksize)
|
||||||
|
item.Value = randByteSlice(rand, 0, qmaxvsize)
|
||||||
|
}
|
||||||
|
return reflect.ValueOf(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
type revtestdata []testdataitem
|
||||||
|
|
||||||
|
func (t revtestdata) Len() int { return len(t) }
|
||||||
|
func (t revtestdata) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||||
|
func (t revtestdata) Less(i, j int) bool { return bytes.Compare(t[i].Key, t[j].Key) == 1 }
|
||||||
|
|
||||||
|
type testdataitem struct {
|
||||||
|
Key []byte
|
||||||
|
Value []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func randByteSlice(rand *rand.Rand, minSize, maxSize int) []byte {
|
||||||
|
n := rand.Intn(maxSize-minSize) + minSize
|
||||||
|
b := make([]byte, n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
b[i] = byte(rand.Intn(255))
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
6
db.go
6
db.go
@ -578,6 +578,12 @@ func (db *DB) checkBucket(b *Bucket, reachable map[pgid]*page, errors *ErrorList
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is for internal access to the raw data bytes from the C cursor, use
|
||||||
|
// carefully, or not at all.
|
||||||
|
func (db *DB) RawData() ([]byte, int) {
|
||||||
|
return db.data, db.pageSize
|
||||||
|
}
|
||||||
|
|
||||||
// page retrieves a page reference from the mmap based on the current page size.
|
// page retrieves a page reference from the mmap based on the current page size.
|
||||||
func (db *DB) page(id pgid) *page {
|
func (db *DB) page(id pgid) *page {
|
||||||
pos := id * pgid(db.pageSize)
|
pos := id * pgid(db.pageSize)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user