Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cursor.Get handles key return values specicially for Set #97

Merged
merged 5 commits into from
Jan 28, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions lmdb/cursor.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ func (c *Cursor) DBI() DBI {
// returned by Get reference readonly sections of memory that must not be
// accessed after the transaction has terminated.
//
// In a Txn with RawRead set to true the Set op causes the returned key to
// share its memory with setkey (making it writable memory). In a Txn with
// RawRead set to false the Set op returns key values with memory distinct from
// setkey, as is always the case when using RawRead.
//
// Get ignores setval if setkey is empty.
//
// See mdb_cursor_get.
Expand All @@ -152,11 +157,30 @@ func (c *Cursor) Get(setkey, setval []byte, op uint) (key, val []byte, err error
*c.txn.val = C.MDB_val{}
return nil, nil, err
}
k := c.txn.bytes(c.txn.key)
v := c.txn.bytes(c.txn.val)

// When MDB_SET is passed to mdb_cursor_get its first argument will be
// returned unchanged. Unfortunately, the normal slice copy/extraction
// routines will be bad for the Go runtime when operating on Go memory
// (panic or potentially garbage memory reference).
if op == Set {
if c.txn.RawRead {
key = setkey
} else {
p := make([]byte, len(setkey))
copy(p, setkey)
key = p
}
} else {
key = c.txn.bytes(c.txn.key)
}
val = c.txn.bytes(c.txn.val)

// Clear transaction storage record storage area for future use and to
// prevent dangling references.
*c.txn.key = C.MDB_val{}
*c.txn.val = C.MDB_val{}
return k, v, nil

return key, val, nil
}

// getVal0 retrieves items from the database without using given key or value
Expand Down
78 changes: 77 additions & 1 deletion lmdb/cursor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ func TestCursor_Get_KV(t *testing.T) {
t.Errorf("unexpected key: %q (not %q)", k, "key")
}
if string(v) != "1" {
t.Errorf("unexpected key: %q (not %q)", k, "1")
t.Errorf("unexpected value: %q (not %q)", k, "1")
}

_, _, err = cur.Get([]byte("key"), []byte("1"), GetBoth)
Expand All @@ -261,6 +261,82 @@ func TestCursor_Get_KV(t *testing.T) {
}
}

func TestCursor_Get_op_Set_bytesBuffer(t *testing.T) {
env := setup(t)
defer clean(env, t)

var dbi DBI
err := env.Update(func(txn *Txn) (err error) {
dbi, err = txn.OpenDBI("testdb", Create|DupSort)
return err
})
if err != nil {
t.Errorf("%s", err)
return
}

err = env.Update(func(txn *Txn) (err error) {
put := func(k, v []byte) {
if err == nil {
err = txn.Put(dbi, k, v, 0)
}
}
put([]byte("k1"), []byte("v11"))
put([]byte("k1"), []byte("v12"))
put([]byte("k1"), []byte("v13"))
put([]byte("k2"), []byte("v21"))
put([]byte("k2"), []byte("v22"))
put([]byte("k2"), []byte("v23"))
return err
})
if err != nil {
t.Errorf("%s", err)
}

err = env.View(func(txn *Txn) (err error) {
cur, err := txn.OpenCursor(dbi)
if err != nil {
return err
}
defer cur.Close()

// Create bytes.Buffer values containing a amount of bytes. Byte
// slices returned from buf.Bytes() have a history of tricking the cgo
// argument checker.
var kbuf bytes.Buffer
kbuf.WriteString("k2")

k, _, err := cur.Get(kbuf.Bytes(), nil, Set)
if err != nil {
return err
}
if string(k) != kbuf.String() {
t.Errorf("unexpected key: %q (not %q)", k, kbuf.String())
}

// No guarantee is made about the return value of mdb_cursor_get when
// MDB_SET is the op, so its value is not checked as part of this test.
// That said, it is important that Cursor.Get not panic if given a
// short buffer as an input value for a Set op (despite that not really
// having any significance)
var vbuf bytes.Buffer
vbuf.WriteString("v22")

k, _, err = cur.Get(kbuf.Bytes(), vbuf.Bytes(), Set)
if err != nil {
return err
}
if string(k) != kbuf.String() {
t.Errorf("unexpected key: %q (not %q)", k, kbuf.String())
}

return nil
})
if err != nil {
t.Errorf("%s", err)
}
}

func TestCursor_Get_DupFixed(t *testing.T) {
env := setup(t)
defer clean(env, t)
Expand Down