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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ Different value types are escaped differently, here is how:
* Numbers are left untouched
* Booleans are converted to `true` / `false`
* Date objects are converted to `'YYYY-mm-dd HH:ii:ss'` strings
* `Temporal.Instant` and `Temporal.ZonedDateTime` are treated as absolute times
and converted to `'YYYY-mm-dd HH:ii:ss.sss'` strings, honoring the `timeZone`
option just like `Date`
* `Temporal.PlainDateTime`, `Temporal.PlainDate` and `Temporal.PlainTime` are
wall-clock values and are converted verbatim to `DATETIME` / `DATE` / `TIME`
literals, ignoring `timeZone`
* Buffers are converted to hex strings, e.g. `X'0fa5'`
* Strings are safely escaped
* Arrays are turned into list, e.g. `['a', 'b']` turns into `'a', 'b'`
Expand Down
22 changes: 22 additions & 0 deletions lib/SqlString.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ SqlString.escape = function escape(val, stringifyObjects, timeZone) {
case 'object':
if (Object.prototype.toString.call(val) === '[object Date]') {
return SqlString.dateToString(val, timeZone || 'local');
} else if (Object.prototype.toString.call(val).indexOf('[object Temporal.') === 0) {
return SqlString.temporalToString(val, timeZone);
} else if (Array.isArray(val)) {
return SqlString.arrayToList(val, timeZone);
} else if (Buffer.isBuffer(val)) {
Expand Down Expand Up @@ -163,6 +165,26 @@ SqlString.dateToString = function dateToString(date, timeZone) {
return escapeString(str);
};

SqlString.temporalToString = function temporalToString(temporal, timeZone) {
switch (Object.prototype.toString.call(temporal)) {
// Absolute points in time. Handled exactly like a Date so the `timeZone`
// argument keeps the same meaning (millisecond precision, matching Date).
case '[object Temporal.Instant]':
case '[object Temporal.ZonedDateTime]':
return SqlString.dateToString(new Date(temporal.epochMilliseconds), timeZone || 'local');
// Wall-clock values that carry no time zone. Emit them verbatim as the
// matching MySQL DATE / TIME / DATETIME literal, ignoring `timeZone`.
case '[object Temporal.PlainDateTime]':
case '[object Temporal.PlainDate]':
case '[object Temporal.PlainTime]':
return escapeString(temporal.toString().replace('T', ' '));
// Any other Temporal type (Duration, PlainYearMonth, PlainMonthDay, ...)
// has no dedicated MySQL type; fall back to its ISO string.
default:
return escapeString(temporal.toString());
}
};

SqlString.bufferToString = function bufferToString(buffer) {
return 'X' + escapeString(buffer.toString('hex'));
};
Expand Down
52 changes: 52 additions & 0 deletions test/unit/test-SqlString.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* global Temporal */

var assert = require('assert');
var SqlString = require('../../');
var test = require('utest');
Expand Down Expand Up @@ -231,6 +233,56 @@ test('SqlString.escape', {
assert.strictEqual(string, "'" + expected + "'");
},

'Temporal.Instant is escaped like a Date': function() {
if (typeof Temporal === 'undefined') return;
var expected = '2012-05-07 11:42:03.002';
var instant = Temporal.Instant.from('2012-05-07T11:42:03.002Z');
var string = SqlString.escape(instant, false, 'Z');

assert.strictEqual(string, "'" + expected + "'");
},

'Temporal.Instant honors the time zone argument': function() {
if (typeof Temporal === 'undefined') return;
var expected = '2012-05-07 13:42:03.002';
var instant = Temporal.Instant.from('2012-05-07T11:42:03.002Z');
var string = SqlString.escape(instant, false, '+0200');

assert.strictEqual(string, "'" + expected + "'");
},

'Temporal.ZonedDateTime is escaped as an absolute time': function() {
if (typeof Temporal === 'undefined') return;
var expected = '2012-05-07 11:42:03.002';
var zoned = Temporal.Instant.from('2012-05-07T11:42:03.002Z').toZonedDateTimeISO('+02:00');
var string = SqlString.escape(zoned, false, 'Z');

assert.strictEqual(string, "'" + expected + "'");
},

'Temporal.PlainDateTime is escaped ignoring the time zone': function() {
if (typeof Temporal === 'undefined') return;
var expected = '2012-05-07 11:42:03.002';
var plain = Temporal.PlainDateTime.from('2012-05-07T11:42:03.002');
var string = SqlString.escape(plain, false, '+0200');

assert.strictEqual(string, "'" + expected + "'");
},

'Temporal.PlainDate is escaped as a DATE literal': function() {
if (typeof Temporal === 'undefined') return;
var string = SqlString.escape(Temporal.PlainDate.from('2012-05-07'));

assert.strictEqual(string, "'2012-05-07'");
},

'Temporal.PlainTime is escaped as a TIME literal': function() {
if (typeof Temporal === 'undefined') return;
var string = SqlString.escape(Temporal.PlainTime.from('11:42:03'));

assert.strictEqual(string, "'11:42:03'");
},

'buffers are converted to hex': function() {
var buffer = new Buffer([0, 1, 254, 255]);
var string = SqlString.escape(buffer);
Expand Down