[Pkg-javascript-commits] [node-leveldown] 42/492: reversable ReadStreams
Andrew Kelley
andrewrk-guest at moszumanska.debian.org
Sun Jul 6 17:13:43 UTC 2014
This is an automated email from the git hooks/post-receive script.
andrewrk-guest pushed a commit to annotated tag rocksdb-0.10.1
in repository node-leveldown.
commit 9291248a32bf008182617cae141afbd748006a86
Author: Rod Vagg <rod at vagg.org>
Date: Sun Sep 9 13:25:07 2012 +1000
reversable ReadStreams
---
README.md | 5 +-
src/iterator.cc | 26 +++++++--
src/iterator.h | 3 +
test/read-stream-test.js | 147 ++++++++++++++++++++++++++++++++++++++++++++++-
4 files changed, 171 insertions(+), 10 deletions(-)
diff --git a/README.md b/README.md
index 22023f4..133b169 100644
--- a/README.md
+++ b/README.md
@@ -103,7 +103,9 @@ Additionally, you can supply an options object as the first parameter to `readSt
* `'start'`: the key you wish to start the read at. By default it will start at the beginning of the store. Note that the *start* doesn't have to be an actual key that exists, LevelDB will simply find the *next* key, greater than the key you provide.
-* `'end'`: the key you wish to end the read on. By default it will continue until the end of the store. Again, the *end* doesn't have to be an actual key as a `<`-type operation is performed to detect the end. You can also use the `destroy()` method instead of supplying an `'end'` parameter to achieve the same effect.
+* `'end'`: the key you wish to end the read on. By default it will continue until the end of the store. Again, the *end* doesn't have to be an actual key as an (inclusive) `<=`-type operation is performed to detect the end. You can also use the `destroy()` method instead of supplying an `'end'` parameter to achieve the same effect.
+
+* `'reverse'`: a boolean, set to true if you want the stream to go in reverse order. Beware that due to the way LevelDB works, a reverse seek will be slower than a forward seek.
### WriteStream
@@ -151,7 +153,6 @@ Important considerations
TODO
----
-* ReadStream reverse read
* Filter streams, e.g.: KeyReadStream, ValueReadStream
* *Windows support, maybe*
* Benchmarks
diff --git a/src/iterator.cc b/src/iterator.cc
index fa018cb..1c46563 100644
--- a/src/iterator.cc
+++ b/src/iterator.cc
@@ -17,12 +17,15 @@ using namespace levelup;
LU_OPTION ( start );
LU_OPTION ( end );
+LU_OPTION ( reverse );
bool levelup::Iterator::GetIterator () {
if (dbIterator == NULL) {
dbIterator = database->NewIterator(options);
if (start != NULL)
dbIterator->Seek(*start);
+ else if (reverse)
+ dbIterator->SeekToLast();
else
dbIterator->SeekToFirst();
return true;
@@ -31,9 +34,18 @@ bool levelup::Iterator::GetIterator () {
}
bool levelup::Iterator::IteratorNext (string& key, string& value) {
- if (!GetIterator()) dbIterator->Next();
+ if (!GetIterator()) {
+ if (reverse)
+ dbIterator->Prev();
+ else
+ dbIterator->Next();
+ }
+
// 'end' here is an inclusive test
- if (dbIterator->Valid() && (end == NULL || end->compare(dbIterator->key().ToString()) > 0)) {
+ if (dbIterator->Valid()
+ && (end == NULL
+ || (reverse && end->compare(dbIterator->key().ToString()) <= 0)
+ || (!reverse && end->compare(dbIterator->key().ToString()) >= 0))) {
key.assign(dbIterator->key().data(), dbIterator->key().size());
value.assign(dbIterator->value().data(), dbIterator->value().size());
return true;
@@ -108,15 +120,19 @@ Handle<Value> levelup::Iterator::New (const Arguments& args) {
Database* database = ObjectWrap::Unwrap<Database>(args[0]->ToObject());
Slice* start = NULL;
if (args[1]->ToObject()->Has(option_start)) {
- Persistent<Object> startBuffer = Persistent<Object>::New(args[1]->ToObject()->Get(option_start)->ToObject());
+ Local<Object> startBuffer = Local<Object>::New(args[1]->ToObject()->Get(option_start)->ToObject());
start = new Slice(Buffer::Data(startBuffer), Buffer::Length(startBuffer));
}
string* end = NULL;
if (args[1]->ToObject()->Has(option_end)) {
- Persistent<Object> endBuffer = Persistent<Object>::New(args[1]->ToObject()->Get(option_end)->ToObject());
+ Local<Object> endBuffer = Local<Object>::New(args[1]->ToObject()->Get(option_end)->ToObject());
end = new string(Buffer::Data(endBuffer), Buffer::Length(endBuffer));
}
- Iterator* iterator = new Iterator(database, start, end);
+ bool reverse = false;
+ if (args[1]->ToObject()->Has(option_reverse)) {
+ reverse = args[1]->ToObject()->Get(option_reverse)->BooleanValue();
+ }
+ Iterator* iterator = new Iterator(database, start, end, reverse);
iterator->Wrap(args.This());
return args.This();
diff --git a/src/iterator.h b/src/iterator.h
index 8b3be29..6dcc4a8 100644
--- a/src/iterator.h
+++ b/src/iterator.h
@@ -31,9 +31,11 @@ private:
Database* database
, Slice* start
, string* end
+ , bool reverse
) : database(database)
, start(start)
, end(end)
+ , reverse(reverse)
{
options = new ReadOptions();
dbIterator = NULL;
@@ -52,6 +54,7 @@ private:
ReadOptions* options;
Slice* start;
string* end;
+ bool reverse;
bool GetIterator ();
diff --git a/test/read-stream-test.js b/test/read-stream-test.js
index db6fad2..7de2d69 100644
--- a/test/read-stream-test.js
+++ b/test/read-stream-test.js
@@ -31,6 +31,7 @@ buster.testCase('ReadStream', {
this.sourceData.forEach(function (d, i) {
var call = this.dataSpy.getCall(i)
if (call) {
+ //console.log('call', i, ':', call.args[0].key, '=', call.args[0].value, '(expected', d.key, '=', d.value, ')')
assert.equals(call.args.length, 1, 'ReadStream "data" event #' + i + ' fired with 1 argument')
refute.isNull(call.args[0].key, 'ReadStream "data" event #' + i + ' argument has "key" property')
refute.isNull(call.args[0].value, 'ReadStream "data" event #' + i + ' argument has "value" property')
@@ -167,6 +168,25 @@ buster.testCase('ReadStream', {
}.bind(this))
}
+ , 'test readStream() with "reverse=true"': function (done) {
+ this.openTestDatabase(function (db) {
+ // execute
+ db.batch(this.sourceData.slice(), function (err) {
+ refute(err)
+
+ var rs = db.readStream({ reverse: true })
+ assert.isFalse(rs.writable)
+ assert.isTrue(rs.readable)
+ rs.on('ready', this.readySpy)
+ rs.on('data' , this.dataSpy)
+ rs.on('end' , this.endSpy)
+ rs.on('close', this.verify.bind(this, rs, done))
+
+ this.sourceData.reverse() // for verify
+ }.bind(this))
+ }.bind(this))
+ }
+
, 'test readStream() with "start"': function (done) {
this.openTestDatabase(function (db) {
db.batch(this.sourceData.slice(), function (err) {
@@ -186,6 +206,26 @@ buster.testCase('ReadStream', {
}.bind(this))
}
+ , 'test readStream() with "start" and "reverse=true"': function (done) {
+ this.openTestDatabase(function (db) {
+ db.batch(this.sourceData.slice(), function (err) {
+ refute(err)
+
+ var rs = db.readStream({ start: '50', reverse: true })
+ assert.isFalse(rs.writable)
+ assert.isTrue(rs.readable)
+ rs.on('ready', this.readySpy)
+ rs.on('data' , this.dataSpy)
+ rs.on('end' , this.endSpy)
+ rs.on('close', this.verify.bind(this, rs, done))
+
+ // reverse and slice off the first 50 so verify() expects only the first 50 even though all 100 are in the db
+ this.sourceData.reverse()
+ this.sourceData = this.sourceData.slice(49)
+ }.bind(this))
+ }.bind(this))
+ }
+
, 'test readStream() with "start" being mid-way key (float)': function (done) {
this.openTestDatabase(function (db) {
db.batch(this.sourceData.slice(), function (err) {
@@ -206,6 +246,29 @@ buster.testCase('ReadStream', {
}.bind(this))
}
+ , 'test readStream() with "start" being mid-way key (float) and "reverse=true"': function (done) {
+ this.openTestDatabase(function (db) {
+ db.batch(this.sourceData.slice(), function (err) {
+ refute(err)
+
+ //NOTE: this is similar to the above case but we're going backwards, the important caveat with
+ // reversable streams is that the start will always be the NEXT key if the actual key you specify
+ // doesn't exist, not the PREVIOUS (i.e. it skips ahead to find a start key)
+ var rs = db.readStream({ start: '49.5', reverse: true })
+ assert.isFalse(rs.writable)
+ assert.isTrue(rs.readable)
+ rs.on('ready', this.readySpy)
+ rs.on('data' , this.dataSpy)
+ rs.on('end' , this.endSpy)
+ rs.on('close', this.verify.bind(this, rs, done))
+
+ // reverse & slice off the first 50 so verify() expects only the first 50 even though all 100 are in the db
+ this.sourceData.reverse()
+ this.sourceData = this.sourceData.slice(49)
+ }.bind(this))
+ }.bind(this))
+ }
+
, 'test readStream() with "start" being mid-way key (string)': function (done) {
this.openTestDatabase(function (db) {
db.batch(this.sourceData.slice(), function (err) {
@@ -240,8 +303,65 @@ buster.testCase('ReadStream', {
rs.on('end' , this.endSpy)
rs.on('close', this.verify.bind(this, rs, done))
- // slice off the last 50 so verify() expects only the first 50 even though all 100 are in the db
- this.sourceData = this.sourceData.slice(0, 50)
+ // slice off the last 49 so verify() expects only 0 -> 50 inclusive, even though all 100 are in the db
+ this.sourceData = this.sourceData.slice(0, 51)
+ }.bind(this))
+ }.bind(this))
+ }
+
+ , 'test readStream() with "end" being mid-way key (float)': function (done) {
+ this.openTestDatabase(function (db) {
+ db.batch(this.sourceData.slice(), function (err) {
+ refute(err)
+
+ var rs = db.readStream({ end: '50.5' })
+ assert.isFalse(rs.writable)
+ assert.isTrue(rs.readable)
+ rs.on('ready', this.readySpy)
+ rs.on('data' , this.dataSpy)
+ rs.on('end' , this.endSpy)
+ rs.on('close', this.verify.bind(this, rs, done))
+
+ // slice off the last 49 so verify() expects only 0 -> 50 inclusive, even though all 100 are in the db
+ this.sourceData = this.sourceData.slice(0, 51)
+ }.bind(this))
+ }.bind(this))
+ }
+
+ , 'test readStream() with "end" being mid-way key (string)': function (done) {
+ this.openTestDatabase(function (db) {
+ db.batch(this.sourceData.slice(), function (err) {
+ refute(err)
+
+ var rs = db.readStream({ end: '50555555' })
+ assert.isFalse(rs.writable)
+ assert.isTrue(rs.readable)
+ rs.on('ready', this.readySpy)
+ rs.on('data' , this.dataSpy)
+ rs.on('end' , this.endSpy)
+ rs.on('close', this.verify.bind(this, rs, done))
+
+ // slice off the last 49 so verify() expects only 0 -> 50 inclusive, even though all 100 are in the db
+ this.sourceData = this.sourceData.slice(0, 51)
+ }.bind(this))
+ }.bind(this))
+ }
+
+ , 'test readStream() with "end" being mid-way key (float) and "reverse=true"': function (done) {
+ this.openTestDatabase(function (db) {
+ db.batch(this.sourceData.slice(), function (err) {
+ refute(err)
+
+ var rs = db.readStream({ end: '50.5', reverse: true })
+ assert.isFalse(rs.writable)
+ assert.isTrue(rs.readable)
+ rs.on('ready', this.readySpy)
+ rs.on('data' , this.dataSpy)
+ rs.on('end' , this.endSpy)
+ rs.on('close', this.verify.bind(this, rs, done))
+
+ this.sourceData.reverse()
+ this.sourceData = this.sourceData.slice(0, 49)
}.bind(this))
}.bind(this))
}
@@ -259,7 +379,28 @@ buster.testCase('ReadStream', {
rs.on('end' , this.endSpy)
rs.on('close', this.verify.bind(this, rs, done))
- this.sourceData = this.sourceData.slice(30, 70)
+ // should include 30 to 70, inclusive
+ this.sourceData = this.sourceData.slice(30, 71)
+ }.bind(this))
+ }.bind(this))
+ }
+
+ , 'test readStream() with both "start" and "end" and "reverse=true"': function (done) {
+ this.openTestDatabase(function (db) {
+ db.batch(this.sourceData.slice(), function (err) {
+ refute(err)
+
+ var rs = db.readStream({ start: 70, end: 30, reverse: true })
+ assert.isFalse(rs.writable)
+ assert.isTrue(rs.readable)
+ rs.on('ready', this.readySpy)
+ rs.on('data' , this.dataSpy)
+ rs.on('end' , this.endSpy)
+ rs.on('close', this.verify.bind(this, rs, done))
+
+ // expect 70 -> 30 inclusive
+ this.sourceData.reverse()
+ this.sourceData = this.sourceData.slice(29, 70)
}.bind(this))
}.bind(this))
}
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-leveldown.git
More information about the Pkg-javascript-commits
mailing list