PG: Add dates to group aggregate (#4549)

* PG: Add dates to group aggregate

* returns dates as UTC
This commit is contained in:
Diamond Lewis
2018-02-16 12:41:02 -06:00
committed by GitHub
parent 10eafe922e
commit 143b0f01cf
3 changed files with 149 additions and 5 deletions

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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;
});