Skip to content
Merged
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
13 changes: 0 additions & 13 deletions documentation/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,6 @@ You need to check corresponding field's zeroFill flag and convert to string manu
```
**Note :** *This option could lose precision on the number as Javascript Number is a Float!*

- `timezone` connection option is not supported by `Node-MySQL2`. You can emulate this by using `typeCast` option instead:
```javascript
const config = {
//...
typeCast: function (field, next) {
if (field.type === 'DATETIME') {
return new Date(`${field.string()}Z`) // can be 'Z' for UTC or an offset in the form '+HH:MM' or '-HH:MM'
}
return next();
}
}
```

## Other Resources

- [Wire protocol documentation](http://dev.mysql.com/doc/internals/en/client-server-protocol.html)
Expand Down
3 changes: 2 additions & 1 deletion lib/commands/execute.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ class Execute extends Command {
const executePacket = new Packets.Execute(
this.statement.id,
this.parameters,
connection.config.charsetNumber
connection.config.charsetNumber,
connection.config.timezone
);
//For reasons why this try-catch is here, please see
// https://github.com/sidorares/node-mysql2/pull/689
Expand Down
20 changes: 18 additions & 2 deletions lib/connection_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class ConnectionConfig {
// REVIEW: Should this be emitted somehow?
// eslint-disable-next-line no-console
console.error(
`Ignoring invalid configuration option passed to Connection: ${key}. This is currently a warning, but in future versions of MySQL2, an error will be thrown if you pass an invalid configuration options to a Connection`
`Ignoring invalid configuration option passed to Connection: ${key}. This is currently a warning, but in future versions of MySQL2, an error will be thrown if you pass an invalid configuration option to a Connection`
);
}
}
Expand All @@ -91,7 +91,23 @@ class ConnectionConfig {
this.debug = options.debug;
this.trace = options.trace !== false;
this.stringifyObjects = options.stringifyObjects || false;
this.timezone = options.timezone || 'local';
if (
options.timezone &&
!/^(?:local|Z|[ +-]\d\d:\d\d)$/.test(options.timezone)
) {
// strictly supports timezones specified by mysqljs/mysql:
// https://github.com/mysqljs/mysql#user-content-connection-options
// eslint-disable-next-line no-console
console.error(
`Ignoring invalid timezone passed to Connection: ${
options.timezone
}. This is currently a warning, but in future versions of MySQL2, an error will be thrown if you pass an invalid configuration option to a Connection`
);
// SqlStrings falls back to UTC on invalid timezone
this.timezone = 'Z';
} else {
this.timezone = options.timezone || 'local';
}
this.queryFormat = options.queryFormat;
this.pool = options.pool || undefined;
this.ssl =
Expand Down
12 changes: 8 additions & 4 deletions lib/packets/execute.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function isJSON(value) {
* Converts a value to an object describing type, String/Buffer representation and length
* @param {*} value
*/
function toParameter(value, encoding) {
function toParameter(value, encoding, timezone) {
let type = Types.VAR_STRING;
let length;
let writer = function(value) {
Expand Down Expand Up @@ -47,7 +47,10 @@ function toParameter(value, encoding) {
if (Object.prototype.toString.call(value) === '[object Date]') {
type = Types.DATETIME;
length = 12;
writer = Packet.prototype.writeDate;
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;
Expand All @@ -71,10 +74,11 @@ function toParameter(value, encoding) {
}

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

toPacket() {
Expand All @@ -92,7 +96,7 @@ class Execute {
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)
toParameter(value, this.encoding, this.timezone)
);
length += parameters.reduce(
(accumulator, parameter) => accumulator + parameter.length,
Expand Down
131 changes: 87 additions & 44 deletions lib/packets/packet.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,38 +248,48 @@ class Packet {
}

// DATE, DATETIME and TIMESTAMP
readDateTime() {
const length = this.readInt8();
if (length === 0xfb) {
return null;
}
let y = 0;
let m = 0;
let d = 0;
let H = 0;
let M = 0;
let S = 0;
let ms = 0;
if (length > 3) {
y = this.readInt16();
m = this.readInt8();
d = this.readInt8();
}
if (length > 6) {
H = this.readInt8();
M = this.readInt8();
S = this.readInt8();
}
if (length > 10) {
ms = this.readInt32() / 1000;
readDateTime(timezone) {
if (!timezone || timezone === 'Z' || timezone === 'local') {
const length = this.readInt8();
if (length === 0xfb) {
return null;
}
let y = 0;
let m = 0;
let d = 0;
let H = 0;
let M = 0;
let S = 0;
let ms = 0;
if (length > 3) {
y = this.readInt16();
m = this.readInt8();
d = this.readInt8();
}
if (length > 6) {
H = this.readInt8();
M = this.readInt8();
S = this.readInt8();
}
if (length > 10) {
ms = this.readInt32() / 1000;
}
if (y + m + d + H + M + S + ms === 0) {
return INVALID_DATE;
}
if (timezone === 'Z') {
return new Date(Date.UTC(y, m - 1, d, H, M, S, ms));
}
return new Date(y, m - 1, d, H, M, S, ms);
}
if (y + m + d + H + M + S + ms === 0) {
return INVALID_DATE;
let str = this.readDateTimeString(6, 'T');
if (str.length === 10) {
str += 'T00:00:00';
}
return new Date(y, m - 1, d, H, M, S, ms);
return new Date(str + timezone);
}

readDateTimeString(decimals) {
readDateTimeString(decimals, timeSep) {
const length = this.readInt8();
let y = 0;
let m = 0;
Expand All @@ -299,7 +309,11 @@ class Packet {
H = this.readInt8();
M = this.readInt8();
S = this.readInt8();
str += ` ${[leftPad(2, H), leftPad(2, M), leftPad(2, S)].join(':')}`;
str += `${timeSep || ' '}${[
leftPad(2, H),
leftPad(2, M),
leftPad(2, S)
].join(':')}`;
}
if (length > 10) {
ms = this.readInt32();
Expand Down Expand Up @@ -427,8 +441,8 @@ class Packet {
return sign * result;
}
return sign === -1 ? `-${str}` : str;

} if (numDigits > 16) {
}
if (numDigits > 16) {
str = this.readString(end - this.offset);
return sign === -1 ? `-${str}` : str;
}
Expand All @@ -450,7 +464,6 @@ class Packet {
return num;
}
return str;

}

// note that if value of inputNumberAsString is bigger than MAX_SAFE_INTEGER
Expand Down Expand Up @@ -575,7 +588,7 @@ class Packet {
return parseGeometry();
}

parseDate() {
parseDate(timezone) {
const strLen = this.readLengthCodedNumber();
if (strLen === null) {
return null;
Expand All @@ -590,15 +603,26 @@ class Packet {
const m = this.parseInt(2);
this.offset++; // -
const d = this.parseInt(2);
return new Date(y, m - 1, d);
if (!timezone || timezone === 'local') {
return new Date(y, m - 1, d);
}
if (timezone === 'Z') {
return new Date(Date.UTC(y, m - 1, d));
}
return new Date(
`${leftPad(4, y)}-${leftPad(2, m)}-${leftPad(2, d)}T00:00:00${timezone}`
);
}

parseDateTime() {
parseDateTime(timezone) {
const str = this.readLengthCodedString('binary');
if (str === null) {
return null;
}
return new Date(str);
if (!timezone || timezone === 'local') {
return new Date(str);
}
return new Date(`${str}${timezone}`);
}

parseFloat(len) {
Expand Down Expand Up @@ -785,15 +809,34 @@ class Packet {
return this.offset;
}

writeDate(d) {
writeDate(d, timezone) {
this.buffer.writeUInt8(11, this.offset);
this.buffer.writeUInt16LE(d.getFullYear(), this.offset + 1);
this.buffer.writeUInt8(d.getMonth() + 1, this.offset + 3);
this.buffer.writeUInt8(d.getDate(), this.offset + 4);
this.buffer.writeUInt8(d.getHours(), this.offset + 5);
this.buffer.writeUInt8(d.getMinutes(), this.offset + 6);
this.buffer.writeUInt8(d.getSeconds(), this.offset + 7);
this.buffer.writeUInt32LE(d.getMilliseconds() * 1000, this.offset + 8);
if (!timezone || timezone === 'local') {
this.buffer.writeUInt16LE(d.getFullYear(), this.offset + 1);
this.buffer.writeUInt8(d.getMonth() + 1, this.offset + 3);
this.buffer.writeUInt8(d.getDate(), this.offset + 4);
this.buffer.writeUInt8(d.getHours(), this.offset + 5);
this.buffer.writeUInt8(d.getMinutes(), this.offset + 6);
this.buffer.writeUInt8(d.getSeconds(), this.offset + 7);
this.buffer.writeUInt32LE(d.getMilliseconds() * 1000, this.offset + 8);
} else {
if (timezone !== 'Z') {
const offset =
(timezone[0] === '-' ? -1 : 1) *
(parseInt(timezone.substring(1, 3), 10) * 60 +
parseInt(timezone.substring(4), 10));
if (offset !== 0) {
d = new Date(d.getTime() + 60000 * offset);
}
}
this.buffer.writeUInt16LE(d.getUTCFullYear(), this.offset + 1);
this.buffer.writeUInt8(d.getUTCMonth() + 1, this.offset + 3);
this.buffer.writeUInt8(d.getUTCDate(), this.offset + 4);
this.buffer.writeUInt8(d.getUTCHours(), this.offset + 5);
this.buffer.writeUInt8(d.getUTCMinutes(), this.offset + 6);
this.buffer.writeUInt8(d.getUTCSeconds(), this.offset + 7);
this.buffer.writeUInt32LE(d.getUTCMilliseconds() * 1000, this.offset + 8);
}
this.offset += 12;
}

Expand Down
20 changes: 7 additions & 13 deletions lib/parsers/binary_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ function readCodeFor(field, config, options, fieldNum) {
const supportBigNumbers =
options.supportBigNumbers || config.supportBigNumbers;
const bigNumberStrings = options.bigNumberStrings || config.bigNumberStrings;
const timezone = options.timezone || config.timezone;
const unsigned = field.flags & FieldFlags.UNSIGNED;
switch (field.columnType) {
case Types.TINY:
Expand All @@ -39,7 +40,7 @@ function readCodeFor(field, config, options, fieldNum) {
if (config.dateStrings) {
return `packet.readDateTimeString(${field.decimals});`;
}
return 'packet.readDateTime();';
return `packet.readDateTime('${timezone}');`;
case Types.TIME:
return 'packet.readTimeString()';
case Types.DECIMAL:
Expand Down Expand Up @@ -68,15 +69,11 @@ function readCodeFor(field, config, options, fieldNum) {
}
return unsigned ? 'packet.readInt64();' : 'packet.readSInt64();';


default:
if (field.characterSet === Charsets.BINARY) {
return 'packet.readLengthCodedBuffer();';
}
return (
`packet.readLengthCodedString(CharsetToEncoding[fields[${fieldNum}].characterSet])`
);

return `packet.readLengthCodedString(CharsetToEncoding[fields[${fieldNum}].characterSet])`;
}
}

Expand Down Expand Up @@ -127,10 +124,9 @@ function compile(fields, options, config) {

if (typeof options.nestTables === 'string') {
tableName = helpers.srcEscape(fields[i].table);
lvalue =
`this[${helpers.srcEscape(
fields[i].table + options.nestTables + fields[i].name
)}]`;
lvalue = `this[${helpers.srcEscape(
fields[i].table + options.nestTables + fields[i].name
)}]`;
} else if (options.nestTables === true) {
tableName = helpers.srcEscape(fields[i].table);
lvalue = `this[${tableName}][${fieldName}]`;
Expand All @@ -149,9 +145,7 @@ function compile(fields, options, config) {
// } else if (fields[i].columnType == Types.NULL) {
// result.push(lvalue + ' = null;');
// } else {
parserFn(
`if (nullBitmaskByte${nullByteIndex} & ${currentFieldNullBit})`
);
parserFn(`if (nullBitmaskByte${nullByteIndex} & ${currentFieldNullBit})`);
parserFn(`${lvalue} = null;`);
parserFn('else');
parserFn(`${lvalue} = ${readCodeFor(fields[i], config, options, i)}`);
Expand Down
21 changes: 16 additions & 5 deletions lib/parsers/parser_cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,29 @@ const parserCache = new LRU({
max: 15000
});

function keyFromFields(type, fields, options) {
function keyFromFields(type, fields, options, config) {
let res =
`${type}/${typeof options.nestTables}/${options.nestTables}/${options.rowsAsArray}${options.supportBigNumbers}/${options.bigNumberStrings}/${typeof options.typeCast}`;
`${type}` +
`/${typeof options.nestTables}` +
`/${options.nestTables}` +
`/${options.rowsAsArray}` +
`/${options.supportBigNumbers || config.supportBigNumbers}` +
`/${options.bigNumberStrings || config.bigNumberStrings}` +
`/${typeof options.typeCast}` +
`/${options.timezone || config.timezone}` +
`/${options.decimalNumbers}` +
`/${options.dateStrings}`;
for (let i = 0; i < fields.length; ++i) {
res +=
`/${fields[i].name}:${fields[i].columnType}:${fields[i].flags}`;
const field = fields[i];
res += `/${field.name}:${field.columnType}:${field.flags}:${
field.characterSet
}`;
}
return res;
}

function getParser(type, fields, options, config, compiler) {
const key = keyFromFields(type, fields, options);
const key = keyFromFields(type, fields, options, config);
let parser = parserCache.get(key);

if (parser) {
Expand Down
Loading