Skip to content

Commit cda4e96

Browse files
committed
Revert changes
1 parent 232e81d commit cda4e96

File tree

6 files changed

+181
-145
lines changed

6 files changed

+181
-145
lines changed

.changeset/spotty-buckets-flash.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
'@powersync/mysql-zongji': minor
2+
'@powersync/mysql-zongji': patch
33
---
44

5-
Export date and time values as structured fields.
5+
Add test verifying datetime fraction format.

lib/common.js

Lines changed: 59 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const iconv = require('iconv-lite');
22
const decodeJson = require('./json_decode');
3+
const dtDecode = require('./datetime_decode');
34
const bigInt = require('big-integer');
45

56
const MysqlTypes = (exports.MysqlTypes = {
@@ -331,14 +332,27 @@ const parseGeometryValue = function (buffer) {
331332
// Returns false, or an object describing the fraction of a second part of a
332333
// TIME, DATETIME, or TIMESTAMP.
333334
const readTemporalFraction = function (parser, fractionPrecision) {
334-
if (!fractionPrecision) return undefined;
335+
if (!fractionPrecision) return false;
335336
let fractionSize = Math.ceil(fractionPrecision / 2);
336337
let fraction = readIntBE(parser._buffer, parser._offset, fractionSize);
337338
parser._offset += fractionSize;
338339
if (fractionPrecision % 2 !== 0) fraction /= 10; // Not using full space
339340
if (fraction < 0) fraction *= -1; // Negative time, fraction not negative
340341

341-
return { fraction, precision: fractionPrecision };
342+
let milliseconds;
343+
if (fractionPrecision > 3) {
344+
milliseconds = Math.floor(fraction / Math.pow(10, fractionPrecision - 3));
345+
} else if (fractionPrecision < 3) {
346+
milliseconds = fraction * Math.pow(10, 3 - fractionPrecision);
347+
} else {
348+
milliseconds = fraction;
349+
}
350+
351+
return {
352+
value: fraction, // the integer after the decimal place
353+
precision: fractionPrecision, // the number of digits after the decimal
354+
milliseconds: milliseconds // the unrounded 3 digits after the decimal
355+
};
342356
};
343357

344358
// This function is used to read and interpret non-null values from parser.
@@ -471,12 +485,12 @@ exports.readMysqlValue = function (
471485
break;
472486
case MysqlTypes.DATE:
473487
raw = parseUInt24(parser);
474-
475-
result = {
476-
year: sliceBits(raw, 9, 24),
477-
month: sliceBits(raw, 5, 9),
478-
day: sliceBits(raw, 0, 5)
479-
};
488+
result = dtDecode.getDate(
489+
zongji.connection.config.dateStrings, // node-mysql dateStrings option
490+
sliceBits(raw, 9, 24), // year
491+
sliceBits(raw, 5, 9), // month
492+
sliceBits(raw, 0, 5) // day
493+
);
480494
break;
481495
case MysqlTypes.TIME:
482496
raw = parseUInt24(parser);
@@ -509,31 +523,35 @@ exports.readMysqlValue = function (
509523
minute = sliceBits(raw, 6, 12);
510524
second = sliceBits(raw, 0, 6);
511525

512-
if (isNegative && (fraction === undefined || fraction.value === 0)) {
526+
if (isNegative && (fraction === false || fraction.value === 0)) {
513527
second++;
514528
}
515529

516-
result = {
517-
isNegative,
518-
hour,
519-
minute,
520-
second,
521-
fraction
522-
};
530+
result =
531+
(isNegative ? '-' : '') +
532+
zeroPad(hour, hour > 99 ? 3 : 2) +
533+
':' +
534+
zeroPad(minute, 2) +
535+
':' +
536+
zeroPad(second, 2);
523537

538+
if (fraction !== false) {
539+
result += dtDecode.getFractionString(fraction);
540+
}
524541
break;
525542
case MysqlTypes.DATETIME:
526543
raw = parseUInt64(parser);
527544
date = Math.floor(raw / 1000000);
528545
time = raw % 1000000;
529-
result = {
530-
year: Math.floor(date / 10000),
531-
month: Math.floor((date % 10000) / 100),
532-
day: date % 100,
533-
hour: Math.floor(time / 10000),
534-
minute: Math.floor((time % 10000) / 100),
535-
second: time % 100
536-
};
546+
result = dtDecode.getDateTime(
547+
zongji.connection.config.dateStrings, // node-mysql dateStrings option
548+
Math.floor(date / 10000), // year
549+
Math.floor((date % 10000) / 100), // month
550+
date % 100, // day
551+
Math.floor(time / 10000), // hour
552+
Math.floor((time % 10000) / 100), // minutes
553+
time % 100 // seconds
554+
);
537555
break;
538556
case MysqlTypes.DATETIME2: {
539557
// Overlapping high-low to get all data in 32-bit numbers
@@ -543,30 +561,31 @@ exports.readMysqlValue = function (
543561
fraction = readTemporalFraction(parser, column.metadata.decimals);
544562

545563
yearMonth = sliceBits(rawHigh, 14, 31);
546-
result = {
547-
year: Math.floor(yearMonth / 13),
548-
month: yearMonth % 13,
549-
day: sliceBits(rawLow, 17, 22),
550-
hour: sliceBits(rawLow, 12, 17),
551-
minute: sliceBits(rawLow, 6, 12),
552-
second: sliceBits(rawLow, 0, 6),
553-
fraction
554-
};
564+
result = dtDecode.getDateTime(
565+
zongji.connection.config.dateStrings, // node-mysql dateStrings option
566+
Math.floor(yearMonth / 13), // year
567+
yearMonth % 13, // month
568+
sliceBits(rawLow, 17, 22), // day
569+
sliceBits(rawLow, 12, 17), // hour
570+
sliceBits(rawLow, 6, 12), // minutes
571+
sliceBits(rawLow, 0, 6), // seconds
572+
fraction // fraction of a second object
573+
);
555574
break;
556575
}
557576
case MysqlTypes.TIMESTAMP:
558577
raw = parser.parseUnsignedNumber(4);
559-
result = {
560-
secondsFromEpoch: raw
561-
};
578+
result = dtDecode.getTimeStamp(zongji.connection.config.dateStrings, raw);
562579
break;
563580
case MysqlTypes.TIMESTAMP2:
564581
raw = readIntBE(parser._buffer, parser._offset, 4);
565582
parser._offset += 4;
566-
result = {
567-
secondsFromEpoch: raw,
568-
fraction: readTemporalFraction(parser, column.metadata.decimals)
569-
};
583+
fraction = readTemporalFraction(parser, column.metadata.decimals);
584+
result = dtDecode.getTimeStamp(
585+
zongji.connection.config.dateStrings,
586+
raw, // seconds from epoch
587+
fraction
588+
); // fraction of a second object
570589
break;
571590
case MysqlTypes.YEAR:
572591
raw = parser.parseUnsignedNumber(1);

lib/datetime_decode.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// This file contains functions useful for converting bare numbers into
2+
// JavaScript Date objects, or DATE/DATETIME/TIMESTAMP strings, according to the
3+
// dateStrings option. The dateStrings option should be read from
4+
// zongji.connection.config, where zongji is the current instance of the ZongJi
5+
// object. The dateStrings option is interpreted the same as in node-mysql.
6+
const common = require('./common'); // used only for common.zeroPad
7+
8+
// dateStrings are used only if the dateStrings option is true, or is an array
9+
// containing the sql type name string, 'DATE', 'DATETIME', or 'TIMESTAMP'.
10+
// This follows the documentation of the dateStrings option in node-mysql.
11+
const useDateStringsForType = function (dateStrings, sqlTypeString) {
12+
return dateStrings && (dateStrings === true || (dateStrings.indexOf && dateStrings.indexOf(sqlTypeString) > -1));
13+
};
14+
15+
// fraction is the fractional second object from readTemporalFraction().
16+
// returns '' or a '.' followed by fraction.precision digits, like '.123'
17+
const getFractionString = (exports.getFractionString = function (fraction) {
18+
return fraction ? '.' + common.zeroPad(fraction.value, fraction.precision) : '';
19+
});
20+
21+
// 1950-00-00 and the like are perfectly valid Mysql dateStrings. A 0 portion
22+
// of a date is essentially a null part of the date, so we should keep it.
23+
// year, month, and date must be integers >= 0. January is month === 1.
24+
const getDateString = (exports.getDateString = function (year, month, date) {
25+
return common.zeroPad(year, 4) + '-' + common.zeroPad(month, 2) + '-' + common.zeroPad(date, 2);
26+
});
27+
28+
// Date object months are 1 less than Mysql months, and we need to filter 0.
29+
// If we don't filter 0, 2017-00-01 will become the javascript Date 2016-12-01,
30+
// which is not what it means. It means 2017-NULL-01, but the Date object
31+
// cannot handle it, so we want to return an invalid month, rather than a
32+
// subtracted month.
33+
const jsMonthFromMysqlMonth = function (month) {
34+
return month > 0 ? month - 1 : undefined;
35+
};
36+
37+
// Returns a new Date object or Mysql dateString, following the dateStrings
38+
// option. With the dateStrings option, it can output valid Mysql DATE strings
39+
// representing values that cannot be represented by a Date object, such as
40+
// values with a null part like '1950-00-04', or a zero date '0000-00-00'.
41+
exports.getDate = function (
42+
dateStrings, // node-mysql dateStrings option
43+
year,
44+
month, // January === 1
45+
date
46+
) {
47+
if (!useDateStringsForType(dateStrings, 'DATE')) {
48+
return new Date(Date.UTC(year, jsMonthFromMysqlMonth(month), date));
49+
}
50+
return getDateString(year, month, date);
51+
};
52+
53+
// Returns a new Date object or Mysql dateString, following the dateStrings
54+
// option. Fraction is an optional parameter that comes from
55+
// readTemporalFraction(). Mysql dateStrings are needed for microsecond
56+
// precision, or to represent '0000-00-00 00:00:00'.
57+
exports.getDateTime = function (
58+
dateStrings, // node-mysql dateStrings option
59+
year,
60+
month, // January === 1
61+
date,
62+
hour,
63+
minute,
64+
second,
65+
fraction // optional fractional second object
66+
) {
67+
if (!useDateStringsForType(dateStrings, 'DATETIME')) {
68+
return new Date(
69+
Date.UTC(year, jsMonthFromMysqlMonth(month), date, hour, minute, second, fraction ? fraction.milliseconds : 0)
70+
);
71+
}
72+
return (
73+
getDateString(year, month, date) +
74+
' ' +
75+
common.zeroPad(hour, 2) +
76+
':' +
77+
common.zeroPad(minute, 2) +
78+
':' +
79+
common.zeroPad(second, 2) +
80+
getFractionString(fraction)
81+
);
82+
};
83+
84+
// Returns a new Date object or Mysql dateString, following the dateStrings
85+
// option. Fraction is an optional parameter that comes from
86+
// readTemporalFraction(). With the dateStrings option from node-mysql,
87+
// this returns a mysql TIMESTAMP string, like '1975-03-01 23:03:20.38945' or
88+
// '1975-03-01 00:03:20'. Mysql strings are needed for precision beyond ms.
89+
exports.getTimeStamp = function (
90+
dateStrings, // node-mysql dateStrings option
91+
secondsFromEpoch, // an integer
92+
fraction // optional fraction of second object
93+
) {
94+
const milliseconds = fraction ? fraction.milliseconds : 0;
95+
const dateObject = new Date(secondsFromEpoch * 1000 + milliseconds);
96+
if (!useDateStringsForType(dateStrings, 'TIMESTAMP')) {
97+
return dateObject;
98+
}
99+
if (secondsFromEpoch === 0 && (!fraction || fraction.value === 0)) {
100+
return '0000-00-00 00:00:00' + getFractionString(fraction);
101+
}
102+
return (
103+
getDateString(dateObject.getFullYear(), dateObject.getMonth() + 1, dateObject.getDate()) +
104+
' ' +
105+
common.zeroPad(dateObject.getHours(), 2) +
106+
':' +
107+
common.zeroPad(dateObject.getMinutes(), 2) +
108+
':' +
109+
common.zeroPad(dateObject.getSeconds(), 2) +
110+
getFractionString(fraction)
111+
);
112+
};

test/settings/mysql.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module.exports = {
77
password: 'my_password',
88
charset: 'utf8mb4_unicode_ci',
99
port: process.env.TEST_MYSQL_PORT,
10-
dateStrings: process.env.TEST_DATE_STRINGS === 'true',
10+
dateStrings: true,
1111
database: 'zongji_test',
1212
timeZone: 'Z'
1313
// debug: true

test/types.js

Lines changed: 6 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -247,24 +247,7 @@ defineTypeTest(
247247
["'01:27:28'"],
248248
["'-01:07:08'"],
249249
["'-01:27:28'"]
250-
],
251-
function (_, event) {
252-
const values = event.rows.map((e) => e.col0);
253-
assert.deepEqual(values, [
254-
{ isNegative: true, hour: 0, minute: 0, second: 1, fraction: undefined },
255-
{ isNegative: false, hour: 0, minute: 0, second: 0, fraction: undefined },
256-
{ isNegative: false, hour: 0, minute: 7, second: 0, fraction: undefined },
257-
{ isNegative: false, hour: 20, minute: 0, second: 0, fraction: undefined },
258-
{ isNegative: false, hour: 19, minute: 0, second: 0, fraction: undefined },
259-
{ isNegative: false, hour: 4, minute: 0, second: 0, fraction: undefined },
260-
{ isNegative: true, hour: 838, minute: 59, second: 59, fraction: undefined },
261-
{ isNegative: false, hour: 838, minute: 59, second: 59, fraction: undefined },
262-
{ isNegative: false, hour: 1, minute: 7, second: 8, fraction: undefined },
263-
{ isNegative: false, hour: 1, minute: 27, second: 28, fraction: undefined },
264-
{ isNegative: true, hour: 1, minute: 7, second: 8, fraction: undefined },
265-
{ isNegative: true, hour: 1, minute: 27, second: 28, fraction: undefined }
266-
]);
267-
}
250+
]
268251
);
269252

270253
defineTypeTest(
@@ -274,32 +257,9 @@ defineTypeTest(
274257
function (_, event) {
275258
assert.deepEqual(event.rows, [
276259
{
277-
col0: {
278-
isNegative: false,
279-
hour: 17,
280-
minute: 51,
281-
second: 4,
282-
fraction: {
283-
fraction: 777,
284-
precision: 3
285-
}
286-
},
287-
col1: {
288-
year: 2018,
289-
month: 9,
290-
day: 8,
291-
hour: 17,
292-
minute: 51,
293-
second: 4,
294-
fraction: {
295-
fraction: 777000,
296-
precision: 6
297-
}
298-
},
299-
col2: {
300-
fraction: { fraction: 78, precision: 2 },
301-
secondsFromEpoch: 1536429064
302-
}
260+
col0: '17:51:04.777',
261+
col1: '2018-09-08 17:51:04.777000',
262+
col2: '2018-09-08 17:51:04.78'
303263
}
304264
]);
305265
}
@@ -308,16 +268,7 @@ defineTypeTest(
308268
defineTypeTest(
309269
'datetime_no_fraction',
310270
['DATETIME NULL'],
311-
[["'1000-01-01 00:00:00'"], ["'9999-12-31 23:59:59'"], ["'2014-12-27 01:07:08'"]],
312-
function (_, event) {
313-
assert.deepEqual(event.rows, [
314-
{ col0: { year: 1000, month: 1, day: 1, hour: 0, minute: 0, second: 0, fraction: undefined } },
315-
{ col0: { year: 9999, month: 12, day: 31, hour: 23, minute: 59, second: 59, fraction: undefined } },
316-
{
317-
col0: { year: 2014, month: 12, day: 27, hour: 1, minute: 7, second: 8, fraction: undefined }
318-
}
319-
]);
320-
}
271+
[["'1000-01-01 00:00:00'"], ["'9999-12-31 23:59:59'"], ["'2014-12-27 01:07:08'"]]
321272
);
322273

323274
defineTypeTest(
@@ -327,22 +278,7 @@ defineTypeTest(
327278
["'1000-01-01'", "'1970-01-01 00:00:01'", 1901],
328279
["'9999-12-31'", "'2038-01-18 03:14:07'", 2155],
329280
["'2014-12-27'", "'2014-12-27 01:07:08'", 2014]
330-
],
331-
function (_, event) {
332-
assert.deepEqual(event.rows, [
333-
{ col0: { year: 1000, month: 1, day: 1 }, col1: { secondsFromEpoch: 1, fraction: undefined }, col2: 1901 },
334-
{
335-
col0: { day: 31, month: 12, year: 9999 },
336-
col1: { secondsFromEpoch: 2147397247, fraction: undefined },
337-
col2: 2155
338-
},
339-
{
340-
col0: { year: 2014, month: 12, day: 27 },
341-
col1: { secondsFromEpoch: 1419642428, fraction: undefined },
342-
col2: 2014
343-
}
344-
]);
345-
}
281+
]
346282
);
347283

348284
defineTypeTest(

0 commit comments

Comments
 (0)