Skip to content

Commit 9b738dd

Browse files
committed
BUG#33740190: indexes for upstream document Date instances
When adding a document to a collection, applications can use JavaScript Date instances to represent JSON date string values. The client simply calls the ".toJSON()" method which returns a UTC date string using the Zulu Time indicator "Z", as specified by the ISO 8601 standard. However, the Zulu Time indicator is not covered by the SQL standard and thus is not allowed by the MySQL optimiser. This means that it is not possible to create a collection index using a virtual column with the DATETIME (or similar) data type. Trying to do so yields the following error: Error: Incorrect datetime value This patch addresses the issue by replacing the Zulu Time indicator by an explicit displacement indicator of "+00:00" which has the exact same meaning from the ISO 8601 standpoint and is also allowed by the MySQL optimiser.
1 parent 9c36556 commit 9b738dd

File tree

6 files changed

+53
-19
lines changed

6 files changed

+53
-19
lines changed

lib/Protocol/Wrappers/Messages/Datatypes/Scalar.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2021, Oracle and/or its affiliates.
2+
* Copyright (c) 2020, 2022, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -368,7 +368,17 @@ Scalar.create = function (value) {
368368
}
369369

370370
if (isDate(value)) {
371-
return Scalar(createString(value.toJSON()));
371+
// The MySQL optimiser does not allow the Zulu Time indicator because
372+
// it is not covered by the SQL standard.
373+
// We need to replace it by the corresponding explicit time zone
374+
// displacement indicator (+00:00).
375+
// Although we do not have to worry about downstream convertion (from
376+
// JSON to a Date instance), the Date constructor still works fine
377+
// using the explicit displacement indicator.
378+
const date = value.toJSON();
379+
const displacement = '+00:00';
380+
381+
return Scalar(createString(date.substring(0, date.length - 1).concat(displacement)));
372382
}
373383

374384
if (isString(value)) {

test/functional/default/document-store/collection-add.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2021, Oracle and/or its affiliates.
2+
* Copyright (c) 2020, 2022, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -228,11 +228,12 @@ describe('adding documents to a collection', () => {
228228

229229
context('BUG#29179767 JavaScript Date converted to empty object', () => {
230230
it('saves a JavaScript Date as a valid JSON value', () => {
231-
const now = new Date();
232-
const expected = [{ createdAt: now.toJSON() }];
231+
const now = (new Date()).toJSON();
232+
const createdAt = now.substring(0, now.length - 1).concat('+00:00');
233+
const expected = [{ createdAt }];
233234
const actual = [];
234235

235-
return collection.add({ name: 'foo', createdAt: now })
236+
return collection.add({ name: 'foo', createdAt })
236237
.execute()
237238
.then(() => {
238239
return collection.find('name = :name')

test/functional/default/document-store/collection-find.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2021, Oracle and/or its affiliates.
2+
* Copyright (c) 2020, 2022, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -518,11 +518,12 @@ describe('finding documents in collections', () => {
518518
});
519519

520520
it('does not fail when a placeholder is assigned a JavaScript Date instance', () => {
521-
const expected = [{ _id: '1', name: 'foo', updatedAt: now.toJSON() }];
521+
const updatedAt = now.toJSON().substring(0, now.toJSON().length - 1).concat('+00:00');
522+
const expected = [{ _id: '1', name: 'foo', updatedAt }];
522523
const actual = [];
523524

524525
return collection.find('updatedAt = :date')
525-
.bind('date', now)
526+
.bind('date', updatedAt)
526527
.execute(doc => actual.push(doc))
527528
.then(() => expect(actual).to.deep.equal(expected));
528529
});

test/functional/default/document-store/collection-index.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2021, Oracle and/or its affiliates.
2+
* Copyright (c) 2020, 2022, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -170,4 +170,18 @@ describe('collection indexes', () => {
170170
.then(actual => expect(actual).to.deep.equal(expected));
171171
});
172172
});
173+
174+
context('BUG#33740190 collection DATETIME index', () => {
175+
it('works with a document field containing a JavaScript Date instance', () => {
176+
const field = '$.createdAt';
177+
const name = 'createdAt_idx';
178+
const definition = { fields: [{ field, type: 'DATETIME' }] };
179+
180+
return collection.add({ name: 'foo', createdAt: new Date() })
181+
.execute()
182+
.then(() => {
183+
return collection.createIndex(name, definition);
184+
});
185+
});
186+
});
173187
});

test/functional/default/document-store/collection-modify.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2021, Oracle and/or its affiliates.
2+
* Copyright (c) 2020, 2022, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -513,13 +513,14 @@ describe('modifying documents in a collection', () => {
513513
});
514514

515515
it('updates a single field using a valid JSON value from a JavaScript Date object', () => {
516-
const now = new Date();
517-
const expected = [{ createdAt: now.toJSON() }];
516+
const now = (new Date()).toJSON();
517+
const createdAt = now.substring(0, now.length - 1).concat('+00:00');
518+
const expected = [{ createdAt }];
518519
const actual = [];
519520

520521
return collection.modify('_id = :id')
521522
.bind('id', '1')
522-
.set('createdAt', now)
523+
.set('createdAt', createdAt)
523524
.execute()
524525
.then(() => {
525526
return collection.find('_id = :id')
@@ -533,13 +534,14 @@ describe('modifying documents in a collection', () => {
533534
});
534535

535536
it('updates a multiple fields using valid JSON values from JavaScript Date objects', () => {
536-
const now = new Date();
537-
const expected = [{ createdAt: now.toJSON(), updatedAt: now.toJSON() }];
537+
const now = (new Date()).toJSON();
538+
const utcDateString = now.substring(0, now.length - 1).concat('+00:00');
539+
const expected = [{ createdAt: utcDateString, updatedAt: utcDateString }];
538540
const actual = [];
539541

540542
return collection.modify('_id = :id')
541543
.bind('id', '1')
542-
.patch({ createdAt: now, updatedAt: now })
544+
.patch({ createdAt: utcDateString, updatedAt: utcDateString })
543545
.execute()
544546
.then(() => {
545547
return collection.find('_id = :id')

test/unit/Protocol/Wrappers/Messages/Datatypes/Scalar.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2021, Oracle and/or its affiliates.
2+
* Copyright (c) 2020, 2022, Oracle and/or its affiliates.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0, as
@@ -166,10 +166,16 @@ describe('Mysqlx.Datatypes.Scalar wrapper', () => {
166166

167167
it('creates a wrapper of Mysqlx.Datatypes.Scalar.Type.V_STRING for Date instances', () => {
168168
const now = new Date();
169+
const dateString = now.toJSON();
169170
const proto = scalar.create(now).valueOf();
170171

171172
expect(proto.getType()).to.equal(ScalarStub.Type.V_STRING);
172-
expect(Buffer.from(proto.getVString().getValue()).toString()).to.equal(now.toJSON());
173+
// MySQL does not support the Zulu Time indicator used in the
174+
// ISO 8601 convention for Date instances.
175+
// 'Z' should be replaced by '+00:00' which has similar
176+
// meaning.
177+
const supportedDateString = dateString.substring(0, dateString.length - 1).concat('+00:00');
178+
expect(Buffer.from(proto.getVString().getValue()).toString()).to.equal(supportedDateString);
173179
});
174180

175181
it('creates a wrapper of Mysqlx.Datatypes.Scalar.Type.V_STRING for strings', () => {

0 commit comments

Comments
 (0)