[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