Skip to content

Commit 847a274

Browse files
authored
fix: MongoDB aggregation pipeline with $dateSubtract from $$NOW returns no results (#9822)
1 parent e3f5ae0 commit 847a274

File tree

2 files changed

+45
-10
lines changed

2 files changed

+45
-10
lines changed

spec/ParseQuery.Aggregate.spec.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,36 @@ describe('Parse.Query Aggregate testing', () => {
439439
}
440440
);
441441

442+
it_id('3723671d-4100-4103-ad9c-60e4c22e20ff')(it_exclude_dbs(['postgres']))('matches expression with $dateSubtract from $$NOW', async () => {
443+
const obj1 = new TestObject({ date: new Date(new Date().getTime() - 1 * 24 * 60 * 60 * 1_000) }); // 1 day ago
444+
const obj2 = new TestObject({ date: new Date(new Date().getTime() - 2 * 24 * 60 * 60 * 1_000) }); // 3 days ago
445+
await Parse.Object.saveAll([obj1, obj2]);
446+
447+
const pipeline = [
448+
{
449+
$match: {
450+
$expr: {
451+
$gte: [
452+
'$date',
453+
{
454+
$dateSubtract: {
455+
startDate: '$$NOW',
456+
unit: 'day',
457+
amount: 2,
458+
},
459+
},
460+
],
461+
},
462+
},
463+
},
464+
];
465+
466+
const query = new Parse.Query('TestObject');
467+
const results = await query.aggregate(pipeline, { useMasterKey: true });
468+
expect(results.length).toBe(1);
469+
expect(new Date(results[0].date.iso)).toEqual(obj1.get('date'));
470+
});
471+
442472
it_only_db('postgres')(
443473
'can group by any date field (it does not work if you have dirty data)', // rows in your collection with non date data in the field that is supposed to be a date
444474
done => {

src/Adapters/Storage/Mongo/MongoStorageAdapter.js

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -960,23 +960,28 @@ export class MongoStorageAdapter implements StorageAdapter {
960960
return pipeline;
961961
}
962962

963-
// This function will attempt to convert the provided value to a Date object. Since this is part
964-
// of an aggregation pipeline, the value can either be a string or it can be another object with
965-
// an operator in it (like $gt, $lt, etc). Because of this I felt it was easier to make this a
966-
// recursive method to traverse down to the "leaf node" which is going to be the string.
963+
/**
964+
* Recursively converts values to Date objects. Since the passed object is part of an aggregation
965+
* pipeline and can contain various logic operators (like $gt, $lt, etc), this function will
966+
* traverse the object and convert any strings that can be parsed as dates into Date objects.
967+
* @param {any} value The value to convert.
968+
* @returns {any} The original value if not convertible to Date, or a Date object if it is.
969+
*/
967970
_convertToDate(value: any): any {
968971
if (value instanceof Date) {
969972
return value;
970973
}
971974
if (typeof value === 'string') {
972-
return new Date(value);
975+
return isNaN(Date.parse(value)) ? value : new Date(value);
973976
}
974-
975-
const returnValue = {};
976-
for (const field in value) {
977-
returnValue[field] = this._convertToDate(value[field]);
977+
if (typeof value === 'object') {
978+
const returnValue = {};
979+
for (const field in value) {
980+
returnValue[field] = this._convertToDate(value[field]);
981+
}
982+
return returnValue;
978983
}
979-
return returnValue;
984+
return value;
980985
}
981986

982987
_parseReadPreference(readPreference: ?string): ?string {

0 commit comments

Comments
 (0)