Skip to content

Commit 4802b1c

Browse files
authored
Add pipeline key to Aggregate (#4959)
* Add pipeline key to Aggregate * clean up * unit tests
1 parent 9e36a3c commit 4802b1c

File tree

4 files changed

+135
-18
lines changed

4 files changed

+135
-18
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
### master
44
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.4...master)
55

6+
#### Improvements:
7+
* Adds Pipeline Operator to Aggregate Router
8+
69
### 2.8.4
710
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.3...2.8.4)
811

9-
#### Improvements
12+
#### Improvements:
1013
* Adds ability to forward errors to express handler (#4697)
1114
* Adds ability to increment the push badge with an arbitrary value (#4889)
1215
* Adds ability to preserve the file names when uploading (#4915)

spec/AggregateRouter.spec.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
const AggregateRouter = require('../lib/Routers/AggregateRouter').AggregateRouter;
2+
3+
describe('AggregateRouter', () => {
4+
it('get pipeline from Array', () => {
5+
const body = [{
6+
group: { objectId: {} }
7+
}];
8+
const expected = [ { $group: { _id: {} } } ];
9+
const result = AggregateRouter.getPipeline(body);
10+
expect(result).toEqual(expected);
11+
});
12+
13+
it('get pipeline from Object', () => {
14+
const body = {
15+
group: { objectId: {} }
16+
};
17+
const expected = [ { $group: { _id: {} } } ];
18+
const result = AggregateRouter.getPipeline(body);
19+
expect(result).toEqual(expected);
20+
});
21+
22+
it('get pipeline from Pipeline Operator (Array)', () => {
23+
const body = {
24+
pipeline: [{
25+
group: { objectId: {} }
26+
}]
27+
};
28+
const expected = [ { $group: { _id: {} } } ];
29+
const result = AggregateRouter.getPipeline(body);
30+
expect(result).toEqual(expected);
31+
});
32+
33+
it('get pipeline from Pipeline Operator (Object)', () => {
34+
const body = {
35+
pipeline: {
36+
group: { objectId: {} }
37+
}
38+
};
39+
const expected = [ { $group: { _id: {} } } ];
40+
const result = AggregateRouter.getPipeline(body);
41+
expect(result).toEqual(expected);
42+
});
43+
44+
it('get pipeline fails multiple keys in Array stage ', () => {
45+
const body = [{
46+
group: { objectId: {} },
47+
match: { name: 'Test' },
48+
}];
49+
try {
50+
AggregateRouter.getPipeline(body);
51+
} catch (e) {
52+
expect(e.message).toBe('Pipeline stages should only have one key found group, match');
53+
}
54+
});
55+
56+
it('get pipeline fails multiple keys in Pipeline Operator Array stage ', () => {
57+
const body = {
58+
pipeline: [{
59+
group: { objectId: {} },
60+
match: { name: 'Test' },
61+
}]
62+
};
63+
try {
64+
AggregateRouter.getPipeline(body);
65+
} catch (e) {
66+
expect(e.message).toBe('Pipeline stages should only have one key found group, match');
67+
}
68+
});
69+
});

spec/ParseQuery.Aggregate.spec.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,24 @@ describe('Parse.Query Aggregate testing', () => {
100100
}).catch(done.fail);
101101
});
102102

103+
it('group by pipeline operator', async () => {
104+
const options = Object.assign({}, masterKeyOptions, {
105+
body: {
106+
pipeline: {
107+
group: { objectId: '$name' },
108+
}
109+
}
110+
});
111+
const resp = await rp.get(Parse.serverURL + '/aggregate/TestObject', options);
112+
expect(resp.results.length).toBe(3);
113+
expect(resp.results[0].hasOwnProperty('objectId')).toBe(true);
114+
expect(resp.results[1].hasOwnProperty('objectId')).toBe(true);
115+
expect(resp.results[2].hasOwnProperty('objectId')).toBe(true);
116+
expect(resp.results[0].objectId).not.toBe(undefined);
117+
expect(resp.results[1].objectId).not.toBe(undefined);
118+
expect(resp.results[2].objectId).not.toBe(undefined);
119+
});
120+
103121
it('group by empty object', (done) => {
104122
const obj = new TestObject();
105123
const pipeline = [{

src/Routers/AggregateRouter.js

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as middleware from '../middlewares';
44
import Parse from 'parse/node';
55
import UsersRouter from './UsersRouter';
66

7-
const BASE_KEYS = ['where', 'distinct'];
7+
const BASE_KEYS = ['where', 'distinct', 'pipeline'];
88

99
const PIPELINE_KEYS = [
1010
'addFields',
@@ -41,24 +41,10 @@ export class AggregateRouter extends ClassesRouter {
4141
handleFind(req) {
4242
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
4343
const options = {};
44-
let pipeline = [];
45-
46-
if (Array.isArray(body)) {
47-
pipeline = body.map((stage) => {
48-
const stageName = Object.keys(stage)[0];
49-
return this.transformStage(stageName, stage);
50-
});
51-
} else {
52-
const stages = [];
53-
for (const stageName in body) {
54-
stages.push(this.transformStage(stageName, body));
55-
}
56-
pipeline = stages;
57-
}
5844
if (body.distinct) {
5945
options.distinct = String(body.distinct);
6046
}
61-
options.pipeline = pipeline;
47+
options.pipeline = AggregateRouter.getPipeline(body);
6248
if (typeof body.where === 'string') {
6349
body.where = JSON.parse(body.where);
6450
}
@@ -72,7 +58,48 @@ export class AggregateRouter extends ClassesRouter {
7258
});
7359
}
7460

75-
transformStage(stageName, stage) {
61+
/* Builds a pipeline from the body. Originally the body could be passed as a single object,
62+
* and now we support many options
63+
*
64+
* Array
65+
*
66+
* body: [{
67+
* group: { objectId: '$name' },
68+
* }]
69+
*
70+
* Object
71+
*
72+
* body: {
73+
* group: { objectId: '$name' },
74+
* }
75+
*
76+
*
77+
* Pipeline Operator with an Array or an Object
78+
*
79+
* body: {
80+
* pipeline: {
81+
* group: { objectId: '$name' },
82+
* }
83+
* }
84+
*
85+
*/
86+
static getPipeline(body) {
87+
let pipeline = body.pipeline || body;
88+
89+
if (!Array.isArray(pipeline)) {
90+
pipeline = Object.keys(pipeline).map((key) => { return { [key]: pipeline[key] } });
91+
}
92+
93+
return pipeline.map((stage) => {
94+
const keys = Object.keys(stage);
95+
if (keys.length != 1) {
96+
throw new Error(`Pipeline stages should only have one key found ${keys.join(', ')}`);
97+
}
98+
return AggregateRouter.transformStage(keys[0], stage);
99+
});
100+
}
101+
102+
static transformStage(stageName, stage) {
76103
if (ALLOWED_KEYS.indexOf(stageName) === -1) {
77104
throw new Parse.Error(
78105
Parse.Error.INVALID_QUERY,

0 commit comments

Comments
 (0)