syncbase/exec: Expose support for parameterized queries.
Allow Syncbase to accept parameterized queries, with '?' placeholders
in the where clause.
MultiPart: 4/5
Change-Id: Icde606faf4806bcb2da9879376e0eecdc9f92861
diff --git a/src/gen-vdl/v.io/v23/services/syncbase/nosql/index.js b/src/gen-vdl/v.io/v23/services/syncbase/nosql/index.js
index ca80fa4..b0fd022 100644
--- a/src/gen-vdl/v.io/v23/services/syncbase/nosql/index.js
+++ b/src/gen-vdl/v.io/v23/services/syncbase/nosql/index.js
@@ -1022,7 +1022,7 @@
};
-Database.prototype.exec = function(ctx, serverCall, schemaVersion, query) {
+Database.prototype.exec = function(ctx, serverCall, schemaVersion, query, params) {
throw new Error('Method Exec not implemented');
};
@@ -1297,7 +1297,7 @@
{
name: 'Exec',
- doc: "// Exec executes a syncQL query and returns all results as specified by in the\n// query's select/delete statement. Concurrency semantics are documented in model.go.",
+ doc: "// Exec executes a syncQL query with positional parameters and returns all\n// results as specified by the query's select/delete statement.\n// Concurrency semantics are documented in model.go.",
inArgs: [{
name: 'schemaVersion',
doc: "",
@@ -1308,6 +1308,11 @@
doc: "",
type: vdl.types.STRING
},
+ {
+ name: 'params',
+ doc: "",
+ type: _type9
+ },
],
outArgs: [],
inStream: null,
diff --git a/src/nosql/abstract-database.js b/src/nosql/abstract-database.js
index 5a15028..50bca14 100644
--- a/src/nosql/abstract-database.js
+++ b/src/nosql/abstract-database.js
@@ -111,6 +111,11 @@
/**
* Executes a syncQL query.
*
+ * If the query is parameterized, paramValues must contain a value for each '?'
+ * placeholder in the query. If there are no placeholders, paramValues must be
+ * empty or omitted. paramTypes should be provided in addition to paramValues
+ * for paramValue elements that lack VOM types, including JS primitives.
+ *
* Returns a stream of rows. The first row contains an array of headers (i.e.
* column names). Subsequent rows contain an array of values for each row that
* matches the query. The number of values returned in each row will match the
@@ -120,7 +125,7 @@
* time of the RPC, and will not reflect subsequent writes to keys not yet
* reached by the stream.
*
- * NOTE(nlacasse): The Go client library returns the headers seperately from
+ * NOTE(nlacasse): The Go client library returns the headers separately from
* the stream. We could potentially do something similar in JavaScript, by
* pulling the headers off the stream and passing them to the callback.
* However, by Vanadium JS convention the callback gets called at the *end* of
@@ -136,17 +141,47 @@
*
* @param {module:vanadium.context.Context} ctx Vanadium context.
* @param {string} query Query string.
+ * @param {Object[]} [paramValues] Query parameters, one per '?' placeholder in
+ * the query.
+ * @param {module:vanadium.vdl.Type[]} [paramTypes] Query parameter types, one
+ * per value in paramValues. Not required if paramValues are VDL typed or if
+ * values being queried are JSValues.
* @param {function} cb Callback.
* @returns {stream} Stream of rows.
*/
-AbstractDatabase.prototype.exec = function(ctx, query, cb) {
+AbstractDatabase.prototype.exec = function(ctx, query, paramValues, paramTypes,
+ cb) {
+ if (typeof cb === 'undefined' && typeof paramValues === 'function') {
+ cb = paramValues;
+ paramValues = undefined;
+ paramTypes = undefined;
+ }
+ if (typeof cb === 'undefined' && typeof paramTypes === 'function') {
+ cb = paramTypes;
+ paramTypes = undefined;
+ }
+
+ var params;
+ if (typeof paramValues !== 'undefined') {
+ paramTypes = paramTypes || [];
+ try {
+ params = paramValues.map(function(param, i) {
+ var type = paramTypes[i] || vanadium.vdl.types.ANY;
+ return vanadium.vdl.canonicalize.value(param, type);
+ });
+ } catch (e) {
+ return cb(e);
+ }
+ }
+
var streamUnwrapper = through2({
objectMode: true
}, function(res, enc, cb) {
return cb(null, res.map(unwrap));
});
- var stream = this._wire(ctx).exec(ctx, this.schemaVersion, query, cb).stream;
+ var stream = this._wire(ctx).exec(ctx, this.schemaVersion,
+ query, params, cb).stream;
var decodedStream = stream.pipe(streamUnwrapper);
stream.on('error', function(err) {
diff --git a/test/integration/test-database.js b/test/integration/test-database.js
index d69910c..48076cc 100644
--- a/test/integration/test-database.js
+++ b/test/integration/test-database.js
@@ -414,6 +414,22 @@
['Moe', moe],
]
},
+ {
+ q: 'select k, v from %s where k = ? or v.Age = ?',
+ paramValues: [
+ 'Moe',
+ 38,
+ ],
+ paramTypes: [
+ vdl.types.STRING,
+ vdl.types.INT32,
+ ],
+ want: [
+ ['k', 'v'],
+ ['Homer', homer],
+ ['Moe', moe],
+ ]
+ },
];
putPeople();
@@ -443,7 +459,9 @@
}
async.forEachSeries(testCases, function(testCase, cb) {
- assertExec(format(testCase.q, table.name), testCase.want, cb);
+ assertExec(format(testCase.q, table.name),
+ testCase.paramValues, testCase.paramTypes,
+ testCase.want, cb);
}, end);
}
@@ -453,11 +471,14 @@
}
// Assert that query 'q' returns the rows in 'want'.
- function assertExec(q, want, cb) {
- var stream = db.exec(ctx, q, function(err) {
+ function assertExec(q, paramValues, paramTypes, want, cb) {
+ var testCb = function(err) {
t.error(err);
cb();
- });
+ };
+ var stream = paramValues === undefined ?
+ db.exec(ctx, q, testCb) :
+ db.exec(ctx, q, paramValues, paramTypes, testCb);
stream.on('error', t.error);
toArray(stream, function(err, got) {
t.error(err);