syncbase/exec: Expose support for parameterized queries.

Allow Syncbase to accept parameterized queries, with '?' placeholders
in the where clause.

MultiPart: 5/5
Change-Id: Ic6f7d608c3d8433ec415e2a42c73c359069b5536
diff --git a/lib/gen/dart-gen/mojom/lib/mojo/syncbase.mojom.dart b/lib/gen/dart-gen/mojom/lib/mojo/syncbase.mojom.dart
index 26479a5..cecd4a2 100644
--- a/lib/gen/dart-gen/mojom/lib/mojo/syncbase.mojom.dart
+++ b/lib/gen/dart-gen/mojom/lib/mojo/syncbase.mojom.dart
@@ -3383,10 +3383,11 @@
 
 class _SyncbaseDbExecParams extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
-    const bindings.StructDataHeader(32, 0)
+    const bindings.StructDataHeader(40, 0)
   ];
   String name = null;
   String query = null;
+  List<List<int>> parameters = null;
   Object stream = null;
 
   _SyncbaseDbExecParams() : super(kVersions.last.size);
@@ -3434,7 +3435,19 @@
     }
     if (mainDataHeader.version >= 0) {
       
-      result.stream = decoder0.decodeServiceInterface(24, false, ExecStreamProxy.newFromEndpoint);
+      var decoder1 = decoder0.decodePointer(24, false);
+      {
+        var si1 = decoder1.decodeDataHeaderForPointerArray(bindings.kUnspecifiedArrayLength);
+        result.parameters = new List<List<int>>(si1.numElements);
+        for (int i1 = 0; i1 < si1.numElements; ++i1) {
+          
+          result.parameters[i1] = decoder1.decodeUint8Array(bindings.ArrayDataHeader.kHeaderSize + bindings.kPointerSize * i1, bindings.kNothingNullable, bindings.kUnspecifiedArrayLength);
+        }
+      }
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.stream = decoder0.decodeServiceInterface(32, false, ExecStreamProxy.newFromEndpoint);
     }
     return result;
   }
@@ -3446,13 +3459,24 @@
     
     encoder0.encodeString(query, 16, false);
     
-    encoder0.encodeInterface(stream, 24, false);
+    if (parameters == null) {
+      encoder0.encodeNullPointer(24, false);
+    } else {
+      var encoder1 = encoder0.encodePointerArray(parameters.length, 24, bindings.kUnspecifiedArrayLength);
+      for (int i0 = 0; i0 < parameters.length; ++i0) {
+        
+        encoder1.encodeUint8Array(parameters[i0], bindings.ArrayDataHeader.kHeaderSize + bindings.kPointerSize * i0, bindings.kNothingNullable, bindings.kUnspecifiedArrayLength);
+      }
+    }
+    
+    encoder0.encodeInterface(stream, 32, false);
   }
 
   String toString() {
     return "_SyncbaseDbExecParams("
            "name: $name" ", "
            "query: $query" ", "
+           "parameters: $parameters" ", "
            "stream: $stream" ")";
   }
 
@@ -9161,7 +9185,7 @@
   dynamic dbCreate(String name,Perms perms,[Function responseFactory = null]);
   dynamic dbDestroy(String name,[Function responseFactory = null]);
   dynamic dbExists(String name,[Function responseFactory = null]);
-  dynamic dbExec(String name,String query,Object stream,[Function responseFactory = null]);
+  dynamic dbExec(String name,String query,List<List<int>> parameters,Object stream,[Function responseFactory = null]);
   dynamic dbBeginBatch(String name,BatchOptions bo,[Function responseFactory = null]);
   dynamic dbCommit(String name,[Function responseFactory = null]);
   dynamic dbAbort(String name,[Function responseFactory = null]);
@@ -10225,10 +10249,11 @@
           -1,
           bindings.MessageHeader.kMessageExpectsResponse);
     }
-    dynamic dbExec(String name,String query,Object stream,[Function responseFactory = null]) {
+    dynamic dbExec(String name,String query,List<List<int>> parameters,Object stream,[Function responseFactory = null]) {
       var params = new _SyncbaseDbExecParams();
       params.name = name;
       params.query = query;
+      params.parameters = parameters;
       params.stream = stream;
       return _proxyImpl.sendMessageWithRequestId(
           params,
@@ -11146,7 +11171,7 @@
       case _Syncbase_dbExecName:
         var params = _SyncbaseDbExecParams.deserialize(
             message.payload);
-        var response = _impl.dbExec(params.name,params.query,params.stream,_SyncbaseDbExecResponseParamsFactory);
+        var response = _impl.dbExec(params.name,params.query,params.parameters,params.stream,_SyncbaseDbExecResponseParamsFactory);
         if (response is Future) {
           return response.then((response) {
             if (response != null) {
diff --git a/lib/src/nosql/abstract_database.dart b/lib/src/nosql/abstract_database.dart
index e2d7680..d56456b 100644
--- a/lib/src/nosql/abstract_database.dart
+++ b/lib/src/nosql/abstract_database.dart
@@ -22,7 +22,7 @@
   }
 
   // Executes a syncQL query.
-  Stream<mojom.Result> exec(String query) {
+  Stream<mojom.Result> exec(String query, [List<List<int>> params = const []]) {
     StreamController<mojom.Result> sc = new StreamController();
 
     mojom.ExecStreamStub stub = new mojom.ExecStreamStub.unbound();
@@ -31,7 +31,7 @@
     _ctx.unclosedStubsManager.register(stub);
 
     // Call dbExec asynchronously.
-    _ctx.syncbase.dbExec(fullName, query, stub).then((v) {
+    _ctx.syncbase.dbExec(fullName, query, params, stub).then((v) {
       // TODO(nlacasse): Same question regarding throwing behavior as TableScan.
       if (isError(v.err)) throw v.err;
     });
diff --git a/mojom/syncbase.mojom b/mojom/syncbase.mojom
index 5c7c136..359e252 100644
--- a/mojom/syncbase.mojom
+++ b/mojom/syncbase.mojom
@@ -129,7 +129,7 @@
   DbCreate(string name, Perms perms) => (Error err);
   DbDestroy(string name) => (Error err);
   DbExists(string name) => (Error err, bool exists);
-  DbExec(string name, string query, ExecStream stream) => (Error err);
+  DbExec(string name, string query, array<array<uint8>> parameters, ExecStream stream) => (Error err);
   DbBeginBatch(string name, BatchOptions? bo) => (Error err, string batch_suffix);
   DbCommit(string name) => (Error err);
   DbAbort(string name) => (Error err);
diff --git a/test/integration/syncbase_database_test.dart b/test/integration/syncbase_database_test.dart
index 7aef437..519abf9 100644
--- a/test/integration/syncbase_database_test.dart
+++ b/test/integration/syncbase_database_test.dart
@@ -217,5 +217,36 @@
     expect(decodeStrs(results[2].values), equals(['yyz', 'Toronto']));
   });
 
+  test('parameterized exec', () async {
+    var app = c.app(utils.uniqueName('app'));
+    await app.create(utils.emptyPerms());
+    var db = app.noSqlDatabase(utils.uniqueName('db'));
+    await db.create(utils.emptyPerms());
+    var table = db.table('airports');
+    await table.create(utils.emptyPerms());
+
+    await table.put('jfk', UTF8.encode('New York'));
+    await table.put('lga', UTF8.encode('New York'));
+    await table.put('ord', UTF8.encode('Chicago'));
+    await table.put('sfo', UTF8.encode('San Francisco'));
+
+    // TODO(ivanpi): Once VDL types are in, add parameterized key comparison
+    // and non-string parameters in where clause.
+    var query = 'select k as code, v as cityname from airports where v = ? or v = ?';
+    var resultStream = db.exec(query, [UTF8.encode('Chicago'), UTF8.encode('New York')]);
+
+    var results = await resultStream.toList();
+    // Expect column headers plus three rows.
+    expect(results.length, 4);
+
+    // Check column headers.
+    expect(decodeStrs(results[0].values), equals(['code', 'cityname']));
+
+    // Check rows.
+    expect(decodeStrs(results[1].values), equals(['jfk', 'New York']));
+    expect(decodeStrs(results[2].values), equals(['lga', 'New York']));
+    expect(decodeStrs(results[3].values), equals(['ord', 'Chicago']));
+  });
+
   // TODO(nlacasse): Test database.get/setPermissions.
 }