todosapp: Use query for getting lists.

Makes getting lists 3 to 4 times faster on example data.

Change-Id: I748ff36162f0f9f4fbd82e90ef23d1a13b7844f9
diff --git a/browser/syncbase_dispatcher.js b/browser/syncbase_dispatcher.js
index 9da0e77..103ff4e 100644
--- a/browser/syncbase_dispatcher.js
+++ b/browser/syncbase_dispatcher.js
@@ -124,17 +124,12 @@
 // SyncbaseDispatcher impl
 
 // Returns list objects without 'sg' fields.
-// TODO(sadovsky): Use a query for better performance. It should be possible to
-// query based on row key regexp.
 define('getListsOnly_', function(ctx, cb) {
-  this.getRows_(ctx, null, function(err, rows) {
+  this.getRowsQuery_(ctx, 'k not like "%' + SEP + '%"', function(err, rows) {
     if (err) return cb(err);
-    var lists = [];
-    _.forEach(rows, function(row) {
-      if (row.key.indexOf(SEP) >= 0) {
-        return;  // ignore nested (and therefore non-list) records
-      }
-      lists.push(_.assign(unmarshal(row.value), {_id: row.key}));
+    var lists = _.map(rows, function(row) {
+      console.assert(!row.key.includes(SEP));
+      return _.assign(unmarshal(row.value), {_id: row.key});
     });
     return cb(null, lists);
   });
@@ -492,7 +487,7 @@
   tb.put(ctx, tagKey(todoId, tag), null, cb);
 });
 
-// Returns all rows in the table.
+// Returns all rows in the table with the given key prefix.
 define('getRows_', function(ctx, prefix, cb) {
   var rows = [], streamErr = null;
   var range = nosql.rowrange.prefix(prefix || '');
@@ -507,6 +502,22 @@
   });
 });
 
+// Returns all rows in the table matching the where clause.
+define('getRowsQuery_', function(ctx, where, cb) {
+  var rows = [], streamErr = null;
+  var q = 'select k, v from ' + this.tb_.name + ' where ' + where;
+  this.db_.exec(ctx, q, function(err) {
+    if (err) return cb(err);
+    if (streamErr) return cb(streamErr);
+    rows = rows.slice(1);  // remove header
+    cb(null, rows);
+  }).on('data', function(row) {
+    rows.push({key: row[0], value: row[1]});
+  }).on('error', function(err) {
+    streamErr = streamErr || err.error;
+  });
+});
+
 // Performs a read-modify-write on key, applying updateFn to the value.
 // Takes care of value marshalling and unmarshalling.
 // TODO(sadovsky): Atomic read-modify-write requires 4 RPCs. MongoDB-style API