Add pipeline key to Aggregate (#4959)
* Add pipeline key to Aggregate * clean up * unit tests
This commit is contained in:
@@ -3,10 +3,13 @@
|
|||||||
### master
|
### master
|
||||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.4...master)
|
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.4...master)
|
||||||
|
|
||||||
|
#### Improvements:
|
||||||
|
* Adds Pipeline Operator to Aggregate Router
|
||||||
|
|
||||||
### 2.8.4
|
### 2.8.4
|
||||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.3...2.8.4)
|
[Full Changelog](https://github.com/parse-community/parse-server/compare/2.8.3...2.8.4)
|
||||||
|
|
||||||
#### Improvements
|
#### Improvements:
|
||||||
* Adds ability to forward errors to express handler (#4697)
|
* Adds ability to forward errors to express handler (#4697)
|
||||||
* Adds ability to increment the push badge with an arbitrary value (#4889)
|
* Adds ability to increment the push badge with an arbitrary value (#4889)
|
||||||
* Adds ability to preserve the file names when uploading (#4915)
|
* Adds ability to preserve the file names when uploading (#4915)
|
||||||
|
|||||||
69
spec/AggregateRouter.spec.js
Normal file
69
spec/AggregateRouter.spec.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
const AggregateRouter = require('../lib/Routers/AggregateRouter').AggregateRouter;
|
||||||
|
|
||||||
|
describe('AggregateRouter', () => {
|
||||||
|
it('get pipeline from Array', () => {
|
||||||
|
const body = [{
|
||||||
|
group: { objectId: {} }
|
||||||
|
}];
|
||||||
|
const expected = [ { $group: { _id: {} } } ];
|
||||||
|
const result = AggregateRouter.getPipeline(body);
|
||||||
|
expect(result).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('get pipeline from Object', () => {
|
||||||
|
const body = {
|
||||||
|
group: { objectId: {} }
|
||||||
|
};
|
||||||
|
const expected = [ { $group: { _id: {} } } ];
|
||||||
|
const result = AggregateRouter.getPipeline(body);
|
||||||
|
expect(result).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('get pipeline from Pipeline Operator (Array)', () => {
|
||||||
|
const body = {
|
||||||
|
pipeline: [{
|
||||||
|
group: { objectId: {} }
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
const expected = [ { $group: { _id: {} } } ];
|
||||||
|
const result = AggregateRouter.getPipeline(body);
|
||||||
|
expect(result).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('get pipeline from Pipeline Operator (Object)', () => {
|
||||||
|
const body = {
|
||||||
|
pipeline: {
|
||||||
|
group: { objectId: {} }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const expected = [ { $group: { _id: {} } } ];
|
||||||
|
const result = AggregateRouter.getPipeline(body);
|
||||||
|
expect(result).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('get pipeline fails multiple keys in Array stage ', () => {
|
||||||
|
const body = [{
|
||||||
|
group: { objectId: {} },
|
||||||
|
match: { name: 'Test' },
|
||||||
|
}];
|
||||||
|
try {
|
||||||
|
AggregateRouter.getPipeline(body);
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toBe('Pipeline stages should only have one key found group, match');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('get pipeline fails multiple keys in Pipeline Operator Array stage ', () => {
|
||||||
|
const body = {
|
||||||
|
pipeline: [{
|
||||||
|
group: { objectId: {} },
|
||||||
|
match: { name: 'Test' },
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
AggregateRouter.getPipeline(body);
|
||||||
|
} catch (e) {
|
||||||
|
expect(e.message).toBe('Pipeline stages should only have one key found group, match');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -100,6 +100,24 @@ describe('Parse.Query Aggregate testing', () => {
|
|||||||
}).catch(done.fail);
|
}).catch(done.fail);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('group by pipeline operator', async () => {
|
||||||
|
const options = Object.assign({}, masterKeyOptions, {
|
||||||
|
body: {
|
||||||
|
pipeline: {
|
||||||
|
group: { objectId: '$name' },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const resp = await rp.get(Parse.serverURL + '/aggregate/TestObject', options);
|
||||||
|
expect(resp.results.length).toBe(3);
|
||||||
|
expect(resp.results[0].hasOwnProperty('objectId')).toBe(true);
|
||||||
|
expect(resp.results[1].hasOwnProperty('objectId')).toBe(true);
|
||||||
|
expect(resp.results[2].hasOwnProperty('objectId')).toBe(true);
|
||||||
|
expect(resp.results[0].objectId).not.toBe(undefined);
|
||||||
|
expect(resp.results[1].objectId).not.toBe(undefined);
|
||||||
|
expect(resp.results[2].objectId).not.toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
it('group by empty object', (done) => {
|
it('group by empty object', (done) => {
|
||||||
const obj = new TestObject();
|
const obj = new TestObject();
|
||||||
const pipeline = [{
|
const pipeline = [{
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as middleware from '../middlewares';
|
|||||||
import Parse from 'parse/node';
|
import Parse from 'parse/node';
|
||||||
import UsersRouter from './UsersRouter';
|
import UsersRouter from './UsersRouter';
|
||||||
|
|
||||||
const BASE_KEYS = ['where', 'distinct'];
|
const BASE_KEYS = ['where', 'distinct', 'pipeline'];
|
||||||
|
|
||||||
const PIPELINE_KEYS = [
|
const PIPELINE_KEYS = [
|
||||||
'addFields',
|
'addFields',
|
||||||
@@ -41,24 +41,10 @@ export class AggregateRouter extends ClassesRouter {
|
|||||||
handleFind(req) {
|
handleFind(req) {
|
||||||
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
|
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
|
||||||
const options = {};
|
const options = {};
|
||||||
let pipeline = [];
|
|
||||||
|
|
||||||
if (Array.isArray(body)) {
|
|
||||||
pipeline = body.map((stage) => {
|
|
||||||
const stageName = Object.keys(stage)[0];
|
|
||||||
return this.transformStage(stageName, stage);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const stages = [];
|
|
||||||
for (const stageName in body) {
|
|
||||||
stages.push(this.transformStage(stageName, body));
|
|
||||||
}
|
|
||||||
pipeline = stages;
|
|
||||||
}
|
|
||||||
if (body.distinct) {
|
if (body.distinct) {
|
||||||
options.distinct = String(body.distinct);
|
options.distinct = String(body.distinct);
|
||||||
}
|
}
|
||||||
options.pipeline = pipeline;
|
options.pipeline = AggregateRouter.getPipeline(body);
|
||||||
if (typeof body.where === 'string') {
|
if (typeof body.where === 'string') {
|
||||||
body.where = JSON.parse(body.where);
|
body.where = JSON.parse(body.where);
|
||||||
}
|
}
|
||||||
@@ -72,7 +58,48 @@ export class AggregateRouter extends ClassesRouter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
transformStage(stageName, stage) {
|
/* Builds a pipeline from the body. Originally the body could be passed as a single object,
|
||||||
|
* and now we support many options
|
||||||
|
*
|
||||||
|
* Array
|
||||||
|
*
|
||||||
|
* body: [{
|
||||||
|
* group: { objectId: '$name' },
|
||||||
|
* }]
|
||||||
|
*
|
||||||
|
* Object
|
||||||
|
*
|
||||||
|
* body: {
|
||||||
|
* group: { objectId: '$name' },
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Pipeline Operator with an Array or an Object
|
||||||
|
*
|
||||||
|
* body: {
|
||||||
|
* pipeline: {
|
||||||
|
* group: { objectId: '$name' },
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static getPipeline(body) {
|
||||||
|
let pipeline = body.pipeline || body;
|
||||||
|
|
||||||
|
if (!Array.isArray(pipeline)) {
|
||||||
|
pipeline = Object.keys(pipeline).map((key) => { return { [key]: pipeline[key] } });
|
||||||
|
}
|
||||||
|
|
||||||
|
return pipeline.map((stage) => {
|
||||||
|
const keys = Object.keys(stage);
|
||||||
|
if (keys.length != 1) {
|
||||||
|
throw new Error(`Pipeline stages should only have one key found ${keys.join(', ')}`);
|
||||||
|
}
|
||||||
|
return AggregateRouter.transformStage(keys[0], stage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static transformStage(stageName, stage) {
|
||||||
if (ALLOWED_KEYS.indexOf(stageName) === -1) {
|
if (ALLOWED_KEYS.indexOf(stageName) === -1) {
|
||||||
throw new Parse.Error(
|
throw new Parse.Error(
|
||||||
Parse.Error.INVALID_QUERY,
|
Parse.Error.INVALID_QUERY,
|
||||||
|
|||||||
Reference in New Issue
Block a user