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: