Skip to content

Commit 8fddac3

Browse files
authored
feat(AggregateRouter): support native mongodb syntax in aggregation pipelines (#7339)
1 parent 381e9bf commit 8fddac3

File tree

5 files changed

+126
-40
lines changed

5 files changed

+126
-40
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ ___
105105
- Add official support for MongoDB 5.0 (Manuel Trezza) [#7469](https://github.com/parse-community/parse-server/pull/7469)
106106

107107
### Other Changes
108+
- Support native mongodb syntax in aggregation pipelines (Raschid JF Rafeally) [#7339](https://github.com/parse-community/parse-server/pull/7339)
108109
- Fix error when a not yet inserted job is updated (Antonio Davi Macedo Coelho de Castro) [#7196](https://github.com/parse-community/parse-server/pull/7196)
109110
- request.context for afterFind triggers (dblythy) [#7078](https://github.com/parse-community/parse-server/pull/7078)
110111
- Winston Logger interpolating stdout to console (dplewis) [#7114](https://github.com/parse-community/parse-server/pull/7114)

DEPRECATIONS.md

+4-5
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22

33
The following is a list of deprecations, according to the [Deprecation Policy](https://github.com/parse-community/parse-server/blob/master/CONTRIBUTING.md#deprecation-policy). After a feature becomes deprecated, and giving developers time to adapt to the change, the deprecated feature will eventually be removed, leading to a breaking change. Developer feedback during the deprecation period may postpone the introduction of the breaking change.
44

5-
| Feature | Issue | Deprecation [ℹ️][i_deprecation] | Planned Removal [ℹ️][i_removal] | Status [ℹ️][i_status] | Notes |
6-
|---------|----|------------------|----------------------|----------|-------|
7-
(none)
8-
5+
| Feature | Issue | Deprecation [ℹ️][i_deprecation] | Planned Removal [ℹ️][i_removal] | Status [ℹ️][i_status] | Notes |
6+
|-----------------------------------------------|----------------------------------------------------------------------|---------------------------------|---------------------------------|-----------------------|-------|
7+
| Native MongoDB syntax in aggregation pipeline | [#7338](https://github.com/parse-community/parse-server/issues/7338) | 5.0.0 (2022) | 6.0.0 (2023) | deprecated | - |
98

109
[i_deprecation]: ## "The version and date of the deprecation."
1110
[i_removal]: ## "The version and date of the planned removal."
12-
[i_status]: ## "The current status of the deprecation: deprecated (the feature is deprecated and still available), removed (the deprecated feature has been removed and is unavailable), retracted (the deprecation has been retracted and the feature will not be removed."
11+
[i_status]: ## "The current status of the deprecation: deprecated (the feature is deprecated and still available), removed (the deprecated feature has been removed and is unavailable), retracted (the deprecation has been retracted and the feature will not be removed."

spec/AggregateRouter.spec.js

+76
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const AggregateRouter = require('../lib/Routers/AggregateRouter').AggregateRouter;
22

33
describe('AggregateRouter', () => {
4+
// TODO: update pipeline syntax. See [#7339](https://bit.ly/3incnWx)
45
it('get pipeline from Array', () => {
56
const body = [
67
{
@@ -12,6 +13,7 @@ describe('AggregateRouter', () => {
1213
expect(result).toEqual(expected);
1314
});
1415

16+
// TODO: update pipeline syntax. See [#7339](https://bit.ly/3incnWx)
1517
it('get pipeline from Object', () => {
1618
const body = {
1719
group: { objectId: {} },
@@ -21,6 +23,7 @@ describe('AggregateRouter', () => {
2123
expect(result).toEqual(expected);
2224
});
2325

26+
// TODO: update pipeline syntax. See [#7339](https://bit.ly/3incnWx)
2427
it('get pipeline from Pipeline Operator (Array)', () => {
2528
const body = {
2629
pipeline: [
@@ -34,6 +37,7 @@ describe('AggregateRouter', () => {
3437
expect(result).toEqual(expected);
3538
});
3639

40+
// TODO: update pipeline syntax. See [#7339](https://bit.ly/3incnWx)
3741
it('get pipeline from Pipeline Operator (Object)', () => {
3842
const body = {
3943
pipeline: {
@@ -45,6 +49,7 @@ describe('AggregateRouter', () => {
4549
expect(result).toEqual(expected);
4650
});
4751

52+
// TODO: update pipeline syntax. See [#7339](https://bit.ly/3incnWx)
4853
it('get pipeline fails multiple keys in Array stage ', () => {
4954
const body = [
5055
{
@@ -59,6 +64,7 @@ describe('AggregateRouter', () => {
5964
}
6065
});
6166

67+
// TODO: update pipeline syntax. See [#7339](https://bit.ly/3incnWx)
6268
it('get pipeline fails multiple keys in Pipeline Operator Array stage ', () => {
6369
const body = {
6470
pipeline: [
@@ -75,6 +81,7 @@ describe('AggregateRouter', () => {
7581
}
7682
});
7783

84+
// TODO: update pipeline syntax. See [#7339](https://bit.ly/3incnWx)
7885
it('get search pipeline from Pipeline Operator (Array)', () => {
7986
const body = {
8087
pipeline: {
@@ -85,4 +92,73 @@ describe('AggregateRouter', () => {
8592
const result = AggregateRouter.getPipeline(body);
8693
expect(result).toEqual(expected);
8794
});
95+
96+
it('support stage name starting with `$`', () => {
97+
const body = {
98+
$match: { someKey: 'whatever' },
99+
};
100+
const expected = [{ $match: { someKey: 'whatever' } }];
101+
const result = AggregateRouter.getPipeline(body);
102+
expect(result).toEqual(expected);
103+
});
104+
105+
it('support nested stage names starting with `$`', () => {
106+
const body = [
107+
{
108+
lookup: {
109+
from: 'ACollection',
110+
let: { id: '_id' },
111+
as: 'results',
112+
pipeline: [
113+
{
114+
$match: {
115+
$expr: {
116+
$eq: ['$_id', '$$id'],
117+
},
118+
},
119+
},
120+
],
121+
},
122+
},
123+
];
124+
const expected = [
125+
{
126+
$lookup: {
127+
from: 'ACollection',
128+
let: { id: '_id' },
129+
as: 'results',
130+
pipeline: [
131+
{
132+
$match: {
133+
$expr: {
134+
$eq: ['$_id', '$$id'],
135+
},
136+
},
137+
},
138+
],
139+
},
140+
},
141+
];
142+
const result = AggregateRouter.getPipeline(body);
143+
expect(result).toEqual(expected);
144+
});
145+
146+
it('support the use of `_id` in stages', () => {
147+
const body = [
148+
{ match: { _id: 'randomId' } },
149+
{ sort: { _id: -1 } },
150+
{ addFields: { _id: 1 } },
151+
{ group: { _id: {} } },
152+
{ project: { _id: 0 } },
153+
];
154+
const expected = [
155+
{ $match: { _id: 'randomId' } },
156+
{ $sort: { _id: -1 } },
157+
{ $addFields: { _id: 1 } },
158+
{ $group: { _id: {} } },
159+
{ $project: { _id: 0 } },
160+
];
161+
const result = AggregateRouter.getPipeline(body);
162+
expect(result).toEqual(expected);
163+
});
88164
});

spec/ParseQuery.Aggregate.spec.js

+26-25
Original file line numberDiff line numberDiff line change
@@ -23,28 +23,28 @@ const loadTestData = () => {
2323
const data1 = {
2424
score: 10,
2525
name: 'foo',
26-
sender: { group: 'A' },
26+
sender: { group: 'A' }, // TODO: change to `$group`. See [#7339](https://bit.ly/3incnWx)
2727
views: 900,
2828
size: ['S', 'M'],
2929
};
3030
const data2 = {
3131
score: 10,
3232
name: 'foo',
33-
sender: { group: 'A' },
33+
sender: { group: 'A' }, // TODO: change to `$group`. See [#7339](https://bit.ly/3incnWx)
3434
views: 800,
3535
size: ['M', 'L'],
3636
};
3737
const data3 = {
3838
score: 10,
3939
name: 'bar',
40-
sender: { group: 'B' },
40+
sender: { group: 'B' }, // TODO: change to `$group`. See [#7339](https://bit.ly/3incnWx)
4141
views: 700,
4242
size: ['S'],
4343
};
4444
const data4 = {
4545
score: 20,
4646
name: 'dpl',
47-
sender: { group: 'B' },
47+
sender: { group: 'B' }, // TODO: change to `$group`. See [#7339](https://bit.ly/3incnWx)
4848
views: 700,
4949
size: ['S'],
5050
};
@@ -83,22 +83,10 @@ describe('Parse.Query Aggregate testing', () => {
8383
);
8484
});
8585

86-
it('invalid query group _id', done => {
86+
it('invalid query group _id required', done => {
8787
const options = Object.assign({}, masterKeyOptions, {
8888
body: {
89-
group: { _id: null },
90-
},
91-
});
92-
get(Parse.serverURL + '/aggregate/TestObject', options).catch(error => {
93-
expect(error.error.code).toEqual(Parse.Error.INVALID_QUERY);
94-
done();
95-
});
96-
});
97-
98-
it('invalid query group objectId required', done => {
99-
const options = Object.assign({}, masterKeyOptions, {
100-
body: {
101-
group: {},
89+
group: {}, // TODO: write as `$group`. See [#7339](https://bit.ly/3incnWx)
10290
},
10391
});
10492
get(Parse.serverURL + '/aggregate/TestObject', options).catch(error => {
@@ -110,7 +98,7 @@ describe('Parse.Query Aggregate testing', () => {
11098
it('group by field', done => {
11199
const options = Object.assign({}, masterKeyOptions, {
112100
body: {
113-
group: { objectId: '$name' },
101+
group: { objectId: '$name' }, // TODO: write as `$group`. See [#7339](https://bit.ly/3incnWx)
114102
},
115103
});
116104
get(Parse.serverURL + '/aggregate/TestObject', options)
@@ -131,7 +119,7 @@ describe('Parse.Query Aggregate testing', () => {
131119
const options = Object.assign({}, masterKeyOptions, {
132120
body: {
133121
pipeline: {
134-
group: { objectId: '$name' },
122+
group: { objectId: '$name' }, // TODO: write as `$group`. See [#7339](https://bit.ly/3incnWx)
135123
},
136124
},
137125
});
@@ -149,7 +137,7 @@ describe('Parse.Query Aggregate testing', () => {
149137
const obj = new TestObject();
150138
const pipeline = [
151139
{
152-
group: { objectId: {} },
140+
group: { objectId: {} }, // TODO: write as `$group`. See [#7339](https://bit.ly/3incnWx)
153141
},
154142
];
155143
obj
@@ -168,7 +156,7 @@ describe('Parse.Query Aggregate testing', () => {
168156
const obj = new TestObject();
169157
const pipeline = [
170158
{
171-
group: { objectId: '' },
159+
group: { objectId: '' }, // TODO: write as `$group`. See [#7339](https://bit.ly/3incnWx)
172160
},
173161
];
174162
obj
@@ -187,7 +175,7 @@ describe('Parse.Query Aggregate testing', () => {
187175
const obj = new TestObject();
188176
const pipeline = [
189177
{
190-
group: { objectId: [] },
178+
group: { objectId: [] }, // TODO: write as `$group`. See [#7339](https://bit.ly/3incnWx)
191179
},
192180
];
193181
obj
@@ -208,6 +196,7 @@ describe('Parse.Query Aggregate testing', () => {
208196
const obj3 = new TestObject();
209197
const pipeline = [
210198
{
199+
// TODO: update to new syntax. See [#7339](https://bit.ly/3incnWx)
211200
group: {
212201
objectId: {
213202
score: '$score',
@@ -234,6 +223,7 @@ describe('Parse.Query Aggregate testing', () => {
234223
const obj3 = new TestObject();
235224
const pipeline = [
236225
{
226+
// TODO: update to new syntax. See [#7339](https://bit.ly/3incnWx)
237227
group: {
238228
objectId: {
239229
day: { $dayOfMonth: '$_updated_at' },
@@ -264,6 +254,7 @@ describe('Parse.Query Aggregate testing', () => {
264254
const obj3 = new TestObject();
265255
const pipeline = [
266256
{
257+
// TODO: update to new syntax. See [#7339](https://bit.ly/3incnWx)
267258
group: {
268259
objectId: {
269260
day: { $dayOfMonth: '$updatedAt' },
@@ -291,7 +282,7 @@ describe('Parse.Query Aggregate testing', () => {
291282
it('group by number', done => {
292283
const options = Object.assign({}, masterKeyOptions, {
293284
body: {
294-
group: { objectId: '$score' },
285+
group: { objectId: '$score' }, // TODO: update to new syntax. See [#7339](https://bit.ly/3incnWx)
295286
},
296287
});
297288
get(Parse.serverURL + '/aggregate/TestObject', options)
@@ -313,6 +304,7 @@ describe('Parse.Query Aggregate testing', () => {
313304
const obj2 = new TestObject({ name: 'item b', quantity: 5, price: 5 });
314305
const pipeline = [
315306
{
307+
// TODO: update to new syntax. See [#7339](https://bit.ly/3incnWx)
316308
group: {
317309
objectId: null,
318310
total: { $sum: { $multiply: ['$quantity', '$price'] } },
@@ -372,7 +364,7 @@ describe('Parse.Query Aggregate testing', () => {
372364
},
373365
{
374366
project: {
375-
objectId: 0,
367+
objectId: 0, // TODO: change to `_id`. See [#7339](https://bit.ly/3incnWx)
376368
total: { $multiply: ['$quantity', '$price'] },
377369
},
378370
},
@@ -459,6 +451,7 @@ describe('Parse.Query Aggregate testing', () => {
459451
const obj3 = new TestObject({ dateField2019: new Date(1990, 11, 1) });
460452
const pipeline = [
461453
{
454+
// TODO: update to new syntax. See [#7339](https://bit.ly/3incnWx)
462455
group: {
463456
objectId: {
464457
day: { $dayOfMonth: '$dateField2019' },
@@ -508,6 +501,7 @@ describe('Parse.Query Aggregate testing', () => {
508501
it('group sum query', done => {
509502
const options = Object.assign({}, masterKeyOptions, {
510503
body: {
504+
// TODO: update to new syntax. See [#7339](https://bit.ly/3incnWx)
511505
group: { objectId: null, total: { $sum: '$score' } },
512506
},
513507
});
@@ -524,6 +518,7 @@ describe('Parse.Query Aggregate testing', () => {
524518
it('group count query', done => {
525519
const options = Object.assign({}, masterKeyOptions, {
526520
body: {
521+
// TODO: update to new syntax. See [#7339](https://bit.ly/3incnWx)
527522
group: { objectId: null, total: { $sum: 1 } },
528523
},
529524
});
@@ -540,6 +535,7 @@ describe('Parse.Query Aggregate testing', () => {
540535
it('group min query', done => {
541536
const options = Object.assign({}, masterKeyOptions, {
542537
body: {
538+
// TODO: update to new syntax. See [#7339](https://bit.ly/3incnWx)
543539
group: { objectId: null, minScore: { $min: '$score' } },
544540
},
545541
});
@@ -556,6 +552,7 @@ describe('Parse.Query Aggregate testing', () => {
556552
it('group max query', done => {
557553
const options = Object.assign({}, masterKeyOptions, {
558554
body: {
555+
// TODO: update to new syntax. See [#7339](https://bit.ly/3incnWx)
559556
group: { objectId: null, maxScore: { $max: '$score' } },
560557
},
561558
});
@@ -572,6 +569,7 @@ describe('Parse.Query Aggregate testing', () => {
572569
it('group avg query', done => {
573570
const options = Object.assign({}, masterKeyOptions, {
574571
body: {
572+
// TODO: update to new syntax. See [#7339](https://bit.ly/3incnWx)
575573
group: { objectId: null, avgScore: { $avg: '$score' } },
576574
},
577575
});
@@ -1017,6 +1015,7 @@ describe('Parse.Query Aggregate testing', () => {
10171015
const options = Object.assign({}, masterKeyOptions, {
10181016
body: {
10191017
project: { score: 1 },
1018+
// TODO: update to new syntax. See [#7339](https://bit.ly/3incnWx)
10201019
group: { objectId: '$score', score: { $sum: '$score' } },
10211020
},
10221021
});
@@ -1044,6 +1043,7 @@ describe('Parse.Query Aggregate testing', () => {
10441043
it('class does not exist return empty', done => {
10451044
const options = Object.assign({}, masterKeyOptions, {
10461045
body: {
1046+
// TODO: update to new syntax. See [#7339](https://bit.ly/3incnWx)
10471047
group: { objectId: null, total: { $sum: '$score' } },
10481048
},
10491049
});
@@ -1058,6 +1058,7 @@ describe('Parse.Query Aggregate testing', () => {
10581058
it('field does not exist return empty', done => {
10591059
const options = Object.assign({}, masterKeyOptions, {
10601060
body: {
1061+
// TODO: update to new syntax. See [#7339](https://bit.ly/3incnWx)
10611062
group: { objectId: null, total: { $sum: '$unknownfield' } },
10621063
},
10631064
});

0 commit comments

Comments
 (0)