Skip to content

Commit 16b1b2a

Browse files
authored
feat: support relativeTime query constraint on Postgres (#7747)
1 parent 7448521 commit 16b1b2a

File tree

6 files changed

+366
-217
lines changed

6 files changed

+366
-217
lines changed

Diff for: spec/MongoTransform.spec.js

+12-11
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
const transform = require('../lib/Adapters/Storage/Mongo/MongoTransform');
55
const dd = require('deep-diff');
66
const mongodb = require('mongodb');
7+
const Utils = require('../lib/Utils');
78

89
describe('parseObjectToMongoObjectForCreate', () => {
910
it('a basic number', done => {
@@ -592,7 +593,7 @@ describe('relativeTimeToDate', () => {
592593
describe('In the future', () => {
593594
it('should parse valid natural time', () => {
594595
const text = 'in 1 year 2 weeks 12 days 10 hours 24 minutes 30 seconds';
595-
const { result, status, info } = transform.relativeTimeToDate(text, now);
596+
const { result, status, info } = Utils.relativeTimeToDate(text, now);
596597
expect(result.toISOString()).toBe('2018-10-22T23:52:46.617Z');
597598
expect(status).toBe('success');
598599
expect(info).toBe('future');
@@ -602,7 +603,7 @@ describe('relativeTimeToDate', () => {
602603
describe('In the past', () => {
603604
it('should parse valid natural time', () => {
604605
const text = '2 days 12 hours 1 minute 12 seconds ago';
605-
const { result, status, info } = transform.relativeTimeToDate(text, now);
606+
const { result, status, info } = Utils.relativeTimeToDate(text, now);
606607
expect(result.toISOString()).toBe('2017-09-24T01:27:04.617Z');
607608
expect(status).toBe('success');
608609
expect(info).toBe('past');
@@ -612,7 +613,7 @@ describe('relativeTimeToDate', () => {
612613
describe('From now', () => {
613614
it('should equal current time', () => {
614615
const text = 'now';
615-
const { result, status, info } = transform.relativeTimeToDate(text, now);
616+
const { result, status, info } = Utils.relativeTimeToDate(text, now);
616617
expect(result.toISOString()).toBe('2017-09-26T13:28:16.617Z');
617618
expect(status).toBe('success');
618619
expect(info).toBe('present');
@@ -621,54 +622,54 @@ describe('relativeTimeToDate', () => {
621622

622623
describe('Error cases', () => {
623624
it('should error if string is completely gibberish', () => {
624-
expect(transform.relativeTimeToDate('gibberishasdnklasdnjklasndkl123j123')).toEqual({
625+
expect(Utils.relativeTimeToDate('gibberishasdnklasdnjklasndkl123j123')).toEqual({
625626
status: 'error',
626627
info: "Time should either start with 'in' or end with 'ago'",
627628
});
628629
});
629630

630631
it('should error if string contains neither `ago` nor `in`', () => {
631-
expect(transform.relativeTimeToDate('12 hours 1 minute')).toEqual({
632+
expect(Utils.relativeTimeToDate('12 hours 1 minute')).toEqual({
632633
status: 'error',
633634
info: "Time should either start with 'in' or end with 'ago'",
634635
});
635636
});
636637

637638
it('should error if there are missing units or numbers', () => {
638-
expect(transform.relativeTimeToDate('in 12 hours 1')).toEqual({
639+
expect(Utils.relativeTimeToDate('in 12 hours 1')).toEqual({
639640
status: 'error',
640641
info: 'Invalid time string. Dangling unit or number.',
641642
});
642643

643-
expect(transform.relativeTimeToDate('12 hours minute ago')).toEqual({
644+
expect(Utils.relativeTimeToDate('12 hours minute ago')).toEqual({
644645
status: 'error',
645646
info: 'Invalid time string. Dangling unit or number.',
646647
});
647648
});
648649

649650
it('should error on floating point numbers', () => {
650-
expect(transform.relativeTimeToDate('in 12.3 hours')).toEqual({
651+
expect(Utils.relativeTimeToDate('in 12.3 hours')).toEqual({
651652
status: 'error',
652653
info: "'12.3' is not an integer.",
653654
});
654655
});
655656

656657
it('should error if numbers are invalid', () => {
657-
expect(transform.relativeTimeToDate('12 hours 123a minute ago')).toEqual({
658+
expect(Utils.relativeTimeToDate('12 hours 123a minute ago')).toEqual({
658659
status: 'error',
659660
info: "'123a' is not an integer.",
660661
});
661662
});
662663

663664
it('should error on invalid interval units', () => {
664-
expect(transform.relativeTimeToDate('4 score 7 years ago')).toEqual({
665+
expect(Utils.relativeTimeToDate('4 score 7 years ago')).toEqual({
665666
status: 'error',
666667
info: "Invalid interval: 'score'",
667668
});
668669
});
669670

670671
it("should error when string contains 'ago' and 'in'", () => {
671-
expect(transform.relativeTimeToDate('in 1 day 2 minutes ago')).toEqual({
672+
expect(Utils.relativeTimeToDate('in 1 day 2 minutes ago')).toEqual({
672673
status: 'error',
673674
info: "Time cannot have both 'in' and 'ago'",
674675
});

Diff for: spec/ParseQuery.spec.js

+53-72
Original file line numberDiff line numberDiff line change
@@ -4766,7 +4766,7 @@ describe('Parse.Query testing', () => {
47664766
.catch(done.fail);
47674767
});
47684768

4769-
it_only_db('mongo')('should handle relative times correctly', function (done) {
4769+
it('should handle relative times correctly', async () => {
47704770
const now = Date.now();
47714771
const obj1 = new Parse.Object('MyCustomObject', {
47724772
name: 'obj1',
@@ -4777,94 +4777,75 @@ describe('Parse.Query testing', () => {
47774777
ttl: new Date(now - 2 * 24 * 60 * 60 * 1000), // 2 days ago
47784778
});
47794779

4780-
Parse.Object.saveAll([obj1, obj2])
4781-
.then(() => {
4782-
const q = new Parse.Query('MyCustomObject');
4783-
q.greaterThan('ttl', { $relativeTime: 'in 1 day' });
4784-
return q.find({ useMasterKey: true });
4785-
})
4786-
.then(results => {
4787-
expect(results.length).toBe(1);
4788-
})
4789-
.then(() => {
4790-
const q = new Parse.Query('MyCustomObject');
4791-
q.greaterThan('ttl', { $relativeTime: '1 day ago' });
4792-
return q.find({ useMasterKey: true });
4793-
})
4794-
.then(results => {
4795-
expect(results.length).toBe(1);
4796-
})
4797-
.then(() => {
4798-
const q = new Parse.Query('MyCustomObject');
4799-
q.lessThan('ttl', { $relativeTime: '5 days ago' });
4800-
return q.find({ useMasterKey: true });
4801-
})
4802-
.then(results => {
4803-
expect(results.length).toBe(0);
4804-
})
4805-
.then(() => {
4806-
const q = new Parse.Query('MyCustomObject');
4807-
q.greaterThan('ttl', { $relativeTime: '3 days ago' });
4808-
return q.find({ useMasterKey: true });
4809-
})
4810-
.then(results => {
4811-
expect(results.length).toBe(2);
4812-
})
4813-
.then(() => {
4814-
const q = new Parse.Query('MyCustomObject');
4815-
q.greaterThan('ttl', { $relativeTime: 'now' });
4816-
return q.find({ useMasterKey: true });
4817-
})
4818-
.then(results => {
4819-
expect(results.length).toBe(1);
4820-
})
4821-
.then(() => {
4822-
const q = new Parse.Query('MyCustomObject');
4823-
q.greaterThan('ttl', { $relativeTime: 'now' });
4824-
q.lessThan('ttl', { $relativeTime: 'in 1 day' });
4825-
return q.find({ useMasterKey: true });
4826-
})
4827-
.then(results => {
4828-
expect(results.length).toBe(0);
4829-
})
4830-
.then(() => {
4831-
const q = new Parse.Query('MyCustomObject');
4832-
q.greaterThan('ttl', { $relativeTime: '1 year 3 weeks ago' });
4833-
return q.find({ useMasterKey: true });
4834-
})
4835-
.then(results => {
4836-
expect(results.length).toBe(2);
4837-
})
4838-
.then(done, done.fail);
4780+
await Parse.Object.saveAll([obj1, obj2])
4781+
const q1 = new Parse.Query('MyCustomObject');
4782+
q1.greaterThan('ttl', { $relativeTime: 'in 1 day' });
4783+
const results1 = await q1.find({ useMasterKey: true });
4784+
expect(results1.length).toBe(1);
4785+
4786+
const q2 = new Parse.Query('MyCustomObject');
4787+
q2.greaterThan('ttl', { $relativeTime: '1 day ago' });
4788+
const results2 = await q2.find({ useMasterKey: true });
4789+
expect(results2.length).toBe(1);
4790+
4791+
const q3 = new Parse.Query('MyCustomObject');
4792+
q3.lessThan('ttl', { $relativeTime: '5 days ago' });
4793+
const results3 = await q3.find({ useMasterKey: true });
4794+
expect(results3.length).toBe(0);
4795+
4796+
const q4 = new Parse.Query('MyCustomObject');
4797+
q4.greaterThan('ttl', { $relativeTime: '3 days ago' });
4798+
const results4 = await q4.find({ useMasterKey: true });
4799+
expect(results4.length).toBe(2);
4800+
4801+
const q5 = new Parse.Query('MyCustomObject');
4802+
q5.greaterThan('ttl', { $relativeTime: 'now' });
4803+
const results5 = await q5.find({ useMasterKey: true });
4804+
expect(results5.length).toBe(1);
4805+
4806+
const q6 = new Parse.Query('MyCustomObject');
4807+
q6.greaterThan('ttl', { $relativeTime: 'now' });
4808+
q6.lessThan('ttl', { $relativeTime: 'in 1 day' });
4809+
const results6 = await q6.find({ useMasterKey: true });
4810+
expect(results6.length).toBe(0);
4811+
4812+
const q7 = new Parse.Query('MyCustomObject');
4813+
q7.greaterThan('ttl', { $relativeTime: '1 year 3 weeks ago' });
4814+
const results7 = await q7.find({ useMasterKey: true });
4815+
expect(results7.length).toBe(2);
48394816
});
48404817

4841-
it_only_db('mongo')('should error on invalid relative time', function (done) {
4818+
it('should error on invalid relative time', async () => {
48424819
const obj1 = new Parse.Object('MyCustomObject', {
48434820
name: 'obj1',
48444821
ttl: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000), // 2 days from now
48454822
});
4846-
4823+
await obj1.save({ useMasterKey: true });
48474824
const q = new Parse.Query('MyCustomObject');
48484825
q.greaterThan('ttl', { $relativeTime: '-12 bananas ago' });
4849-
obj1
4850-
.save({ useMasterKey: true })
4851-
.then(() => q.find({ useMasterKey: true }))
4852-
.then(done.fail, () => done());
4826+
try {
4827+
await q.find({ useMasterKey: true });
4828+
fail("Should have thrown error");
4829+
} catch(error) {
4830+
expect(error.code).toBe(Parse.Error.INVALID_JSON);
4831+
}
48534832
});
48544833

4855-
it_only_db('mongo')('should error when using $relativeTime on non-Date field', function (done) {
4834+
it('should error when using $relativeTime on non-Date field', async () => {
48564835
const obj1 = new Parse.Object('MyCustomObject', {
48574836
name: 'obj1',
48584837
nonDateField: 'abcd',
48594838
ttl: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000), // 2 days from now
48604839
});
4861-
4840+
await obj1.save({ useMasterKey: true });
48624841
const q = new Parse.Query('MyCustomObject');
48634842
q.greaterThan('nonDateField', { $relativeTime: '1 day ago' });
4864-
obj1
4865-
.save({ useMasterKey: true })
4866-
.then(() => q.find({ useMasterKey: true }))
4867-
.then(done.fail, () => done());
4843+
try {
4844+
await q.find({ useMasterKey: true });
4845+
fail("Should have thrown error");
4846+
} catch(error) {
4847+
expect(error.code).toBe(Parse.Error.INVALID_JSON);
4848+
}
48684849
});
48694850

48704851
it('should match complex structure with dot notation when using matchesKeyInQuery', function (done) {

Diff for: spec/PostgresStorageAdapter.spec.js

+129
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,135 @@ describe_only_db('postgres')('PostgresStorageAdapter', () => {
149149
await expectAsync(adapter.getClass('UnknownClass')).toBeRejectedWith(undefined);
150150
});
151151

152+
it('$relativeTime should error on $eq', async () => {
153+
const tableName = '_User';
154+
const schema = {
155+
fields: {
156+
objectId: { type: 'String' },
157+
username: { type: 'String' },
158+
email: { type: 'String' },
159+
emailVerified: { type: 'Boolean' },
160+
createdAt: { type: 'Date' },
161+
updatedAt: { type: 'Date' },
162+
authData: { type: 'Object' },
163+
},
164+
};
165+
const client = adapter._client;
166+
await adapter.createTable(tableName, schema);
167+
await client.none('INSERT INTO $1:name ($2:name, $3:name) VALUES ($4, $5)', [
168+
tableName,
169+
'objectId',
170+
'username',
171+
'Bugs',
172+
'Bunny',
173+
]);
174+
const database = Config.get(Parse.applicationId).database;
175+
await database.loadSchema({ clearCache: true });
176+
try {
177+
await database.find(
178+
tableName,
179+
{
180+
createdAt: {
181+
$eq: {
182+
$relativeTime: '12 days ago'
183+
}
184+
}
185+
},
186+
{ }
187+
);
188+
fail("Should have thrown error");
189+
} catch(error) {
190+
expect(error.code).toBe(Parse.Error.INVALID_JSON);
191+
}
192+
await dropTable(client, tableName);
193+
});
194+
195+
it('$relativeTime should error on $ne', async () => {
196+
const tableName = '_User';
197+
const schema = {
198+
fields: {
199+
objectId: { type: 'String' },
200+
username: { type: 'String' },
201+
email: { type: 'String' },
202+
emailVerified: { type: 'Boolean' },
203+
createdAt: { type: 'Date' },
204+
updatedAt: { type: 'Date' },
205+
authData: { type: 'Object' },
206+
},
207+
};
208+
const client = adapter._client;
209+
await adapter.createTable(tableName, schema);
210+
await client.none('INSERT INTO $1:name ($2:name, $3:name) VALUES ($4, $5)', [
211+
tableName,
212+
'objectId',
213+
'username',
214+
'Bugs',
215+
'Bunny',
216+
]);
217+
const database = Config.get(Parse.applicationId).database;
218+
await database.loadSchema({ clearCache: true });
219+
try {
220+
await database.find(
221+
tableName,
222+
{
223+
createdAt: {
224+
$ne: {
225+
$relativeTime: '12 days ago'
226+
}
227+
}
228+
},
229+
{ }
230+
);
231+
fail("Should have thrown error");
232+
} catch(error) {
233+
expect(error.code).toBe(Parse.Error.INVALID_JSON);
234+
}
235+
await dropTable(client, tableName);
236+
});
237+
238+
it('$relativeTime should error on $exists', async () => {
239+
const tableName = '_User';
240+
const schema = {
241+
fields: {
242+
objectId: { type: 'String' },
243+
username: { type: 'String' },
244+
email: { type: 'String' },
245+
emailVerified: { type: 'Boolean' },
246+
createdAt: { type: 'Date' },
247+
updatedAt: { type: 'Date' },
248+
authData: { type: 'Object' },
249+
},
250+
};
251+
const client = adapter._client;
252+
await adapter.createTable(tableName, schema);
253+
await client.none('INSERT INTO $1:name ($2:name, $3:name) VALUES ($4, $5)', [
254+
tableName,
255+
'objectId',
256+
'username',
257+
'Bugs',
258+
'Bunny',
259+
]);
260+
const database = Config.get(Parse.applicationId).database;
261+
await database.loadSchema({ clearCache: true });
262+
try {
263+
await database.find(
264+
tableName,
265+
{
266+
createdAt: {
267+
$exists: {
268+
$relativeTime: '12 days ago'
269+
}
270+
}
271+
},
272+
{ }
273+
);
274+
fail("Should have thrown error");
275+
} catch(error) {
276+
expect(error.code).toBe(Parse.Error.INVALID_JSON);
277+
}
278+
await dropTable(client, tableName);
279+
});
280+
152281
it('should use index for caseInsensitive query using Postgres', async () => {
153282
const tableName = '_User';
154283
const schema = {

0 commit comments

Comments
 (0)