Skip to content

Commit

Permalink
(v27) support empty key/values: 1. as valid dupsort key/values 2. as …
Browse files Browse the repository at this point in the history
…valid Set/SetRange/GetBoth/GetBothRange arguments (#124)
  • Loading branch information
AskAlexSharov authored Nov 1, 2023
1 parent 9206bf3 commit e364f0a
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 1 deletion.
18 changes: 17 additions & 1 deletion mdbx/cursor.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,10 @@ func (c *Cursor) DBI() DBI {
// See mdb_cursor_get.
func (c *Cursor) Get(setkey, setval []byte, op uint) (key, val []byte, err error) {
switch {
case len(setkey) == 0:
case len(setkey) == 0 && len(setval) == 0:
err = c.getVal0(op)
case len(setkey) == 0:
err = c.getVal01(setval, op)
case len(setval) == 0:
err = c.getVal1(setkey, op)
default:
Expand Down Expand Up @@ -209,6 +211,20 @@ func (c *Cursor) getVal1(setkey []byte, op uint) error {
)
return operrno("mdbx_cursor_get", ret)
}
func (c *Cursor) getVal01(setval []byte, op uint) error {
var v *C.char
if len(setval) > 0 {
v = (*C.char)(unsafe.Pointer(&setval[0]))
}
ret := C.mdbxgo_cursor_get01(
c._c,
v, C.size_t(len(setval)),
c.txn.key,
c.txn.val,
C.MDBX_cursor_op(op),
)
return operrno("mdbx_cursor_get", ret)
}

// getVal2 retrieves items from the database using key and value data for
// reference (GetBoth, GetBothRange, etc).
Expand Down
127 changes: 127 additions & 0 deletions mdbx/cursor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,133 @@ func TestCursor_Del(t *testing.T) {
}
}

func TestDupCursor_EmptyKeyValues(t *testing.T) {
env := setup(t)

var db DBI
err := env.Update(func(txn *Txn) (err error) {
db, err = txn.OpenDBI("testingdup", Create|DupSort, nil, nil)
if err != nil {
return err
}
cur, err := txn.OpenCursor(db)
if err != nil {
return err
}
defer cur.Close()

// empty value - must function as valid dupsort value
if err = txn.Put(db, []byte{1}, []byte{}, 0); err != nil {
panic(err)
}
if err = txn.Put(db, []byte{1}, []byte{8}, 0); err != nil {
panic(err)
}

_, v, err := cur.Get([]byte{1}, []byte{}, GetBothRange)
if err != nil {
panic(err)
}
if !bytes.Equal(v, []byte{}) {
panic(v)
}
_, v, err = cur.Get([]byte{1}, []byte{0}, GetBothRange)
if err != nil {
panic(err)
}
if !bytes.Equal(v, []byte{8}) {
panic(v)
}
_, v, err = cur.Get([]byte{}, []byte{0}, GetBoth)
if err == nil {
panic("expecting 'not found' error")
}
if v != nil {
panic(v)
}

// can use empty key as valid key in non-dupsort operations
k, v, err := cur.Get([]byte{}, nil, SetRange)
if err != nil {
panic(err)
}
if k == nil {
panic("nil")
}
if !bytes.Equal(k, []byte{1}) {
panic(fmt.Sprintf("%x", k))
}
if !bytes.Equal(v, []byte{}) {
panic(fmt.Sprintf("%x", v))
}
k, v, err = cur.Get([]byte{}, nil, Set)
if err == nil {
panic("expected 'not found' error")
}
if k != nil {
panic("nil")
}

// empty key - must function as valid dupsort key
if err = txn.Put(db, []byte{}, []byte{}, 0); err != nil {
panic(err)
}
if err = txn.Put(db, []byte{}, []byte{2}, 0); err != nil {
panic(err)
}
_, v, err = cur.Get([]byte{}, []byte{}, GetBothRange)
if err != nil {
panic(err)
}
if !bytes.Equal(v, []byte{}) {
panic(v)
}
_, v, err = cur.Get([]byte{}, []byte{0}, GetBothRange)
if err != nil {
panic(err)
}
if !bytes.Equal(v, []byte{2}) {
panic(v)
}
_, v, err = cur.Get([]byte{}, []byte{0}, GetBoth)
if err == nil {
panic("expecting 'not found' error ")
}
if v != nil {
panic(v)
}

// non-existing key
_, v, err = cur.Get([]byte{7}, []byte{}, GetBoth)
if err == nil {
panic("expecting 'not found' error")
}
if v != nil {
panic(v)
}

// sub-db doesn't have empty value, but we must be able to search from it
if err = txn.Put(db, []byte{2}, []byte{1}, 0); err != nil {
return err
}
if err = txn.Put(db, []byte{2}, []byte{3}, 0); err != nil {
return err
}
_, v, err = cur.Get([]byte{2}, []byte{}, GetBothRange)
if err != nil {
panic(err)
}
if !bytes.Equal(v, []byte{1}) {
panic(v)
}

return nil
})
if err != nil {
t.Error(err)
}
}

// This test verifies the behavior of Cursor.Count when DupSort is provided.
func TestCursor_Count_DupSort(t *testing.T) {
env := setup(t)
Expand Down
5 changes: 5 additions & 0 deletions mdbx/mdbxgo.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ int mdbxgo_cursor_putmulti(MDBX_cursor *cur, char *kdata, size_t kn, char *vdata
return mdbx_cursor_put(cur, &key, &val[0], flags);
}

int mdbxgo_cursor_get01(MDBX_cursor *cur, char *vdata, size_t vn, MDBX_val *key, MDBX_val *val, MDBX_cursor_op op) {
MDBXGO_SET_VAL(val, vn, vdata);
return mdbx_cursor_get(cur, key, val, op);
}

int mdbxgo_cursor_get1(MDBX_cursor *cur, char *kdata, size_t kn, MDBX_val *key, MDBX_val *val, MDBX_cursor_op op) {
MDBXGO_SET_VAL(key, kn, kdata);
return mdbx_cursor_get(cur, key, val, op);
Expand Down
1 change: 1 addition & 0 deletions mdbx/mdbxgo.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ int mdbxgo_put2(MDBX_txn *txn, MDBX_dbi dbi, char *kdata, size_t kn, char *vdata
int mdbxgo_cursor_put1(MDBX_cursor *cur, char *kdata, size_t kn, MDBX_val *val, MDBX_put_flags_t flags);
int mdbxgo_cursor_put2(MDBX_cursor *cur, char *kdata, size_t kn, char *vdata, size_t vn, MDBX_put_flags_t flags);
int mdbxgo_cursor_putmulti(MDBX_cursor *cur, char *kdata, size_t kn, char *vdata, size_t vn, size_t vstride, MDBX_put_flags_t flags);
int mdbxgo_cursor_get01(MDBX_cursor *cur, char *vdata, size_t vn, MDBX_val *key, MDBX_val *val, MDBX_cursor_op op);
int mdbxgo_cursor_get1(MDBX_cursor *cur, char *kdata, size_t kn, MDBX_val *key, MDBX_val *val, MDBX_cursor_op op);
int mdbxgo_cursor_get2(MDBX_cursor *cur, char *kdata, size_t kn, char *vdata, size_t vn, MDBX_val *key, MDBX_val *val, MDBX_cursor_op op);

Expand Down

0 comments on commit e364f0a

Please sign in to comment.