Skip to content

Commit

Permalink
Merge pull request #996 from trevorr/timezone
Browse files Browse the repository at this point in the history
Add support for timezone connection option
  • Loading branch information
sidorares authored Aug 12, 2019
2 parents 108f178 + 3fcc1f3 commit 769c002
Show file tree
Hide file tree
Showing 15 changed files with 351 additions and 120 deletions.
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

0 comments on commit 769c002

Please sign in to comment.