Skip to content

Commit

Permalink
added iterator.seek()
Browse files Browse the repository at this point in the history
  • Loading branch information
mafintosh committed May 31, 2015
1 parent d513066 commit 8ded32b
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 1 deletion.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Tested & supported platforms
* <a href="#leveldown_getProperty"><code><b>leveldown#getProperty()</b></code></a>
* <a href="#leveldown_iterator"><code><b>leveldown#iterator()</b></code></a>
* <a href="#iterator_next"><code><b>iterator#next()</b></code></a>
* <a href="#iterator_next"><code><b>iterator#next()</b></code></a>
* <a href="#iterator_end"><code><b>iterator#end()</b></code></a>
* <a href="#leveldown_destroy"><code><b>leveldown.destroy()</b></code></a>
* <a href="#leveldown_repair"><code><b>leveldown.repair()</b></code></a>
Expand Down Expand Up @@ -229,6 +230,14 @@ Otherwise, the `callback` function will be called with the following 3 arguments
* `value` - either a `String` or a Node.js `Buffer` object depending on the `valueAsBuffer` argument when the `iterator()` was called.


--------------------------------------------------------
<a name="iterator_seek"></a>
### iterator#seek(key)
<code>seek()</code> is an instance method on an existing iterator object, used to seek the underlying LevelDB iterator to a given key.

by calling <code>seek(key)</code> subsequent calls to <code>next(cb)</code> will return key/values larger or smaller than <code>key</code>
based on your <code>reverse</code> setting in the iterator constructor.

--------------------------------------------------------
<a name="iterator_end"></a>
### iterator#end(callback)
Expand Down
5 changes: 5 additions & 0 deletions iterator.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ function Iterator (db, options) {

util.inherits(Iterator, AbstractIterator)

Iterator.prototype.seek = function (key) {
if (typeof key !== 'string') throw new Error('seek requires a string key')
this.cache = null
this.binding.seek(key)
}

Iterator.prototype._next = function (callback) {
var that = this
Expand Down
47 changes: 46 additions & 1 deletion src/iterator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Iterator::Iterator (
options->snapshot = database->NewSnapshot();
dbIterator = NULL;
count = 0;
seeking = false;
nexting = false;
ended = false;
endWorker = NULL;
Expand Down Expand Up @@ -125,13 +126,15 @@ bool Iterator::GetIterator () {

bool Iterator::Read (std::string& key, std::string& value) {
// if it's not the first call, move to next item.
if (!GetIterator()) {
if (!GetIterator() && !seeking) {
if (reverse)
dbIterator->Prev();
else
dbIterator->Next();
}

seeking = false;

// now check if this is the end or not, if not then return the key & value
if (dbIterator->Valid()) {
std::string key_ = dbIterator->key().ToString();
Expand Down Expand Up @@ -200,6 +203,47 @@ void checkEndCallback (Iterator* iterator) {
}
}

NAN_METHOD(Iterator::Seek) {
NanScope();

Iterator* iterator = node::ObjectWrap::Unwrap<Iterator>(args.This());
iterator->GetIterator();
leveldb::Iterator* dbIterator = iterator->dbIterator;
NanUtf8String key(args[0]);

dbIterator->Seek(*key);
iterator->seeking = true;

if (dbIterator->Valid()) {
int cmp = dbIterator->key().compare(*key);
if (cmp > 0 && iterator->reverse) {
dbIterator->Prev();
}
if (cmp < 0 && !iterator->reverse) {
dbIterator->Next();
}
} else {
if (iterator->reverse) {
dbIterator->SeekToLast();
} else {
dbIterator->SeekToFirst();
}
if (dbIterator->Valid()) {
int cmp = dbIterator->key().compare(*key);
if (cmp > 0 && iterator->reverse) {
dbIterator->SeekToFirst();
dbIterator->Prev();
}
if (cmp < 0 && !iterator->reverse) {
dbIterator->SeekToLast();
dbIterator->Next();
}
}
}

NanReturnValue(args.Holder());
}

NAN_METHOD(Iterator::Next) {
NanScope();

Expand Down Expand Up @@ -253,6 +297,7 @@ void Iterator::Init () {
NanAssignPersistent(iterator_constructor, tpl);
tpl->SetClassName(NanNew("Iterator"));
tpl->InstanceTemplate()->SetInternalFieldCount(1);
NODE_SET_PROTOTYPE_METHOD(tpl, "seek", Iterator::Seek);
NODE_SET_PROTOTYPE_METHOD(tpl, "next", Iterator::Next);
NODE_SET_PROTOTYPE_METHOD(tpl, "end", Iterator::End);
}
Expand Down
2 changes: 2 additions & 0 deletions src/iterator.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class Iterator : public node::ObjectWrap {
leveldb::ReadOptions* options;
leveldb::Slice* start;
std::string* end;
bool seeking;
bool reverse;
bool keys;
bool values;
Expand All @@ -87,6 +88,7 @@ class Iterator : public node::ObjectWrap {
bool GetIterator ();

static NAN_METHOD(New);
static NAN_METHOD(Seek);
static NAN_METHOD(Next);
static NAN_METHOD(End);
};
Expand Down
63 changes: 63 additions & 0 deletions test/iterator-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,68 @@ const test = require('tape')
, testCommon = require('abstract-leveldown/testCommon')
, leveldown = require('../')
, abstract = require('abstract-leveldown/abstract/iterator-test')
, make = require('./make')

abstract.all(leveldown, test, testCommon)

make('iterator throws if key is not a string', function (db, t, done) {
var ite = db.iterator()
var error
try {
ite.seek()
} catch (e) {
error = e
}

t.ok(error, 'had error')
t.end()
})

make('iterator is seekable', function (db, t, done) {
var ite = db.iterator()
ite.seek('two')
ite.next(function (err, key, value) {
t.error(err, 'no error')
t.same(key.toString(), 'two', 'key matches')
t.same(value.toString(), '2', 'value matches')
ite.next(function (err, key, value) {
t.error(err, 'no error')
t.same(key, undefined, 'end of iterator')
t.same(value, undefined, 'end of iterator')
ite.end(done)
})
})
})

make('reverse seek in the middle', function (db, t, done) {
var ite = db.iterator({reverse: true, limit: 1})
ite.seek('three!')
ite.next(function (err, key, value) {
t.error(err, 'no error')
t.same(key.toString(), 'three', 'key matches')
t.same(value.toString(), '3', 'value matches')
ite.end(done)
})
})

make('iterator invalid seek', function (db, t, done) {
var ite = db.iterator()
ite.seek('zzz')
ite.next(function (err, key, value) {
t.error(err, 'no error')
t.same(key, undefined, 'end of iterator')
t.same(value, undefined, 'end of iterator')
ite.end(done)
})
})

make('reverse seek from invalid range', function (db, t, done) {
var ite = db.iterator({reverse: true})
ite.seek('zzz')
ite.next(function (err, key, value) {
t.error(err, 'no error')
t.same(key.toString(), 'two', 'end of iterator')
t.same(value.toString(), '2', 'end of iterator')
ite.end(done)
})
})

0 comments on commit 8ded32b

Please sign in to comment.