Skip to content

Commit bafa1a2

Browse files
callmehiphopstephenplusplus
authored andcommitted
fix query param types bug (#44)
1 parent ca0f55b commit bafa1a2

File tree

6 files changed

+339
-575
lines changed

6 files changed

+339
-575
lines changed

src/codec.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,3 +274,76 @@ var TYPES = [
274274
];
275275

276276
codec.TYPES = TYPES;
277+
278+
/**
279+
* Encodes a ExecuteSqlRequest object into the correct format.
280+
*
281+
* @private
282+
*
283+
* @param {object} query The query object.
284+
* @param {object} [query.params] A map of parameter name to values.
285+
* @param {object} [query.types] A map of parameter types.
286+
* @returns {object}
287+
*/
288+
function encodeQuery(query) {
289+
if (query.params) {
290+
let fields = {};
291+
292+
if (!query.types) {
293+
query.types = {};
294+
}
295+
296+
for (let prop in query.params) {
297+
let field = query.params[prop];
298+
299+
if (!query.types[prop]) {
300+
query.types[prop] = codec.getType(field);
301+
}
302+
303+
fields[prop] = codec.encode(field);
304+
}
305+
306+
query.params = {fields};
307+
}
308+
309+
if (query.types) {
310+
let formattedTypes = {};
311+
312+
for (let prop in query.types) {
313+
let type = query.types[prop];
314+
let childType;
315+
let child;
316+
317+
// if a type is an ARRAY, then we'll accept an object specifying
318+
// the type and the child type
319+
if (is.object(type)) {
320+
childType = type.child;
321+
child = codec.TYPES.indexOf(childType);
322+
type = type.type;
323+
}
324+
325+
let code = codec.TYPES.indexOf(type);
326+
327+
if (code === -1) {
328+
code = 0; // unspecified
329+
}
330+
331+
formattedTypes[prop] = {code};
332+
333+
if (child === -1) {
334+
child = 0; // unspecified
335+
}
336+
337+
if (is.number(child)) {
338+
formattedTypes[prop].arrayElementType = {code: child};
339+
}
340+
}
341+
342+
delete query.types;
343+
query.paramTypes = formattedTypes;
344+
}
345+
346+
return query;
347+
}
348+
349+
codec.encodeQuery = encodeQuery;

src/database.js

Lines changed: 5 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,8 @@ Database.prototype.getTransaction = function(options, callback) {
610610
* @param {string|object} query A SQL query or query object. See an
611611
* [ExecuteSqlRequest](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.v1#google.spanner.v1.ExecuteSqlRequest)
612612
* object.
613+
* @param {object} [query.params] A map of parameter name to values.
614+
* @param {object} [query.types] A map of parameter types.
613615
* @param {DatabaseRunRequest} [options] [Transaction options](https://cloud.google.com/spanner/docs/timestamp-bounds).
614616
* @param {RunCallback} [callback] Callback function.
615617
* @returns {Promise<RunResponse>}
@@ -737,6 +739,7 @@ Database.prototype.run = function(query, options, callback) {
737739
* [ExecuteSqlRequest](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.v1#google.spanner.v1.ExecuteSqlRequest)
738740
* object.
739741
* @param {object} [query.params] A map of parameter name to values.
742+
* @param {object} [query.types] A map of parameter types.
740743
* @param {DatabaseRunRequest} [options] [Transaction options](https://cloud.google.com/spanner/docs/timestamp-bounds).
741744
* @returns {ReadableStream} A readable stream that emits rows.
742745
*
@@ -837,67 +840,7 @@ Database.prototype.runStream = function(query, options) {
837840
};
838841
}
839842

840-
var reqOpts = extend({}, query, {
841-
session: this.formattedName_,
842-
});
843-
844-
var fields = {};
845-
var prop;
846-
847-
if (reqOpts.params) {
848-
reqOpts.types = reqOpts.types || {};
849-
850-
for (prop in reqOpts.params) {
851-
var field = reqOpts.params[prop];
852-
853-
if (!reqOpts.types[prop]) {
854-
reqOpts.types[prop] = codec.getType(field);
855-
}
856-
857-
fields[prop] = codec.encode(field);
858-
}
859-
860-
reqOpts.params = {
861-
fields: fields,
862-
};
863-
}
864-
865-
if (reqOpts.types) {
866-
var types = {};
867-
868-
for (prop in reqOpts.types) {
869-
var type = reqOpts.types[prop];
870-
var childType;
871-
var child;
872-
873-
// if a type is an ARRAY, then we'll accept an object specifying
874-
// the type and the child type
875-
if (is.object(type)) {
876-
childType = type.child;
877-
child = codec.TYPES.indexOf(childType);
878-
type = type.type;
879-
}
880-
881-
var code = codec.TYPES.indexOf(type);
882-
883-
if (code === -1) {
884-
throw new Error('Unknown param type: ' + type);
885-
}
886-
887-
types[prop] = {code: code};
888-
889-
if (child === -1) {
890-
throw new Error('Unknown param type: ' + childType);
891-
}
892-
893-
if (is.number(child)) {
894-
types[prop].arrayElementType = {code: child};
895-
}
896-
}
897-
898-
reqOpts.paramTypes = types;
899-
delete reqOpts.types;
900-
}
843+
var reqOpts = codec.encodeQuery(query);
901844

902845
if (options) {
903846
reqOpts.transaction = {
@@ -911,7 +854,7 @@ Database.prototype.runStream = function(query, options) {
911854
return self.pool_.requestStream({
912855
client: 'SpannerClient',
913856
method: 'executeStreamingSql',
914-
reqOpts: extend(reqOpts, {resumeToken: resumeToken}),
857+
reqOpts: extend(reqOpts, {resumeToken}),
915858
});
916859
}
917860

src/transaction.js

Lines changed: 29 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,8 @@ Transaction.prototype.rollback = function(callback) {
536536
* @param {string|object} query A SQL query or
537537
* [`ExecuteSqlRequest`](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.v1#google.spanner.v1.ExecuteSqlRequest)
538538
* object.
539+
* @param {object} [query.params] A map of parameter name to values.
540+
* @param {object} [query.types] A map of parameter types.
539541
* @param {RunCallback} [callback] Callback function.
540542
* @returns {Promise<RunResponse>}
541543
*
@@ -585,6 +587,30 @@ Transaction.prototype.rollback = function(callback) {
585587
*
586588
* transaction.run(query, function(err, rows) {});
587589
* });
590+
*
591+
* //-
592+
* // If you need to enforce a specific param type, a types map can be provided.
593+
* // This is typically useful if your param value can be null.
594+
* //-
595+
* database.runTransaction(function(err, transaction) {
596+
* if (err) {
597+
* // Error handling omitted.
598+
* }
599+
*
600+
* const query = {
601+
* sql: 'SELECT * FROM Singers WHERE name = @name AND id = @id',
602+
* params: {
603+
* id: spanner.int(8),
604+
* name: null
605+
* },
606+
* types: {
607+
* id: 'int64',
608+
* name: 'string'
609+
* }
610+
* };
611+
*
612+
* transaction.run(query, function(err, rows) {});
613+
* });
588614
*/
589615
Transaction.prototype.run = function(query, callback) {
590616
var rows = [];
@@ -612,6 +638,8 @@ Transaction.prototype.run = function(query, callback) {
612638
* @param {string|object} query - A SQL query or
613639
* [`ExecuteSqlRequest`](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.v1#google.spanner.v1.ExecuteSqlRequest)
614640
* object.
641+
* @param {object} [query.params] A map of parameter name to values.
642+
* @param {object} [query.types] A map of parameter types.
615643
* @returns {ReadableStream}
616644
*
617645
* @example
@@ -691,65 +719,10 @@ Transaction.prototype.runStream = function(query) {
691719
var reqOpts = extend(
692720
{
693721
transaction: {},
694-
session: this.formattedName_,
695722
},
696-
query
723+
codec.encodeQuery(query)
697724
);
698725

699-
var fields = {};
700-
var prop;
701-
702-
if (reqOpts.params) {
703-
reqOpts.types = reqOpts.types || {};
704-
705-
for (prop in reqOpts.params) {
706-
var field = reqOpts.params[prop];
707-
708-
if (!reqOpts.types[prop]) {
709-
reqOpts.types[prop] = codec.getType(field);
710-
}
711-
712-
fields[prop] = codec.encode(field);
713-
}
714-
715-
reqOpts.params = {
716-
fields: fields,
717-
};
718-
}
719-
720-
if (reqOpts.types) {
721-
var types = {};
722-
723-
for (prop in reqOpts.types) {
724-
var type = reqOpts.types[prop];
725-
var child;
726-
727-
if (is.object(type)) {
728-
child = codec.TYPES.indexOf(type.child);
729-
type = type.type;
730-
}
731-
732-
var code = codec.TYPES.indexOf(type);
733-
734-
if (code === -1) {
735-
code = 0; // unspecified
736-
}
737-
738-
types[prop] = {code: code};
739-
740-
if (child === -1) {
741-
child = 0; // unspecified
742-
}
743-
744-
if (is.number(child)) {
745-
types[prop].arrayElementType = {code: child};
746-
}
747-
}
748-
749-
reqOpts.paramTypes = types;
750-
delete reqOpts.types;
751-
}
752-
753726
if (this.id) {
754727
reqOpts.transaction.id = this.id;
755728
} else {

0 commit comments

Comments
 (0)