Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/commands/execute.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class Execute extends Command {
this.options = Object.assign({}, connection.config, this._executeOptions);
this._setTimeout();
const executePacket = new Packets.Execute(
this.statement.id,
this.statement,
this.parameters,
connection.config.charsetNumber,
connection.config.timezone
Expand Down
111 changes: 82 additions & 29 deletions lib/packets/execute.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const CursorType = require('../constants/cursor');
const CommandCodes = require('../constants/commands');
const FieldFlags = require('../constants/field_flags.js');
const Types = require('../constants/types');
const Packet = require('../packets/packet');
const CharsetToEncoding = require('../constants/charset_encodings.js');
Expand All @@ -18,49 +19,96 @@ function isJSON(value) {
* Converts a value to an object describing type, String/Buffer representation and length
* @param {*} value
*/
function toParameter(value, encoding, timezone) {
let type = Types.VAR_STRING;
function toParameter(value, encoding, timezone, parameterHints) {
let type = parameterHints.columnType;
let length;
let writer = function(value) {
// eslint-disable-next-line no-invalid-this
return Packet.prototype.writeLengthCodedString.call(this, value, encoding);
};
const unsigned = parameterHints.flags & FieldFlags.UNSIGNED;

if (value !== null) {
switch (typeof value) {
case 'undefined':
throw new TypeError('Bind parameters must not contain undefined');
if (typeof value === 'undefined') {
throw new TypeError('Bind parameters must not contain undefined');
}

case 'number':
type = Types.DOUBLE;
length = 8;
writer = Packet.prototype.writeDouble;
break;
// currently exception for Date and plain object input parameters - even if statement expects another type we'll still send date/stringified json
// this might change in the future
if (Object.prototype.toString.call(value) === '[object Date]') {
type = Types.DATETIME;
length = 12;
writer = function(value) {
// eslint-disable-next-line no-invalid-this
return Packet.prototype.writeDate.call(this, value, timezone);
};
return { value, type, length, writer };
}

if (isJSON(value)) {
value = JSON.stringify(value);
type = Types.JSON;
length = Packet.lengthCodedStringLength(value, encoding);
return { value, type, length, writer };
}

case 'boolean':
if (Buffer.isBuffer(value)) {
length = Packet.lengthCodedNumberLength(value.length) + value.length;
writer = Packet.prototype.writeLengthCodedBuffer;
type = Types.VAR_STRING;
return { value, type, length, writer };
}

switch(parameterHints.columnType) {
case Types.TINY:
value = value | 0;
type = Types.TINY;
length = 1;
writer = Packet.prototype.writeInt8;
writer = unsigned ? Packet.prototype.writeInt8 : Packet.prototype.writeUInt8;
break;

case 'object':
if (Object.prototype.toString.call(value) === '[object Date]') {
type = Types.DATETIME;
case Types.YEAR:
case Types.SHORT:
value = value | 0;
length = 2;
writer = unsigned ? Packet.prototype.writeInt16 : Packet.prototype.writeSInt16;
break;
case Types.LONG:
case Types.INT24: // in binary protocol int24 is encoded in 4 bytes int32
value = value | 0;
length = 4;
writer = unsigned ? Packet.prototype.writeInt32() : Packet.prototype.writeSInt32();
break;
case Types.DATETIME:
if (!(Object.prototype.toString.call(value) === '[object Date]')) {
length = 12;
writer = function(value) {
// eslint-disable-next-line no-invalid-this
return Packet.prototype.writeDate.call(this, value, timezone);
};
} else if (isJSON(value)) {
value = JSON.stringify(value);
type = Types.JSON;
} else if (Buffer.isBuffer(value)) {
length = Packet.lengthCodedNumberLength(value.length) + value.length;
writer = Packet.prototype.writeLengthCodedBuffer;
} else {
// if parameter is not a date serialize it as string by default
value = value.toString();
type = Types.VAR_STRING;
}
break;

case Types.FLOAT:
length = 4;
writer = Packet.prototype.writeFloat;
break;
case Types.DOUBLE:
length = 8;
writer = Packet.prototype.writeDouble;
break;
case Types.JSON:
value = JSON.stringify(value);
type = parameterHints.type;
break;
case Types.LONGLONG:
// this should also cover anything that serializes to a string ( BigInt etc )
type = Types.VAR_STRING;
value = value.toString();
break;
default:
type = Types.VAR_STRING;
value = value.toString();
}
} else {
Expand All @@ -74,14 +122,19 @@ function toParameter(value, encoding, timezone) {
}

class Execute {
constructor(id, parameters, charsetNumber, timezone) {
this.id = id;
constructor(statement, parameters, charsetNumber, timezone) {
this.statement = statement;
this.parameters = parameters;
this.encoding = CharsetToEncoding[charsetNumber];
this.timezone = timezone;
}

toPacket() {

if ((this.statement.parameters.length > 0 && !this.parameters) || (this.parameters && this.parameters.length !== this.statement.parameters.length)) {
throw new TypeError(`Incorrect number of bind parameters, expected ${this.statement.parameters.length} but supplied ${this.parameters.length}`);
}

// TODO: don't try to calculate packet length in advance, allocate some big buffer in advance (header + 256 bytes?)
// and copy + reallocate if not enough
// 0 + 4 - length, seqId
Expand All @@ -95,8 +148,8 @@ class Execute {
length += Math.floor((this.parameters.length + 7) / 8);
length += 1; // new-params-bound-flag
length += 2 * this.parameters.length; // type byte for each parameter if new-params-bound-flag is set
parameters = this.parameters.map(value =>
toParameter(value, this.encoding, this.timezone)
parameters = this.parameters.map((value, index) =>
toParameter(value, this.encoding, this.timezone, this.statement.parameters[index])
);
length += parameters.reduce(
(accumulator, parameter) => accumulator + parameter.length,
Expand All @@ -107,7 +160,7 @@ class Execute {
const packet = new Packet(0, buffer, 0, length);
packet.offset = 4;
packet.writeInt8(CommandCodes.STMT_EXECUTE);
packet.writeInt32(this.id);
packet.writeInt32(this.statement.id);
packet.writeInt8(CursorType.NO_CURSOR); // flags
packet.writeInt32(1); // iteration-count, always 1
if (parameters) {
Expand Down
2 changes: 1 addition & 1 deletion test/integration/connection/test-execute-bind-boolean.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ connection.execute(
);

process.on('exit', () => {
assert.deepEqual(rows, [{ trueValue: 1, falseValue: 0 }]);
assert.deepEqual(rows, [{ trueValue: 'true', falseValue: 'false' }]);
});
4 changes: 2 additions & 2 deletions test/integration/connection/test-execute-type-casting.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ connection.query('select 1', waitConnectErr => {

let row;
connection.execute(
`SELECT * FROM ${table} WHERE id = ?;`,
[1],
`SELECT * FROM ${table} WHERE id = ? LIMIT ?`,
[1, 1],
(err, rows) => {
if (err) {
throw err;
Expand Down
2 changes: 1 addition & 1 deletion test/integration/connection/test-signed-tinyint.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ connection.query(
connection.query('INSERT INTO signed_ints values (-3, -120, 500)');
connection.query('INSERT INTO signed_ints values (3, -110, -500)');

connection.execute('SELECT * from signed_ints', [5], (err, _rows) => {
connection.execute('SELECT * from signed_ints', [], (err, _rows) => {
if (err) {
throw err;
}
Expand Down