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

lmdb: Return dedicated ErrNotFound from Txn.Get and Cursor.Get #65

Closed
wants to merge 2 commits into from
Closed
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
27 changes: 27 additions & 0 deletions lmdb/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,33 @@ func BenchmarkTxn_Get_raw_ro(b *testing.B) {
}
}

// Get a missing key, with RawRead turned on.
func BenchmarkTxn_Get_missing_raw(b *testing.B) {
env := setup(b)
defer clean(env, b)

dbi := openBenchDBI(b, env)

err := env.View(func(txn *Txn) (err error) {
txn.RawRead = true
key := []byte("does not exist")

b.ResetTimer()
defer b.StopTimer()
for i := 0; i < b.N; i++ {
_, err := txn.Get(dbi, key)
if !IsNotFound(err) {
b.Fatalf("found non-existent key: %v", err)
}
}
return nil
})
if err != nil {
b.Error(err)
return
}
}

// repeatedly scan all the values in a database.
func BenchmarkScan_ro(b *testing.B) {
initRandSource(b)
Expand Down
32 changes: 20 additions & 12 deletions lmdb/cursor.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,16 +137,27 @@ func (c *Cursor) DBI() DBI {
//
// Get ignores setval if setkey is empty.
//
// Returns ErrNotFound if setkey is not present.
//
// See mdb_cursor_get.
func (c *Cursor) Get(setkey, setval []byte, op uint) (key, val []byte, err error) {
var ret C.int
switch {
case len(setkey) == 0:
err = c.getVal0(op)
ret = c.getVal0(op)
case len(setval) == 0:
err = c.getVal1(setkey, op)
ret = c.getVal1(setkey, op)
default:
err = c.getVal2(setkey, setval, op)
ret = c.getVal2(setkey, setval, op)
}

if ret == C.MDB_NOTFOUND {
*c.txn.key = C.MDB_val{}
*c.txn.val = C.MDB_val{}
return nil, nil, ErrNotFound
}

err = operrno("mdb_cursor_get", ret)
if err != nil {
*c.txn.key = C.MDB_val{}
*c.txn.val = C.MDB_val{}
Expand All @@ -163,38 +174,35 @@ func (c *Cursor) Get(setkey, setval []byte, op uint) (key, val []byte, err error
// data for reference (Next, First, Last, etc).
//
// See mdb_cursor_get.
func (c *Cursor) getVal0(op uint) error {
ret := C.mdb_cursor_get(c._c, c.txn.key, c.txn.val, C.MDB_cursor_op(op))
return operrno("mdb_cursor_get", ret)
func (c *Cursor) getVal0(op uint) C.int {
return C.mdb_cursor_get(c._c, c.txn.key, c.txn.val, C.MDB_cursor_op(op))
}

// getVal1 retrieves items from the database using key data for reference
// (Set, SetRange, etc).
//
// See mdb_cursor_get.
func (c *Cursor) getVal1(setkey []byte, op uint) error {
ret := C.lmdbgo_mdb_cursor_get1(
func (c *Cursor) getVal1(setkey []byte, op uint) C.int {
return C.lmdbgo_mdb_cursor_get1(
c._c,
unsafe.Pointer(&setkey[0]), C.size_t(len(setkey)),
c.txn.key, c.txn.val,
C.MDB_cursor_op(op),
)
return operrno("mdb_cursor_get", ret)
}

// getVal2 retrieves items from the database using key and value data for
// reference (GetBoth, GetBothRange, etc).
//
// See mdb_cursor_get.
func (c *Cursor) getVal2(setkey, setval []byte, op uint) error {
ret := C.lmdbgo_mdb_cursor_get2(
func (c *Cursor) getVal2(setkey, setval []byte, op uint) C.int {
return C.lmdbgo_mdb_cursor_get2(
c._c,
unsafe.Pointer(&setkey[0]), C.size_t(len(setkey)),
unsafe.Pointer(&setval[0]), C.size_t(len(setval)),
c.txn.key, c.txn.val,
C.MDB_cursor_op(op),
)
return operrno("mdb_cursor_get", ret)
}

func (c *Cursor) putNilKey(flags uint) error {
Expand Down
42 changes: 42 additions & 0 deletions lmdb/cursor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -811,3 +811,45 @@ func BenchmarkCursor_Renew(b *testing.B) {
return nil
})
}

func BenchmarkCursor_Get_missing_raw(b *testing.B) {
env := setup(b)
defer clean(env, b)

var db DBI
err := env.View(func(txn *Txn) (err error) {
db, err = txn.OpenRoot(0)
if err != nil {
return err
}
return nil
})
if err != nil {
b.Error(err)
return
}

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

key := []byte("does not exist")

b.ResetTimer()
defer b.StopTimer()

for i := 0; i < b.N; i++ {
if _, _, err := cur.Get(key, nil, SetKey); !IsNotFound(err) {
b.Fatal("found key that should not exist")
}
}
return
})
if err != nil {
b.Error(err)
return
}
}
8 changes: 7 additions & 1 deletion lmdb/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ const (
// lmdb.IsErrnoFn(err, os.IsPermission)
type Errno C.int

// ErrNotFound is returned by Txn.Get and Cursor.Get
var ErrNotFound = &OpError{"lmdb", NotFound}

// minimum and maximum values produced for the Errno type. syscall.Errnos of
// other values may still be produced.
const minErrno, maxErrno C.int = C.MDB_KEYEXIST, C.MDB_LAST_ERRCODE
Expand All @@ -87,7 +90,7 @@ func _operrno(op string, ret int) error {
// not exist or if the Cursor reached the end of the database without locating
// a value (EOF).
func IsNotFound(err error) bool {
return IsErrno(err, NotFound)
return err == ErrNotFound
}

// IsNotExist returns true the path passed to the Env.Open method does not
Expand Down Expand Up @@ -124,6 +127,9 @@ func IsErrnoFn(err error, fn func(error) bool) bool {
if err == nil {
return false
}
if err == ErrNotFound {
return fn(NotFound)
}
if err, ok := err.(*OpError); ok {
return fn(err.Errno)
}
Expand Down
15 changes: 15 additions & 0 deletions lmdb/txn.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,8 @@ func (txn *Txn) bytes(val *C.MDB_val) []byte {
// returned by Get references a readonly section of memory that must not be
// accessed after txn has terminated.
//
// Returns ErrNotFound if the key is not present in dbi.
//
// See mdb_get.
func (txn *Txn) Get(dbi DBI, key []byte) ([]byte, error) {
kdata, kn := valBytes(key)
Expand All @@ -287,6 +289,12 @@ func (txn *Txn) Get(dbi DBI, key []byte) ([]byte, error) {
unsafe.Pointer(&kdata[0]), C.size_t(kn),
txn.val,
)

if ret == C.MDB_NOTFOUND {
*txn.val = C.MDB_val{}
return nil, ErrNotFound
}

err := operrno("mdb_get", ret)
if err != nil {
*txn.val = C.MDB_val{}
Expand Down Expand Up @@ -352,6 +360,8 @@ func (txn *Txn) PutReserve(dbi DBI, key []byte, n int, flags uint) ([]byte, erro
// Del deletes an item from database dbi. Del ignores val unless dbi has the
// DupSort flag.
//
// Returns ErrNotFound if the key is not present in dbi.
//
// See mdb_del.
func (txn *Txn) Del(dbi DBI, key, val []byte) error {
kdata, kn := valBytes(key)
Expand All @@ -361,6 +371,11 @@ func (txn *Txn) Del(dbi DBI, key, val []byte) error {
unsafe.Pointer(&kdata[0]), C.size_t(kn),
unsafe.Pointer(&vdata[0]), C.size_t(vn),
)

if ret == C.MDB_NOTFOUND {
return ErrNotFound
}

return operrno("mdb_del", ret)
}

Expand Down