diff --git a/mdbx/cursor.go b/mdbx/cursor.go index 6b31e80..4b9301d 100644 --- a/mdbx/cursor.go +++ b/mdbx/cursor.go @@ -157,10 +157,10 @@ func (c *Cursor) Get(setkey, setval []byte, op uint) (key, val []byte, err error key = setkey } else { if op != LastDup { - key = c.txn.bytes(c.txn.key) + key = castToBytes(c.txn.key) } } - val = c.txn.bytes(c.txn.val) + val = castToBytes(c.txn.val) // Clear transaction storage record storage area for future use and to // prevent dangling references. @@ -184,9 +184,13 @@ func (c *Cursor) getVal0(op uint) error { // // See mdb_cursor_get. func (c *Cursor) getVal1(setkey []byte, op uint) error { + var k *C.char + if len(setkey) > 0 { + k = (*C.char)(unsafe.Pointer(&setkey[0])) + } ret := C.mdbxgo_cursor_get1( c._c, - (*C.char)(unsafe.Pointer(&setkey[0])), C.size_t(len(setkey)), + k, C.size_t(len(setkey)), c.txn.key, c.txn.val, C.MDBX_cursor_op(op), @@ -199,36 +203,38 @@ func (c *Cursor) getVal1(setkey []byte, op uint) error { // // See mdb_cursor_get. func (c *Cursor) getVal2(setkey, setval []byte, op uint) error { + var k, v *C.char + if len(setkey) > 0 { + k = (*C.char)(unsafe.Pointer(&setkey[0])) + } + if len(setval) > 0 { + v = (*C.char)(unsafe.Pointer(&setval[0])) + } ret := C.mdbxgo_cursor_get2( c._c, - (*C.char)(unsafe.Pointer(&setkey[0])), C.size_t(len(setkey)), - (*C.char)(unsafe.Pointer(&setval[0])), C.size_t(len(setval)), + k, C.size_t(len(setkey)), + v, C.size_t(len(setval)), c.txn.key, c.txn.val, C.MDBX_cursor_op(op), ) return operrno("mdbx_cursor_get", ret) } -func (c *Cursor) putNilKey(flags uint) error { - ret := C.mdbxgo_cursor_put2(c._c, nil, 0, nil, 0, C.MDBX_put_flags_t(flags)) - return operrno("mdbx_cursor_put", ret) -} - // Put stores an item in the database. // // See mdb_cursor_put. func (c *Cursor) Put(key, val []byte, flags uint) error { - if len(key) == 0 { - return c.putNilKey(flags) + var k, v *C.char + if len(key) > 0 { + k = (*C.char)(unsafe.Pointer(&key[0])) } - vn := len(val) - if vn == 0 { - val = []byte{0} + if len(val) > 0 { + v = (*C.char)(unsafe.Pointer(&val[0])) } ret := C.mdbxgo_cursor_put2( c._c, - (*C.char)(unsafe.Pointer(&key[0])), C.size_t(len(key)), - (*C.char)(unsafe.Pointer(&val[0])), C.size_t(len(val)), + k, C.size_t(len(key)), + v, C.size_t(len(val)), C.MDBX_put_flags_t(flags), ) return operrno("mdbx_cursor_put", ret) @@ -238,14 +244,14 @@ func (c *Cursor) Put(key, val []byte, flags uint) error { // avoiding a memcopy. The returned byte slice is only valid in txn's thread, // before it has terminated. func (c *Cursor) PutReserve(key []byte, n int, flags uint) ([]byte, error) { - if len(key) == 0 { - return nil, c.putNilKey(flags) + var k *C.char + if len(key) > 0 { + k = (*C.char)(unsafe.Pointer(&key[0])) } - c.txn.val.iov_len = C.size_t(n) ret := C.mdbxgo_cursor_put1( c._c, - (*C.char)(unsafe.Pointer(&key[0])), C.size_t(len(key)), + k, C.size_t(len(key)), c.txn.val, C.MDBX_put_flags_t(flags|C.MDBX_RESERVE), ) @@ -254,7 +260,7 @@ func (c *Cursor) PutReserve(key []byte, n int, flags uint) ([]byte, error) { *c.txn.val = C.MDBX_val{} return nil, err } - b := getBytes(c.txn.val) + b := castToBytes(c.txn.val) *c.txn.val = C.MDBX_val{} return b, nil } @@ -265,18 +271,19 @@ func (c *Cursor) PutReserve(key []byte, n int, flags uint) ([]byte, error) { // // See mdb_cursor_put. func (c *Cursor) PutMulti(key []byte, page []byte, stride int, flags uint) error { - if len(key) == 0 { - return c.putNilKey(flags) + var k *C.char + if len(key) > 0 { + k = (*C.char)(unsafe.Pointer(&key[0])) } - if len(page) == 0 { - page = []byte{0} + var v *C.char + if len(page) > 0 { + v = (*C.char)(unsafe.Pointer(&page[0])) } - vn := WrapMulti(page, stride).Len() ret := C.mdbxgo_cursor_putmulti( c._c, - (*C.char)(unsafe.Pointer(&key[0])), C.size_t(len(key)), - (*C.char)(unsafe.Pointer(&page[0])), C.size_t(vn), C.size_t(stride), + k, C.size_t(len(key)), + v, C.size_t(vn), C.size_t(stride), C.MDBX_put_flags_t(flags|C.MDBX_MULTIPLE), ) return operrno("mdbxgo_cursor_putmulti", ret) diff --git a/mdbx/mdbx_test.go b/mdbx/mdbx_test.go index a958cd6..626ea69 100644 --- a/mdbx/mdbx_test.go +++ b/mdbx/mdbx_test.go @@ -1,10 +1,108 @@ package mdbx import ( + "bytes" "fmt" "testing" ) +func TestEmptyKeysAndValues(t *testing.T) { + env, err1 := NewEnv() + if err1 != nil { + t.Fatalf("Cannot create environment: %s", err1) + } + err1 = env.SetGeometry(-1, -1, 1024*1024, -1, -1, 4096) + if err1 != nil { + t.Fatalf("Cannot set mapsize: %s", err1) + } + path := t.TempDir() + err1 = env.Open(path, 0, 0664) + defer env.Close() + if err1 != nil { + t.Fatalf("Cannot open environment: %s", err1) + } + + var db DBI + numEntries := 4 + if err := env.Update(func(txn *Txn) (err error) { + db, err = txn.OpenRoot(0) + if err != nil { + panic(err) + } + + err = txn.Put(db, nil, []byte{}, NoOverwrite) + if err != nil { + panic(err) + } + err = txn.Put(db, []byte{}, []byte{}, NoOverwrite) + if err == nil { //expect err: MDBX_KEYEXIST + panic(err) + } + err = txn.Put(db, []byte{1}, []byte{}, NoOverwrite) + if err != nil { + panic(err) + } + err = txn.Put(db, []byte{2}, nil, NoOverwrite) + if err != nil { + panic(err) + } + err = txn.Put(db, []byte{3}, []byte{1}, NoOverwrite) + if err != nil { + panic(err) + } + return nil + }); err != nil { + t.Fatal(err) + } + + stat, err1 := env.Stat() + if err1 != nil { + t.Fatalf("Cannot get stat %s", err1) + } else if stat.Entries != uint64(numEntries) { + t.Errorf("Less entry in the database than expected: %d <> %d", stat.Entries, numEntries) + } + if err := env.View(func(txn *Txn) error { + v, err := txn.Get(db, nil) + if err != nil { + panic(err) + } + if !bytes.Equal(v, []byte{}) { + panic(fmt.Sprintf("%x", v)) + } + v, err = txn.Get(db, []byte{}) + if err != nil { + panic(err) + } + if !bytes.Equal(v, []byte{}) { + panic(fmt.Sprintf("%x", v)) + } + v, err = txn.Get(db, []byte{1}) + if err != nil { + panic(err) + } + if !bytes.Equal(v, []byte{}) { + panic(fmt.Sprintf("%x", v)) + } + v, err = txn.Get(db, []byte{2}) + if err != nil { + panic(err) + } + if !bytes.Equal(v, []byte{}) { + panic(fmt.Sprintf("%x", v)) + } + v, err = txn.Get(db, []byte{3}) + if err != nil { + panic(err) + } + if !bytes.Equal(v, []byte{1}) { + panic(fmt.Sprintf("%x", v)) + } + return nil + }); err != nil { + t.Fatal(err) + } +} + func TestTest1(t *testing.T) { env, err1 := NewEnv() if err1 != nil { diff --git a/mdbx/txn.go b/mdbx/txn.go index 1c13395..c0d9a3d 100644 --- a/mdbx/txn.go +++ b/mdbx/txn.go @@ -568,7 +568,7 @@ func (txn *Txn) subFlag(flags uint, fn TxnOp) error { } func (txn *Txn) bytes(val *C.MDBX_val) []byte { - return getBytes(val) + return castToBytes(val) } // Get retrieves items from database dbi. If txn.RawRead is true the slice @@ -577,10 +577,13 @@ func (txn *Txn) bytes(val *C.MDBX_val) []byte { // // See mdbx_get. func (txn *Txn) Get(dbi DBI, key []byte) ([]byte, error) { - kdata, kn := valBytes(key) + var k *C.char + if len(key) > 0 { + k = (*C.char)(unsafe.Pointer(&key[0])) + } ret := C.mdbxgo_get( txn._txn, C.MDBX_dbi(dbi), - (*C.char)(unsafe.Pointer(&kdata[0])), C.size_t(kn), + k, C.size_t(len(key)), txn.val, ) err := operrno("mdbx_get", ret) @@ -588,34 +591,26 @@ func (txn *Txn) Get(dbi DBI, key []byte) ([]byte, error) { *txn.val = C.MDBX_val{} return nil, err } - b := txn.bytes(txn.val) + b := castToBytes(txn.val) *txn.val = C.MDBX_val{} return b, nil } -func (txn *Txn) putNilKey(dbi DBI, flags uint) error { - // mdbx_put with an empty key will always fail - ret := C.mdbxgo_put2(txn._txn, C.MDBX_dbi(dbi), nil, 0, nil, 0, C.MDBX_put_flags_t(flags)) - return operrno("mdbx_put", ret) -} - // Put stores an item in database dbi. // // See mdbx_put. -func (txn *Txn) Put(dbi DBI, key []byte, val []byte, flags uint) error { - kn := len(key) - if kn == 0 { - return txn.putNilKey(dbi, flags) +func (txn *Txn) Put(dbi DBI, key, val []byte, flags uint) error { + var k, v *C.char + if len(key) > 0 { + k = (*C.char)(unsafe.Pointer(&key[0])) } - vn := len(val) - if vn == 0 { - val = []byte{0} + if len(val) > 0 { + v = (*C.char)(unsafe.Pointer(&val[0])) } - ret := C.mdbxgo_put2( txn._txn, C.MDBX_dbi(dbi), - (*C.char)(unsafe.Pointer(&key[0])), C.size_t(kn), - (*C.char)(unsafe.Pointer(&val[0])), C.size_t(vn), + k, C.size_t(len(key)), + v, C.size_t(len(val)), C.MDBX_put_flags_t(flags), ) return operrno("mdbx_put", ret) @@ -625,13 +620,14 @@ func (txn *Txn) Put(dbi DBI, key []byte, val []byte, flags uint) error { // avoiding a memcopy. The returned byte slice is only valid in txn's thread, // before it has terminated. func (txn *Txn) PutReserve(dbi DBI, key []byte, n int, flags uint) ([]byte, error) { - if len(key) == 0 { - return nil, txn.putNilKey(dbi, flags) - } txn.val.iov_len = C.size_t(n) + var k *C.char + if len(key) > 0 { + k = (*C.char)(unsafe.Pointer(&key[0])) + } ret := C.mdbxgo_put1( txn._txn, C.MDBX_dbi(dbi), - (*C.char)(unsafe.Pointer(&key[0])), C.size_t(len(key)), + k, C.size_t(len(key)), txn.val, C.MDBX_put_flags_t(flags|C.MDBX_RESERVE), ) @@ -640,7 +636,7 @@ func (txn *Txn) PutReserve(dbi DBI, key []byte, n int, flags uint) ([]byte, erro *txn.val = C.MDBX_val{} return nil, err } - b := getBytes(txn.val) + b := castToBytes(txn.val) *txn.val = C.MDBX_val{} return b, nil } @@ -650,20 +646,17 @@ func (txn *Txn) PutReserve(dbi DBI, key []byte, n int, flags uint) ([]byte, erro // // See mdbx_del. func (txn *Txn) Del(dbi DBI, key, val []byte) error { - kdata, kn := valBytes(key) - if val == nil { - ret := C.mdbxgo_del( - txn._txn, C.MDBX_dbi(dbi), - (*C.char)(unsafe.Pointer(&kdata[0])), C.size_t(kn), - nil, 0, - ) - return operrno("mdbx_del", ret) - } - vdata, vn := valBytes(val) + var k, v *C.char + if len(key) > 0 { + k = (*C.char)(unsafe.Pointer(&key[0])) + } + if len(val) > 0 { + v = (*C.char)(unsafe.Pointer(&val[0])) + } ret := C.mdbxgo_del( txn._txn, C.MDBX_dbi(dbi), - (*C.char)(unsafe.Pointer(&kdata[0])), C.size_t(kn), - (*C.char)(unsafe.Pointer(&vdata[0])), C.size_t(vn), + k, C.size_t(len(key)), + v, C.size_t(len(val)), ) return operrno("mdbx_del", ret) } @@ -697,12 +690,17 @@ type CmpFunc *C.MDBX_cmp_func // Cmp - this func follow bytes.Compare return style: The result will be 0 if a==b, -1 if a < b, and +1 if a > b. func (txn *Txn) Cmp(dbi DBI, a []byte, b []byte) int { - adata, an := valBytes(a) - bdata, bn := valBytes(b) + var adata, bdata *C.char + if len(a) > 0 { + adata = (*C.char)(unsafe.Pointer(&a[0])) + } + if len(b) > 0 { + bdata = (*C.char)(unsafe.Pointer(&b[0])) + } ret := int(C.mdbxgo_cmp( txn._txn, C.MDBX_dbi(dbi), - (*C.char)(unsafe.Pointer(&adata[0])), C.size_t(an), - (*C.char)(unsafe.Pointer(&bdata[0])), C.size_t(bn), + adata, C.size_t(len(a)), + bdata, C.size_t(len(b)), )) if ret > 0 { return 1 @@ -715,12 +713,17 @@ func (txn *Txn) Cmp(dbi DBI, a []byte, b []byte) int { // DCmp - this func follow bytes.Compare return style: The result will be 0 if a==b, -1 if a < b, and +1 if a > b. func (txn *Txn) DCmp(dbi DBI, a []byte, b []byte) int { - adata, an := valBytes(a) - bdata, bn := valBytes(b) + var adata, bdata *C.char + if len(a) > 0 { + adata = (*C.char)(unsafe.Pointer(&a[0])) + } + if len(b) > 0 { + bdata = (*C.char)(unsafe.Pointer(&b[0])) + } ret := int(C.mdbxgo_dcmp( txn._txn, C.MDBX_dbi(dbi), - (*C.char)(unsafe.Pointer(&adata[0])), C.size_t(an), - (*C.char)(unsafe.Pointer(&bdata[0])), C.size_t(bn), + adata, C.size_t(len(a)), + bdata, C.size_t(len(b)), )) if ret > 0 { return 1 diff --git a/mdbx/val.go b/mdbx/val.go index 54b7c7c..40847d4 100644 --- a/mdbx/val.go +++ b/mdbx/val.go @@ -25,8 +25,8 @@ import ( // source file malloc.go and the compiler typecheck.go for more information // about memory limits and array bound limits. // -// https://github.com/golang/go/blob/a03bdc3e6bea34abd5077205371e6fb9ef354481/src/runtime/malloc.go#L151-L164 -// https://github.com/golang/go/blob/36a80c5941ec36d9c44d6f3c068d13201e023b5f/src/cmd/compile/internal/gc/typecheck.go#L383 +// https://github.com/golang/go/blob/a03bdc3e6bea34abd5077205371e6fb9ef354481/src/runtime/malloc.go#L151-L164 +// https://github.com/golang/go/blob/36a80c5941ec36d9c44d6f3c068d13201e023b5f/src/cmd/compile/internal/gc/typecheck.go#L383 // // On 64-bit systems, luckily, the value 2^32-1 coincides with the maximum data // size for LMDB (MAXDATASIZE). @@ -48,9 +48,9 @@ type Multi struct { // WrapMulti converts a page of contiguous values with stride size into a // Multi. WrapMulti panics if len(page) is not a multiple of stride. // -// _, val, _ := cursor.Get(nil, nil, lmdb.FirstDup) -// _, page, _ := cursor.Get(nil, nil, lmdb.GetMultiple) -// multi := lmdb.WrapMulti(page, len(val)) +// _, val, _ := cursor.Get(nil, nil, lmdb.FirstDup) +// _, page, _ := cursor.Get(nil, nil, lmdb.GetMultiple) +// multi := lmdb.WrapMulti(page, len(val)) // // See mdb_cursor_get and MDB_GET_MULTIPLE. func WrapMulti(page []byte, stride int) *Multi { @@ -89,8 +89,7 @@ func (m *Multi) Stride() int { // Size returns the total size of the Multi data and is equal to // -// m.Len()*m.Stride() -// +// m.Len()*m.Stride() func (m *Multi) Size() int { return len(m.page) } @@ -101,27 +100,17 @@ func (m *Multi) Page() []byte { return m.page[:len(m.page):len(m.page)] } -var eb = []byte{0} - -func valBytes(b []byte) ([]byte, int) { - if len(b) == 0 { - return eb, 0 - } - return b, len(b) -} - func wrapVal(b []byte) *C.MDBX_val { - p, n := valBytes(b) + var v unsafe.Pointer + if len(b) > 0 { + v = unsafe.Pointer(&b[0]) + } return &C.MDBX_val{ - iov_base: unsafe.Pointer(&p[0]), - iov_len: C.size_t(n), + iov_base: v, + iov_len: C.size_t(len(b)), } } -func getBytes(val *C.MDBX_val) []byte { +func castToBytes(val *C.MDBX_val) []byte { return (*[valMaxSize]byte)(val.iov_base)[:val.iov_len:val.iov_len] } - -func getBytesCopy(val *C.MDBX_val) []byte { - return C.GoBytes(val.iov_base, C.int(val.iov_len)) -} diff --git a/mdbx/val_test.go b/mdbx/val_test.go index dac4560..8999101 100644 --- a/mdbx/val_test.go +++ b/mdbx/val_test.go @@ -44,42 +44,15 @@ func TestMultiVal_panic(t *testing.T) { WrapMulti([]byte("123"), 2) } -func TestValBytes(t *testing.T) { - ptr, n := valBytes(nil) - if len(ptr) == 0 { - t.Errorf("unexpected unaddressable slice") - } - if n != 0 { - t.Errorf("unexpected length: %d (expected 0)", n) - } - - b := []byte("abc") - ptr, n = valBytes(b) - if len(ptr) == 0 { - t.Errorf("unexpected unaddressable slice") - } - if n != 3 { - t.Errorf("unexpected length: %d (expected %d)", n, len(b)) - } -} - func TestVal(t *testing.T) { orig := []byte("hey hey") val := wrapVal(orig) - p := getBytes(val) + p := castToBytes(val) if !bytes.Equal(p, orig) { - t.Errorf("getBytes() not the same as original data: %q", p) + t.Errorf("castToBytes() not the same as original data: %q", p) } if &p[0] != &orig[0] { - t.Errorf("getBytes() is not the same slice as original") - } - - p = getBytesCopy(val) - if !bytes.Equal(p, orig) { - t.Errorf("getBytesCopy() not the same as original data: %q", p) - } - if &p[0] == &orig[0] { - t.Errorf("getBytesCopy() overlaps with orignal slice") + t.Errorf("castToBytes() is not the same slice as original") } }