Skip to content

Commit 518421a

Browse files
committed
feat(execute): Use source parameter type instead of always typecasting to string (allows round trip of numbers, JSON etc.)
1 parent 513451f commit 518421a

File tree

5 files changed

+159
-44
lines changed

5 files changed

+159
-44
lines changed

lib/packets/execute.js

Lines changed: 91 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,73 @@ function leftPad(num, value) {
2121
return (pad + s).slice(-num);
2222
}
2323

24+
function toMysqlDateTime(date) {
25+
return [date.getFullYear(), date.getMonth() + 1, date.getDate()].join('-') + ' ' +
26+
[date.getHours(), date.getMinutes(), date.getSeconds()].join(':') + '.' +
27+
leftPad(3, date.getMilliseconds());
28+
}
29+
30+
function isJSON(value) {
31+
return Array.isArray(value) ||
32+
value.constructor === Object ||
33+
(typeof value.toJSON === 'function' && !Buffer.isBuffer(value));
34+
}
35+
36+
/**
37+
* Converts a value to an object describing type, String/Buffer representation and length
38+
* @param {*} value
39+
*/
40+
function toParameter(value, encoding) {
41+
var type = Types.VAR_STRING;
42+
var length;
43+
var fixed = false;
44+
if (value !== null) {
45+
switch (typeof value) {
46+
case 'number':
47+
type = Types.DOUBLE;
48+
fixed = true;
49+
var bufferValue = Buffer.allocUnsafe(8);
50+
bufferValue.writeDoubleLE(value, 0);
51+
value = bufferValue;
52+
break;
53+
54+
case 'boolean':
55+
type = Types.TINY;
56+
fixed = true;
57+
var bufferValue = Buffer.allocUnsafe(1);
58+
bufferValue.writeInt8(value | 0, 0);
59+
value = bufferValue;
60+
break;
61+
62+
case 'object':
63+
if (Object.prototype.toString.call(value) == '[object Date]') {
64+
value = toMysqlDateTime(value);
65+
} else if (isJSON(value)) {
66+
type = Types.JSON;
67+
value = JSON.stringify(value);
68+
}
69+
break;
70+
}
71+
} else {
72+
type = Types.NULL;
73+
value = '';
74+
}
75+
if (fixed) {
76+
length = value.length;
77+
} else {
78+
if (Buffer.isBuffer(value)) {
79+
length = Packet.lengthCodedNumberLength(value.length) + value.length;
80+
} else {
81+
value = value.toString();
82+
length = Packet.lengthCodedStringLength(value, encoding);
83+
}
84+
}
85+
return { type, value, length, fixed };
86+
}
87+
2488
Execute.prototype.toPacket = function() {
89+
var self = this;
90+
2591
// TODO: don't try to calculate packet length in advance, allocate some big buffer in advance (header + 256 bytes?)
2692
// and copy + reallocate if not enough
2793

@@ -32,33 +98,17 @@ Execute.prototype.toPacket = function() {
3298
// 9 + 1 - flags
3399
// 10 + 4 - iteration-count (always 1)
34100
var length = 14;
101+
var parameters;
35102
if (this.parameters && this.parameters.length > 0) {
36103
length += Math.floor((this.parameters.length + 7) / 8);
37104
length += 1; // new-params-bound-flag
38105
length += 2 * this.parameters.length; // type byte for each parameter if new-params-bound-flag is set
39-
for (i = 0; i < this.parameters.length; i++) {
40-
if (this.parameters[i] !== null) {
41-
if (
42-
Object.prototype.toString.call(this.parameters[i]) == '[object Date]'
43-
) {
44-
var d = this.parameters[i];
45-
// TODO: move to asMysqlDateTime()
46-
this.parameters[i] =
47-
[d.getFullYear(), d.getMonth() + 1, d.getDate()].join('-') +
48-
' ' +
49-
[d.getHours(), d.getMinutes(), d.getSeconds()].join(':') +
50-
'.' + leftPad(3, d.getMilliseconds())
51-
;
52-
}
53-
if (Buffer.isBuffer(this.parameters[i])) {
54-
length += Packet.lengthCodedNumberLength(this.parameters[i].length);
55-
length += this.parameters[i].length;
56-
} else {
57-
var str = this.parameters[i].toString();
58-
length += Packet.lengthCodedStringLength(str, this.encoding);
59-
}
60-
}
61-
}
106+
parameters = this.parameters.map(function (value) {
107+
return toParameter(value, self.encoding);
108+
});
109+
length += parameters.reduce(function (accumulator, parameter) {
110+
return accumulator + parameter.length;
111+
}, 0);
62112
}
63113

64114
var buffer = Buffer.allocUnsafe(length);
@@ -68,11 +118,11 @@ Execute.prototype.toPacket = function() {
68118
packet.writeInt32(this.id);
69119
packet.writeInt8(CursorType.NO_CURSOR); // flags
70120
packet.writeInt32(1); // iteration-count, always 1
71-
if (this.parameters && this.parameters.length > 0) {
121+
if (parameters) {
72122
var bitmap = 0;
73123
var bitValue = 1;
74-
for (i = 0; i < this.parameters.length; i++) {
75-
if (this.parameters[i] === null) {
124+
parameters.forEach(function (parameter) {
125+
if (parameter.type === Types.NULL) {
76126
bitmap += bitValue;
77127
}
78128
bitValue *= 2;
@@ -81,7 +131,7 @@ Execute.prototype.toPacket = function() {
81131
bitmap = 0;
82132
bitValue = 1;
83133
}
84-
}
134+
});
85135
if (bitValue != 1) {
86136
packet.writeInt8(bitmap);
87137
}
@@ -91,27 +141,24 @@ Execute.prototype.toPacket = function() {
91141
// if not, previous execution types are used (TODO prooflink)
92142
packet.writeInt8(1); // new-params-bound-flag
93143

94-
// TODO: don't typecast always to sting, use parameters type
95-
for (i = 0; i < this.parameters.length; i++) {
96-
if (this.parameters[i] !== null) {
97-
packet.writeInt16(Types.VAR_STRING);
98-
} else {
99-
packet.writeInt16(Types.NULL);
100-
}
101-
}
144+
// Write parameter types
145+
parameters.forEach(function (parameter) {
146+
packet.writeInt8(parameter.type); // field type
147+
packet.writeInt8(0); // parameter flag
148+
});
102149

103-
for (i = 0; i < this.parameters.length; i++) {
104-
if (this.parameters[i] !== null) {
105-
if (Buffer.isBuffer(this.parameters[i])) {
106-
packet.writeLengthCodedBuffer(this.parameters[i]);
150+
// Write parameter values
151+
parameters.forEach(function (parameter) {
152+
if (parameter.type !== Types.NULL) {
153+
if (parameter.fixed) {
154+
packet.writeBuffer(parameter.value);
155+
} else if (Buffer.isBuffer(parameter.value)) {
156+
packet.writeLengthCodedBuffer(parameter.value);
107157
} else {
108-
packet.writeLengthCodedString(
109-
this.parameters[i].toString(),
110-
this.encoding
111-
);
158+
packet.writeLengthCodedString(parameter.value, self.encoding);
112159
}
113160
}
114-
}
161+
});
115162
}
116163
return packet;
117164
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
var common = require('../../common');
2+
var connection = common.createConnection();
3+
var assert = require('assert');
4+
5+
var rows, fields;
6+
connection.execute('SELECT ? AS trueValue, ? AS falseValue', [true, false], function(err, _rows, _fields) {
7+
if (err) {
8+
throw err;
9+
}
10+
rows = _rows;
11+
fields = _fields;
12+
connection.end();
13+
});
14+
15+
process.on('exit', function() {
16+
assert.deepEqual(rows, [{ trueValue: 1, falseValue: 0 }]);
17+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
var common = require('../../common');
2+
var connection = common.createConnection();
3+
var assert = require('assert');
4+
5+
var rows, fields;
6+
connection.execute('SELECT ? AS result', [{ a: 1, b: true, c: ["foo"] }], function(err, _rows, _fields) {
7+
if (err) {
8+
throw err;
9+
}
10+
rows = _rows;
11+
fields = _fields;
12+
connection.end();
13+
});
14+
15+
process.on('exit', function() {
16+
assert.deepEqual(rows, [{ result: { a: 1, b: true, c: ["foo"] } }]);
17+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
var common = require('../../common');
2+
var connection = common.createConnection();
3+
var assert = require('assert');
4+
5+
var rows, fields;
6+
connection.execute('SELECT ? AS firstValue, ? AS nullValue, ? AS lastValue', ['foo', null, 'bar'], function(err, _rows, _fields) {
7+
if (err) {
8+
throw err;
9+
}
10+
rows = _rows;
11+
fields = _fields;
12+
connection.end();
13+
});
14+
15+
process.on('exit', function() {
16+
assert.deepEqual(rows, [{ firstValue: 'foo', nullValue: null, lastValue: 'bar' }]);
17+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
var common = require('../../common');
2+
var connection = common.createConnection();
3+
var assert = require('assert');
4+
5+
var rows, fields;
6+
connection.execute('SELECT ? AS zeroValue, ? AS positiveValue, ? AS negativeValue, ? AS decimalValue', [0, 123, -123, 1.25], function(err, _rows, _fields) {
7+
if (err) {
8+
throw err;
9+
}
10+
rows = _rows;
11+
fields = _fields;
12+
connection.end();
13+
});
14+
15+
process.on('exit', function() {
16+
assert.deepEqual(rows, [{ zeroValue: 0, positiveValue: 123, negativeValue: -123, decimalValue: 1.25 }]);
17+
});

0 commit comments

Comments
 (0)