Skip to content

Commit 0b87f38

Browse files
committed
feat(execute): Tidy up source param types, move toMysqlDateBuffer to packet.js, catch undefined params earlier
1 parent 7c55a07 commit 0b87f38

File tree

6 files changed

+95
-52
lines changed

6 files changed

+95
-52
lines changed

documentation/Prepared-Statements.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,18 @@ Note that you should not use statement after connection reset (`changeUser()` or
3636
# Configuration
3737

3838
`maxPreparedStatements` : We keep the cached statements in a [lru-cache](https://github.com/isaacs/node-lru-cache). Default size is `16000` but you can use this option to override it. Any statements that are dropped from cache will be `closed`.
39+
40+
# Serialization of bind parameters
41+
42+
The bind parameter values passed to `execute` are serialized JS -> MySQL as:
43+
44+
* `null` -> `NULL`
45+
* `number` -> `DOUBLE`
46+
* `boolean` -> `TINY` (0 for false, 1 for true)
47+
* `object` -> depending on prototype:
48+
* `Date` -> `DATETIME`
49+
* `JSON` like object - `JSON`
50+
* `Buffer` -> `VAR_STRING`
51+
* Other -> `VAR_STRING`
52+
53+
Passing in `undefined` or a `function` will result in an error.

lib/connection.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,18 @@ Connection.prototype.execute = function execute(sql, values, cb) {
715715
}
716716
this._resolveNamedPlaceholders(options);
717717

718+
// check for values containing undefined
719+
if (options.values) {
720+
options.values.forEach(function(val) {
721+
if (val === undefined) {
722+
throw new TypeError('Bind parameters must not contain undefined. To pass SQL NULL specify JS null');
723+
}
724+
if (typeof val === 'function') {
725+
throw new TypeError('Bind parameters must not contain function(s). To pass the body of a function as a string call .toString() first')
726+
}
727+
});
728+
}
729+
718730
var executeCommand = new Commands.Execute(options, cb);
719731
var prepareCommand = new Commands.Prepare(options, function(err, stmt) {
720732
if (err) {

lib/packets/execute.js

Lines changed: 23 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,74 +17,59 @@ function isJSON(value) {
1717
(typeof value.toJSON === 'function' && !Buffer.isBuffer(value));
1818
}
1919

20-
function toMysqlDateBuffer(value) {
21-
var bufferValue = Buffer.allocUnsafe(12);
22-
bufferValue.writeUInt8(11, 0);
23-
bufferValue.writeUInt16LE(value.getFullYear(), 1);
24-
bufferValue.writeUInt8(value.getMonth() + 1, 3);
25-
bufferValue.writeUInt8(value.getDate(), 4);
26-
bufferValue.writeUInt8(value.getHours(), 5);
27-
bufferValue.writeUInt8(value.getMinutes(), 6);
28-
bufferValue.writeUInt8(value.getSeconds(), 7);
29-
bufferValue.writeUInt32LE(value.getMilliseconds() * 1000, 8);
30-
return bufferValue;
31-
}
32-
3320
/**
3421
* Converts a value to an object describing type, String/Buffer representation and length
3522
* @param {*} value
3623
*/
3724
function toParameter(value, encoding) {
3825
var type = Types.VAR_STRING;
3926
var length;
40-
var fixed = false;
27+
var writer = function (value) {
28+
return Packet.prototype.writeLengthCodedString.call(this, value, encoding)
29+
}
4130
if (value !== null) {
4231
switch (typeof value) {
4332
case 'undefined':
4433
throw new TypeError('Bind parameters must not contain undefined');
4534

4635
case 'number':
4736
type = Types.DOUBLE;
48-
fixed = true;
49-
var bufferValue = Buffer.allocUnsafe(8);
50-
bufferValue.writeDoubleLE(value, 0);
51-
value = bufferValue;
37+
length = 8;
38+
writer = Packet.prototype.writeDouble;
5239
break;
5340

5441
case 'boolean':
42+
value = value | 0;
5543
type = Types.TINY;
56-
fixed = true;
57-
var bufferValue = Buffer.allocUnsafe(1);
58-
bufferValue.writeInt8(value | 0, 0);
59-
value = bufferValue;
44+
length = 1;
45+
writer = Packet.prototype.writeInt8;
6046
break;
6147

6248
case 'object':
6349
if (Object.prototype.toString.call(value) == '[object Date]') {
6450
type = Types.DATETIME;
65-
fixed = true;
66-
value = toMysqlDateBuffer(value);
51+
length = 12;
52+
writer = Packet.prototype.writeDate
6753
} else if (isJSON(value)) {
68-
type = Types.JSON;
6954
value = JSON.stringify(value);
55+
type = Types.JSON;
56+
} else if (Buffer.isBuffer(value)) {
57+
length = Packet.lengthCodedNumberLength(value.length) + value.length;
58+
writer = Packet.prototype.writeLengthCodedBuffer
7059
}
7160
break;
61+
62+
default:
63+
value = value.toString();
7264
}
7365
} else {
66+
value = ''
7467
type = Types.NULL;
75-
value = '';
7668
}
77-
if (fixed) {
78-
length = value.length;
79-
} else {
80-
if (Buffer.isBuffer(value)) {
81-
length = Packet.lengthCodedNumberLength(value.length) + value.length;
82-
} else {
83-
value = value.toString();
84-
length = Packet.lengthCodedStringLength(value, encoding);
85-
}
69+
if (!length) {
70+
length = Packet.lengthCodedStringLength(value, encoding);
8671
}
87-
return { type, value, length, fixed };
72+
return { value, type, length, writer };
8873
}
8974

9075
Execute.prototype.toPacket = function() {
@@ -151,14 +136,8 @@ Execute.prototype.toPacket = function() {
151136

152137
// Write parameter values
153138
parameters.forEach(function (parameter) {
154-
if (parameter.type !== Types.NULL) {
155-
if (parameter.fixed) {
156-
packet.writeBuffer(parameter.value);
157-
} else if (Buffer.isBuffer(parameter.value)) {
158-
packet.writeLengthCodedBuffer(parameter.value);
159-
} else {
160-
packet.writeLengthCodedString(parameter.value, self.encoding);
161-
}
139+
if (parameter.type !== Types.NULL) {
140+
parameter.writer.call(packet, parameter.value)
162141
}
163142
});
164143
}

lib/packets/packet.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,11 @@ Packet.prototype.writeInt8 = function(n) {
760760
this.offset++;
761761
};
762762

763+
Packet.prototype.writeDouble = function(n) {
764+
this.buffer.writeDoubleLE(n, this.offset);
765+
this.offset += 8;
766+
}
767+
763768
Packet.prototype.writeBuffer = function(b) {
764769
b.copy(this.buffer, this.offset);
765770
this.offset += b.length;
@@ -839,6 +844,18 @@ Packet.prototype.writeLengthCodedNumber = function(n) {
839844
return this.offset;
840845
};
841846

847+
Packet.prototype.writeDate = function(d) {
848+
this.buffer.writeUInt8(11, this.offset);
849+
this.buffer.writeUInt16LE(d.getFullYear(), this.offset + 1);
850+
this.buffer.writeUInt8(d.getMonth() + 1, this.offset + 3);
851+
this.buffer.writeUInt8(d.getDate(), this.offset + 4);
852+
this.buffer.writeUInt8(d.getHours(), this.offset + 5);
853+
this.buffer.writeUInt8(d.getMinutes(), this.offset + 6);
854+
this.buffer.writeUInt8(d.getSeconds(), this.offset + 7);
855+
this.buffer.writeUInt32LE(d.getMilliseconds() * 1000, this.offset + 8);
856+
this.offset += 12;
857+
}
858+
842859
Packet.prototype.writeHeader = function(sequenceId) {
843860
var offset = this.offset;
844861
this.offset = 0;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
var common = require('../../common');
2+
var connection = common.createConnection();
3+
var assert = require('assert');
4+
5+
var error = null;
6+
7+
try {
8+
connection.execute('SELECT ? AS result', [function () {}], function(err, _rows) { });
9+
} catch (err) {
10+
error = err
11+
connection.end();
12+
}
13+
14+
process.on('exit', function() {
15+
assert.equal(error.name, 'TypeError');
16+
if (!error.message.match(/function/)) {
17+
assert.fail('Expected error.message to contain \'function\'')
18+
}
19+
});

test/integration/connection/test-execute-bind-undefined.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ var connection = common.createConnection();
33
var assert = require('assert');
44

55
var error = null;
6-
var foo = {};
76

8-
connection.execute('SELECT ? AS result', [foo.bar], function(err, _rows) { });
9-
10-
// TODO: Needs to be a better way of catching this exception
11-
process.on('uncaughtException', (err) => {
12-
error = err;
13-
process.exit(0);
14-
});
7+
try {
8+
connection.execute('SELECT ? AS result', [undefined], function(err, _rows) { });
9+
} catch (err) {
10+
error = err
11+
connection.end();
12+
}
1513

1614
process.on('exit', function() {
1715
assert.equal(error.name, 'TypeError');
16+
if (!error.message.match(/undefined/)) {
17+
assert.fail('Expected error.message to contain \'undefined\'')
18+
}
1819
});

0 commit comments

Comments
 (0)