PG: Add dates to group aggregate (#4549)
* PG: Add dates to group aggregate * returns dates as UTC
This commit is contained in:
@@ -96,6 +96,89 @@ describe('Parse.Query Aggregate testing', () => {
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('group by empty object', (done) => {
|
||||
const obj = new TestObject();
|
||||
const pipeline = [{
|
||||
group: { objectId: {} }
|
||||
}];
|
||||
obj.save().then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.aggregate(pipeline);
|
||||
}).then((results) => {
|
||||
expect(results[0].objectId).toEqual(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('group by empty string', (done) => {
|
||||
const obj = new TestObject();
|
||||
const pipeline = [{
|
||||
group: { objectId: '' }
|
||||
}];
|
||||
obj.save().then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.aggregate(pipeline);
|
||||
}).then((results) => {
|
||||
expect(results[0].objectId).toEqual(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('group by empty array', (done) => {
|
||||
const obj = new TestObject();
|
||||
const pipeline = [{
|
||||
group: { objectId: [] }
|
||||
}];
|
||||
obj.save().then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.aggregate(pipeline);
|
||||
}).then((results) => {
|
||||
expect(results[0].objectId).toEqual(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('group by date object', (done) => {
|
||||
const obj1 = new TestObject();
|
||||
const obj2 = new TestObject();
|
||||
const obj3 = new TestObject();
|
||||
const pipeline = [{
|
||||
group: {
|
||||
objectId: { day: { $dayOfMonth: "$_updated_at" }, month: { $month: "$_created_at" }, year: { $year: "$_created_at" } },
|
||||
count: { $sum: 1 }
|
||||
}
|
||||
}];
|
||||
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.aggregate(pipeline);
|
||||
}).then((results) => {
|
||||
const createdAt = new Date(obj1.createdAt);
|
||||
expect(results[0].objectId.day).toEqual(createdAt.getUTCDate());
|
||||
expect(results[0].objectId.month).toEqual(createdAt.getMonth() + 1);
|
||||
expect(results[0].objectId.year).toEqual(createdAt.getUTCFullYear());
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it_exclude_dbs(['postgres'])('cannot group by date field (excluding createdAt and updatedAt)', (done) => {
|
||||
const obj1 = new TestObject({ dateField: new Date(1990, 11, 1) });
|
||||
const obj2 = new TestObject({ dateField: new Date(1990, 5, 1) });
|
||||
const obj3 = new TestObject({ dateField: new Date(1990, 11, 1) });
|
||||
const pipeline = [{
|
||||
group: {
|
||||
objectId: { day: { $dayOfMonth: "$dateField" }, month: { $month: "$dateField" }, year: { $year: "$dateField" } },
|
||||
count: { $sum: 1 }
|
||||
}
|
||||
}];
|
||||
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
|
||||
const query = new Parse.Query(TestObject);
|
||||
return query.aggregate(pipeline);
|
||||
}).then(done.fail).catch((error) => {
|
||||
expect(error.code).toEqual(Parse.Error.INVALID_QUERY);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('group by pointer', (done) => {
|
||||
const pointer1 = new TestObject();
|
||||
const pointer2 = new TestObject();
|
||||
|
||||
@@ -526,7 +526,7 @@ export class MongoStorageAdapter implements StorageAdapter {
|
||||
aggregate(className: string, schema: any, pipeline: any, readPreference: ?string) {
|
||||
let isPointerField = false;
|
||||
pipeline = pipeline.map((stage) => {
|
||||
if (stage.$group && stage.$group._id) {
|
||||
if (stage.$group && stage.$group._id && (typeof stage.$group._id === 'string')) {
|
||||
const field = stage.$group._id.substring(1);
|
||||
if (schema.fields[field] && schema.fields[field].type === 'Pointer') {
|
||||
isPointerField = true;
|
||||
@@ -552,12 +552,21 @@ export class MongoStorageAdapter implements StorageAdapter {
|
||||
readPreference = this._parseReadPreference(readPreference);
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection => collection.aggregate(pipeline, { readPreference, maxTimeMS: this._maxTimeMS }))
|
||||
.catch(error => {
|
||||
if (error.code === 16006) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, error.message);
|
||||
}
|
||||
throw error;
|
||||
})
|
||||
.then(results => {
|
||||
results.forEach(result => {
|
||||
if (result.hasOwnProperty('_id')) {
|
||||
if (isPointerField && result._id) {
|
||||
result._id = result._id.split('$')[1];
|
||||
}
|
||||
if (result._id == null || _.isEmpty(result._id)) {
|
||||
result._id = null;
|
||||
}
|
||||
result.objectId = result._id;
|
||||
delete result._id;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,21 @@ const ParseToPosgresComparator = {
|
||||
'$lte': '<='
|
||||
}
|
||||
|
||||
const mongoAggregateToPostgres = {
|
||||
$dayOfMonth: 'DAY',
|
||||
$dayOfWeek: 'DOW',
|
||||
$dayOfYear: 'DOY',
|
||||
$isoDayOfWeek: 'ISODOW',
|
||||
$isoWeekYear:'ISOYEAR',
|
||||
$hour: 'HOUR',
|
||||
$minute: 'MINUTE',
|
||||
$second: 'SECOND',
|
||||
$millisecond: 'MILLISECONDS',
|
||||
$month: 'MONTH',
|
||||
$week: 'WEEK',
|
||||
$year: 'YEAR',
|
||||
};
|
||||
|
||||
const toPostgresValue = value => {
|
||||
if (typeof value === 'object') {
|
||||
if (value.__type === 'Date') {
|
||||
@@ -179,6 +194,15 @@ const transformDotField = (fieldName) => {
|
||||
}
|
||||
|
||||
const transformAggregateField = (fieldName) => {
|
||||
if (typeof fieldName !== 'string') {
|
||||
return fieldName;
|
||||
}
|
||||
if (fieldName === '$_created_at') {
|
||||
return 'createdAt';
|
||||
}
|
||||
if (fieldName === '$_updated_at') {
|
||||
return 'updatedAt';
|
||||
}
|
||||
return fieldName.substr(1);
|
||||
}
|
||||
|
||||
@@ -1519,6 +1543,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
||||
let index = 2;
|
||||
let columns: string[] = [];
|
||||
let countField = null;
|
||||
let groupValues = null;
|
||||
let wherePattern = '';
|
||||
let limitPattern = '';
|
||||
let skipPattern = '';
|
||||
@@ -1532,13 +1557,33 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
||||
if (value === null || value === undefined) {
|
||||
continue;
|
||||
}
|
||||
if (field === '_id') {
|
||||
if (field === '_id' && (typeof value === 'string') && value !== '') {
|
||||
columns.push(`$${index}:name AS "objectId"`);
|
||||
groupPattern = `GROUP BY $${index}:name`;
|
||||
values.push(transformAggregateField(value));
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (field === '_id' && (typeof value === 'object') && Object.keys(value).length !== 0) {
|
||||
groupValues = value;
|
||||
const groupByFields = [];
|
||||
for (const alias in value) {
|
||||
const operation = Object.keys(value[alias])[0];
|
||||
const source = transformAggregateField(value[alias][operation]);
|
||||
if (mongoAggregateToPostgres[operation]) {
|
||||
if (!groupByFields.includes(`"${source}"`)) {
|
||||
groupByFields.push(`"${source}"`);
|
||||
}
|
||||
columns.push(`EXTRACT(${mongoAggregateToPostgres[operation]} FROM $${index}:name AT TIME ZONE 'UTC') AS $${index + 1}:name`);
|
||||
values.push(source, alias);
|
||||
index += 2;
|
||||
}
|
||||
}
|
||||
groupPattern = `GROUP BY $${index}:raw`;
|
||||
values.push(groupByFields.join());
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (value.$sum) {
|
||||
if (typeof value.$sum === 'string') {
|
||||
columns.push(`SUM($${index}:name) AS $${index + 1}:name`);
|
||||
@@ -1646,13 +1691,20 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
||||
debug(qs, values);
|
||||
return this._client.map(qs, values, a => this.postgresObjectToParseObject(className, a, schema))
|
||||
.then(results => {
|
||||
if (countField) {
|
||||
results[0][countField] = parseInt(results[0][countField], 10);
|
||||
}
|
||||
results.forEach(result => {
|
||||
if (!result.hasOwnProperty('objectId')) {
|
||||
result.objectId = null;
|
||||
}
|
||||
if (groupValues) {
|
||||
result.objectId = {};
|
||||
for (const key in groupValues) {
|
||||
result.objectId[key] = result[key];
|
||||
delete result[key];
|
||||
}
|
||||
}
|
||||
if (countField) {
|
||||
result[countField] = parseInt(result[countField], 10);
|
||||
}
|
||||
});
|
||||
return results;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user