leveldb: introducing DB.Has and Snapshot.Has methods (closes #82)
diff --git a/leveldb/db.go b/leveldb/db.go
index a653ec3..4d9f180 100644
--- a/leveldb/db.go
+++ b/leveldb/db.go
@@ -625,7 +625,7 @@
}
v := db.s.version()
- value, cSched, err := v.get(ikey, ro)
+ value, cSched, err := v.get(ikey, ro, false)
v.release()
if cSched {
// Trigger table compaction.
@@ -634,8 +634,51 @@
return
}
+func (db *DB) has(key []byte, seq uint64, ro *opt.ReadOptions) (ret bool, err error) {
+ ikey := newIkey(key, seq, ktSeek)
+
+ em, fm := db.getMems()
+ for _, m := range [...]*memDB{em, fm} {
+ if m == nil {
+ continue
+ }
+ defer m.decref()
+
+ mk, _, me := m.mdb.Find(ikey)
+ if me == nil {
+ ukey, _, kt, kerr := parseIkey(mk)
+ if kerr != nil {
+ // Shouldn't have had happen.
+ panic(kerr)
+ }
+ if db.s.icmp.uCompare(ukey, key) == 0 {
+ if kt == ktDel {
+ return false, nil
+ }
+ return true, nil
+ }
+ } else if me != ErrNotFound {
+ return false, me
+ }
+ }
+
+ v := db.s.version()
+ _, cSched, err := v.get(ikey, ro, true)
+ v.release()
+ if cSched {
+ // Trigger table compaction.
+ db.compSendTrigger(db.tcompCmdC)
+ }
+ if err == nil {
+ ret = true
+ } else if err == ErrNotFound {
+ err = nil
+ }
+ return
+}
+
// Get gets the value for the given key. It returns ErrNotFound if the
-// DB does not contain the key.
+// DB does not contains the key.
//
// The returned slice is its own copy, it is safe to modify the contents
// of the returned slice.
@@ -651,6 +694,20 @@
return db.get(key, se.seq, ro)
}
+// Has returns true if the DB does contains the given key.
+//
+// It is safe to modify the contents of the argument after Get returns.
+func (db *DB) Has(key []byte, ro *opt.ReadOptions) (ret bool, err error) {
+ err = db.ok()
+ if err != nil {
+ return
+ }
+
+ se := db.acquireSnapshot()
+ defer db.releaseSnapshot(se)
+ return db.has(key, se.seq, ro)
+}
+
// NewIterator returns an iterator for the latest snapshot of the
// uderlying DB.
// The returned iterator is not goroutine-safe, but it is safe to use
diff --git a/leveldb/db_snapshot.go b/leveldb/db_snapshot.go
index e5679d8..e41dcc6 100644
--- a/leveldb/db_snapshot.go
+++ b/leveldb/db_snapshot.go
@@ -90,7 +90,7 @@
}
// Get gets the value for the given key. It returns ErrNotFound if
-// the DB does not contain the key.
+// the DB does not contains the key.
//
// The caller should not modify the contents of the returned slice, but
// it is safe to modify the contents of the argument after Get returns.
@@ -108,6 +108,23 @@
return snap.db.get(key, snap.elem.seq, ro)
}
+// Has returns true if the DB does contains the given key.
+//
+// It is safe to modify the contents of the argument after Get returns.
+func (snap *Snapshot) Has(key []byte, ro *opt.ReadOptions) (ret bool, err error) {
+ err = snap.db.ok()
+ if err != nil {
+ return
+ }
+ snap.mu.RLock()
+ defer snap.mu.RUnlock()
+ if snap.released {
+ err = ErrSnapshotReleased
+ return
+ }
+ return snap.db.has(key, snap.elem.seq, ro)
+}
+
// NewIterator returns an iterator for the snapshot of the uderlying DB.
// The returned iterator is not goroutine-safe, but it is safe to use
// multiple iterators concurrently, with each in a dedicated goroutine.
diff --git a/leveldb/external_test.go b/leveldb/external_test.go
index cedee79..afbf594 100644
--- a/leveldb/external_test.go
+++ b/leveldb/external_test.go
@@ -24,6 +24,7 @@
CachedOpenFiles: -1,
Strict: opt.StrictAll,
WriteBuffer: 1000,
+ CompactionTableSize: 2000,
}
Describe("write test", func() {
diff --git a/leveldb/table.go b/leveldb/table.go
index ea6eb29..c3432fc 100644
--- a/leveldb/table.go
+++ b/leveldb/table.go
@@ -373,7 +373,17 @@
return nil, nil, err
}
defer ch.Release()
- return ch.Value().(*table.Reader).Find(key, ro)
+ return ch.Value().(*table.Reader).Find(key, true, ro)
+}
+
+// Finds key that is greater than or equal to the given key.
+func (t *tOps) findKey(f *tFile, key []byte, ro *opt.ReadOptions) (rkey []byte, err error) {
+ ch, err := t.open(f)
+ if err != nil {
+ return nil, err
+ }
+ defer ch.Release()
+ return ch.Value().(*table.Reader).FindKey(key, true, ro)
}
// Returns approximate offset of the given key.
diff --git a/leveldb/table/block_test.go b/leveldb/table/block_test.go
index e583d9a..00e6f9e 100644
--- a/leveldb/table/block_test.go
+++ b/leveldb/table/block_test.go
@@ -122,6 +122,7 @@
}
testutil.DoIteratorTesting(&t)
+ iter.Release()
done <- true
}
}
diff --git a/leveldb/table/reader.go b/leveldb/table/reader.go
index a7a2c9b..9e4135d 100644
--- a/leveldb/table/reader.go
+++ b/leveldb/table/reader.go
@@ -798,13 +798,7 @@
return iterator.NewIndexedIterator(index, opt.GetStrict(r.o, ro, opt.StrictReader))
}
-// Find finds key/value pair whose key is greater than or equal to the
-// given key. It returns ErrNotFound if the table doesn't contain
-// such pair.
-//
-// The caller should not modify the contents of the returned slice, but
-// it is safe to modify the contents of the argument after Find returns.
-func (r *Reader) Find(key []byte, ro *opt.ReadOptions) (rkey, value []byte, err error) {
+func (r *Reader) find(key []byte, filtered bool, ro *opt.ReadOptions, noValue bool) (rkey, value []byte, err error) {
r.mu.RLock()
defer r.mu.RUnlock()
@@ -833,7 +827,7 @@
r.err = r.newErrCorruptedBH(r.indexBH, "bad data block handle")
return
}
- if r.filter != nil {
+ if filtered && r.filter != nil {
filterBlock, rel, ferr := r.getFilterBlock(true)
if ferr == nil {
if !filterBlock.contains(r.filter, dataBH.offset, key) {
@@ -854,21 +848,52 @@
}
// Don't use block buffer, no need to copy the buffer.
rkey = data.Key()
- if r.bpool == nil {
- value = data.Value()
- } else {
- // Use block buffer, and since the buffer will be recycled, the buffer
- // need to be copied.
- value = append([]byte{}, data.Value()...)
+ if !noValue {
+ if r.bpool == nil {
+ value = data.Value()
+ } else {
+ // Use block buffer, and since the buffer will be recycled, the buffer
+ // need to be copied.
+ value = append([]byte{}, data.Value()...)
+ }
}
return
}
+// Find finds key/value pair whose key is greater than or equal to the
+// given key. It returns ErrNotFound if the table doesn't contain
+// such pair.
+// If filtered is true then the nearest 'block' will be checked against
+// 'filter data' (if present) and will immediately return ErrNotFound if
+// 'filter data' indicates that such pair doesn't exist.
+//
+// The caller may modify the contents of the returned slice as it is its
+// own copy.
+// It is safe to modify the contents of the argument after Find returns.
+func (r *Reader) Find(key []byte, filtered bool, ro *opt.ReadOptions) (rkey, value []byte, err error) {
+ return r.find(key, filtered, ro, false)
+}
+
+// Find finds key that is greater than or equal to the given key.
+// It returns ErrNotFound if the table doesn't contain such key.
+// If filtered is true then the nearest 'block' will be checked against
+// 'filter data' (if present) and will immediately return ErrNotFound if
+// 'filter data' indicates that such key doesn't exist.
+//
+// The caller may modify the contents of the returned slice as it is its
+// own copy.
+// It is safe to modify the contents of the argument after Find returns.
+func (r *Reader) FindKey(key []byte, filtered bool, ro *opt.ReadOptions) (rkey []byte, err error) {
+ rkey, _, err = r.find(key, filtered, ro, true)
+ return
+}
+
// Get gets the value for the given key. It returns errors.ErrNotFound
// if the table does not contain the key.
//
-// The caller should not modify the contents of the returned slice, but
-// it is safe to modify the contents of the argument after Get returns.
+// The caller may modify the contents of the returned slice as it is its
+// own copy.
+// It is safe to modify the contents of the argument after Find returns.
func (r *Reader) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error) {
r.mu.RLock()
defer r.mu.RUnlock()
@@ -878,7 +903,7 @@
return
}
- rkey, value, err := r.Find(key, ro)
+ rkey, value, err := r.find(key, false, ro, false)
if err == nil && r.cmp.Compare(rkey, key) != 0 {
value = nil
err = ErrNotFound
diff --git a/leveldb/table/table_test.go b/leveldb/table/table_test.go
index 6662a59..4b59b31 100644
--- a/leveldb/table/table_test.go
+++ b/leveldb/table/table_test.go
@@ -23,7 +23,7 @@
}
func (t tableWrapper) TestFind(key []byte) (rkey, rvalue []byte, err error) {
- return t.Reader.Find(key, nil)
+ return t.Reader.Find(key, false, nil)
}
func (t tableWrapper) TestGet(key []byte) (value []byte, err error) {
diff --git a/leveldb/testutil/db.go b/leveldb/testutil/db.go
index 2e2d81f..ec3f177 100644
--- a/leveldb/testutil/db.go
+++ b/leveldb/testutil/db.go
@@ -35,6 +35,10 @@
TestGet(key []byte) (value []byte, err error)
}
+type Has interface {
+ TestHas(key []byte) (ret bool, err error)
+}
+
type NewIterator interface {
TestNewIterator(slice *util.Range) iterator.Iterator
}
@@ -213,5 +217,6 @@
}
DoIteratorTesting(&it)
+ iter.Release()
}
}
diff --git a/leveldb/testutil/kvtest.go b/leveldb/testutil/kvtest.go
index 3b28f8b..d7c7715 100644
--- a/leveldb/testutil/kvtest.go
+++ b/leveldb/testutil/kvtest.go
@@ -84,6 +84,26 @@
}
})
+ It("Should only find present key with Has", func() {
+ if db, ok := p.(Has); ok {
+ ShuffledIndex(nil, kv.Len(), 1, func(i int) {
+ key_, key, _ := kv.IndexInexact(i)
+
+ // Using exact key.
+ ret, err := db.TestHas(key)
+ Expect(err).ShouldNot(HaveOccurred(), "Error for key %q", key)
+ Expect(ret).Should(BeTrue(), "False for key %q", key)
+
+ // Using inexact key.
+ if len(key_) > 0 {
+ ret, err = db.TestHas(key_)
+ Expect(err).ShouldNot(HaveOccurred(), "Error for key %q", key_)
+ Expect(ret).ShouldNot(BeTrue(), "True for key %q", key)
+ }
+ })
+ }
+ })
+
TestIter := func(r *util.Range, _kv KeyValue) {
if db, ok := p.(NewIterator); ok {
iter := db.TestNewIterator(r)
@@ -95,6 +115,7 @@
}
DoIteratorTesting(&t)
+ iter.Release()
}
}
diff --git a/leveldb/testutil_test.go b/leveldb/testutil_test.go
index 111f873..25bf2b2 100644
--- a/leveldb/testutil_test.go
+++ b/leveldb/testutil_test.go
@@ -34,6 +34,10 @@
return t.Get(key, t.ro)
}
+func (t *testingDB) TestHas(key []byte) (ret bool, err error) {
+ return t.Has(key, t.ro)
+}
+
func (t *testingDB) TestNewIterator(slice *util.Range) iterator.Iterator {
return t.NewIterator(slice, t.ro)
}
diff --git a/leveldb/version.go b/leveldb/version.go
index e598648..5ab7b53 100644
--- a/leveldb/version.go
+++ b/leveldb/version.go
@@ -114,7 +114,7 @@
}
}
-func (v *version) get(ikey iKey, ro *opt.ReadOptions) (value []byte, tcomp bool, err error) {
+func (v *version) get(ikey iKey, ro *opt.ReadOptions, noValue bool) (value []byte, tcomp bool, err error) {
ukey := ikey.ukey()
var (
@@ -142,7 +142,15 @@
}
}
- fikey, fval, ferr := v.s.tops.find(t, ikey, ro)
+ var (
+ fikey, fval []byte
+ ferr error
+ )
+ if noValue {
+ fikey, ferr = v.s.tops.findKey(t, ikey, ro)
+ } else {
+ fikey, fval, ferr = v.s.tops.find(t, ikey, ro)
+ }
switch ferr {
case nil:
case ErrNotFound: