Skip to content

Commit 40587d7

Browse files
committed
feat: Add rawFieldNames per-query option for aggregation pipeline
1 parent 5d4a318 commit 40587d7

File tree

7 files changed

+79
-39
lines changed

7 files changed

+79
-39
lines changed

spec/ParseQuery.Aggregate.spec.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1652,4 +1652,27 @@ describe('Parse.Query Aggregate testing', () => {
16521652
expect(e.code).toBe(Parse.Error.INVALID_QUERY);
16531653
}
16541654
});
1655+
1656+
it_id('f01a0002-0001-0001-0001-000000000001')(it_exclude_dbs(['postgres']))('rawFieldNames: true lets users write _created_at directly', async () => {
1657+
const obj = new TestObject();
1658+
await obj.save();
1659+
const iso = new Date(obj.createdAt.getTime() + 1).toISOString();
1660+
const pipeline = [
1661+
{
1662+
$match: {
1663+
_id: obj.id,
1664+
_created_at: { $lte: { $date: iso } },
1665+
},
1666+
},
1667+
{ $count: 'total' },
1668+
];
1669+
const query = new Parse.Query('TestObject');
1670+
const results = await query.aggregate(pipeline, {
1671+
rawValues: true,
1672+
rawFieldNames: true,
1673+
useMasterKey: true,
1674+
});
1675+
expect(results.length).toBe(1);
1676+
expect(results[0].total).toBe(1);
1677+
});
16551678
});

src/Adapters/Storage/Mongo/MongoStorageAdapter.js

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -938,7 +938,8 @@ export class MongoStorageAdapter implements StorageAdapter {
938938
hint: ?mixed,
939939
explain?: boolean,
940940
comment: ?string,
941-
rawValues?: boolean
941+
rawValues?: boolean,
942+
rawFieldNames?: boolean
942943
) {
943944
validateExplainValue(explain);
944945
if (rawValues) {
@@ -947,7 +948,7 @@ export class MongoStorageAdapter implements StorageAdapter {
947948
let isPointerField = false;
948949
pipeline = pipeline.map(stage => {
949950
if (stage.$group) {
950-
stage.$group = this._parseAggregateGroupArgs(schema, stage.$group);
951+
stage.$group = this._parseAggregateGroupArgs(schema, stage.$group, rawFieldNames);
951952
if (
952953
stage.$group._id &&
953954
typeof stage.$group._id === 'string' &&
@@ -957,13 +958,13 @@ export class MongoStorageAdapter implements StorageAdapter {
957958
}
958959
}
959960
if (stage.$match) {
960-
stage.$match = this._parseAggregateArgs(schema, stage.$match, rawValues);
961+
stage.$match = this._parseAggregateArgs(schema, stage.$match, rawValues, rawFieldNames);
961962
}
962963
if (stage.$project) {
963-
stage.$project = this._parseAggregateProjectArgs(schema, stage.$project);
964+
stage.$project = this._parseAggregateProjectArgs(schema, stage.$project, rawValues, rawFieldNames);
964965
}
965966
if (stage.$geoNear && stage.$geoNear.query) {
966-
stage.$geoNear.query = this._parseAggregateArgs(schema, stage.$geoNear.query, rawValues);
967+
stage.$geoNear.query = this._parseAggregateArgs(schema, stage.$geoNear.query, rawValues, rawFieldNames);
967968
}
968969
return stage;
969970
});
@@ -980,7 +981,7 @@ export class MongoStorageAdapter implements StorageAdapter {
980981
})
981982
)
982983
.then(results => {
983-
if (rawValues) {
984+
if (rawFieldNames) {
984985
return results;
985986
}
986987
results.forEach(result => {
@@ -1005,6 +1006,9 @@ export class MongoStorageAdapter implements StorageAdapter {
10051006
if (rawValues) {
10061007
return objects.map(obj => EJSON.serialize(obj));
10071008
}
1009+
if (rawFieldNames) {
1010+
return objects;
1011+
}
10081012
return objects.map(object => mongoObjectToParseObject(className, object, schema));
10091013
})
10101014
.catch(err => this.handleError(err));
@@ -1029,17 +1033,17 @@ export class MongoStorageAdapter implements StorageAdapter {
10291033
//
10301034
// As much as I hate recursion...this seemed like a good fit for it. We're essentially traversing
10311035
// down a tree to find a "leaf node" and checking to see if it needs to be converted.
1032-
_parseAggregateArgs(schema: any, pipeline: any, rawValues?: boolean): any {
1036+
_parseAggregateArgs(schema: any, pipeline: any, rawValues?: boolean, rawFieldNames?: boolean): any {
10331037
if (pipeline === null) {
10341038
return null;
10351039
} else if (Utils.isDate(pipeline)) {
10361040
return pipeline;
10371041
} else if (Array.isArray(pipeline)) {
1038-
return pipeline.map(value => this._parseAggregateArgs(schema, value, rawValues));
1042+
return pipeline.map(value => this._parseAggregateArgs(schema, value, rawValues, rawFieldNames));
10391043
} else if (typeof pipeline === 'object') {
10401044
const returnValue = {};
10411045
for (const field in pipeline) {
1042-
if (schema.fields[field] && schema.fields[field].type === 'Pointer') {
1046+
if (!rawFieldNames && schema.fields[field] && schema.fields[field].type === 'Pointer') {
10431047
if (typeof pipeline[field] === 'object') {
10441048
returnValue[`_p_${field}`] = pipeline[field];
10451049
} else if (rawValues) {
@@ -1050,18 +1054,20 @@ export class MongoStorageAdapter implements StorageAdapter {
10501054
} else if (schema.fields[field] && schema.fields[field].type === 'Date' && !rawValues) {
10511055
returnValue[field] = this._convertToDate(pipeline[field]);
10521056
} else {
1053-
returnValue[field] = this._parseAggregateArgs(schema, pipeline[field], rawValues);
1057+
returnValue[field] = this._parseAggregateArgs(schema, pipeline[field], rawValues, rawFieldNames);
10541058
}
10551059

1056-
if (field === 'objectId') {
1057-
returnValue['_id'] = returnValue[field];
1058-
delete returnValue[field];
1059-
} else if (field === 'createdAt') {
1060-
returnValue['_created_at'] = returnValue[field];
1061-
delete returnValue[field];
1062-
} else if (field === 'updatedAt') {
1063-
returnValue['_updated_at'] = returnValue[field];
1064-
delete returnValue[field];
1060+
if (!rawFieldNames) {
1061+
if (field === 'objectId') {
1062+
returnValue['_id'] = returnValue[field];
1063+
delete returnValue[field];
1064+
} else if (field === 'createdAt') {
1065+
returnValue['_created_at'] = returnValue[field];
1066+
delete returnValue[field];
1067+
} else if (field === 'updatedAt') {
1068+
returnValue['_updated_at'] = returnValue[field];
1069+
delete returnValue[field];
1070+
}
10651071
}
10661072
}
10671073
return returnValue;
@@ -1073,24 +1079,26 @@ export class MongoStorageAdapter implements StorageAdapter {
10731079
// two functions and making the code even harder to understand, I decided to split it up. The
10741080
// difference with this function is we are not transforming the values, only the keys of the
10751081
// pipeline.
1076-
_parseAggregateProjectArgs(schema: any, pipeline: any): any {
1082+
_parseAggregateProjectArgs(schema: any, pipeline: any, rawValues?: boolean, rawFieldNames?: boolean): any {
10771083
const returnValue = {};
10781084
for (const field in pipeline) {
1079-
if (schema.fields[field] && schema.fields[field].type === 'Pointer') {
1085+
if (!rawFieldNames && schema.fields[field] && schema.fields[field].type === 'Pointer') {
10801086
returnValue[`_p_${field}`] = pipeline[field];
10811087
} else {
1082-
returnValue[field] = this._parseAggregateArgs(schema, pipeline[field]);
1088+
returnValue[field] = this._parseAggregateArgs(schema, pipeline[field], rawValues, rawFieldNames);
10831089
}
10841090

1085-
if (field === 'objectId') {
1086-
returnValue['_id'] = returnValue[field];
1087-
delete returnValue[field];
1088-
} else if (field === 'createdAt') {
1089-
returnValue['_created_at'] = returnValue[field];
1090-
delete returnValue[field];
1091-
} else if (field === 'updatedAt') {
1092-
returnValue['_updated_at'] = returnValue[field];
1093-
delete returnValue[field];
1091+
if (!rawFieldNames) {
1092+
if (field === 'objectId') {
1093+
returnValue['_id'] = returnValue[field];
1094+
delete returnValue[field];
1095+
} else if (field === 'createdAt') {
1096+
returnValue['_created_at'] = returnValue[field];
1097+
delete returnValue[field];
1098+
} else if (field === 'updatedAt') {
1099+
returnValue['_updated_at'] = returnValue[field];
1100+
delete returnValue[field];
1101+
}
10941102
}
10951103
}
10961104
return returnValue;
@@ -1101,16 +1109,16 @@ export class MongoStorageAdapter implements StorageAdapter {
11011109
// The <expression> could be a column name, prefixed with the '$' character. We'll look for
11021110
// these <expression> and check to see if it is a 'Pointer' or if it's one of createdAt,
11031111
// updatedAt or objectId and change it accordingly.
1104-
_parseAggregateGroupArgs(schema: any, pipeline: any): any {
1112+
_parseAggregateGroupArgs(schema: any, pipeline: any, rawFieldNames?: boolean): any {
11051113
if (Array.isArray(pipeline)) {
1106-
return pipeline.map(value => this._parseAggregateGroupArgs(schema, value));
1114+
return pipeline.map(value => this._parseAggregateGroupArgs(schema, value, rawFieldNames));
11071115
} else if (typeof pipeline === 'object') {
11081116
const returnValue = {};
11091117
for (const field in pipeline) {
1110-
returnValue[field] = this._parseAggregateGroupArgs(schema, pipeline[field]);
1118+
returnValue[field] = this._parseAggregateGroupArgs(schema, pipeline[field], rawFieldNames);
11111119
}
11121120
return returnValue;
1113-
} else if (typeof pipeline === 'string') {
1121+
} else if (typeof pipeline === 'string' && !rawFieldNames) {
11141122
const field = pipeline.substring(1);
11151123
if (schema.fields[field] && schema.fields[field].type === 'Pointer') {
11161124
return `$_p_${field}`;

src/Adapters/Storage/Postgres/PostgresStorageAdapter.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2273,7 +2273,8 @@ export class PostgresStorageAdapter implements StorageAdapter {
22732273
hint: ?mixed,
22742274
explain?: boolean,
22752275
_comment?: ?string,
2276-
_rawValues?: boolean
2276+
_rawValues?: boolean,
2277+
_rawFieldNames?: boolean
22772278
) {
22782279
debug('aggregate');
22792280
const values = [className];

src/Adapters/Storage/StorageAdapter.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ export interface StorageAdapter {
126126
hint: ?mixed,
127127
explain?: boolean,
128128
comment?: string,
129-
rawValues?: boolean
129+
rawValues?: boolean,
130+
rawFieldNames?: boolean
130131
): Promise<any>;
131132
performInitialization(options: ?any): Promise<void>;
132133
watch(callback: () => void): void;

src/Controllers/DatabaseController.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1270,6 +1270,7 @@ class DatabaseController {
12701270
explain,
12711271
comment,
12721272
rawValues,
1273+
rawFieldNames,
12731274
}: any = {},
12741275
auth: any = {},
12751276
validSchemaController: SchemaController.SchemaController
@@ -1411,7 +1412,8 @@ class DatabaseController {
14111412
hint,
14121413
explain,
14131414
comment,
1414-
rawValues
1415+
rawValues,
1416+
rawFieldNames
14151417
);
14161418
}
14171419
} else if (explain) {

src/RestQuery.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ function _UnsafeRestQuery(
220220
case 'readPreference':
221221
case 'comment':
222222
case 'rawValues':
223+
case 'rawFieldNames':
223224
this.findOptions[option] = restOptions[option];
224225
break;
225226
case 'order':

src/Routers/AggregateRouter.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ export class AggregateRouter extends ClassesRouter {
3131
options.rawValues = body.rawValues;
3232
delete body.rawValues;
3333
}
34+
if (typeof body.rawFieldNames === 'boolean') {
35+
options.rawFieldNames = body.rawFieldNames;
36+
delete body.rawFieldNames;
37+
}
3438
options.pipeline = AggregateRouter.getPipeline(body);
3539
if (typeof body.where === 'string') {
3640
try {
@@ -49,7 +53,7 @@ export class AggregateRouter extends ClassesRouter {
4953
req.info.clientSDK,
5054
req.info.context
5155
);
52-
if (!options.rawValues) {
56+
if (!options.rawValues && !options.rawFieldNames) {
5357
for (const result of response.results) {
5458
if (typeof result === 'object') {
5559
UsersRouter.removeHiddenProperties(result);

0 commit comments

Comments
 (0)