Skip to content

Support for Aggregate Queries #4207

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Nov 12, 2017
Merged

Conversation

dplewis
Copy link
Member

@dplewis dplewis commented Sep 24, 2017

Adds a new endpoint for distinct and aggregate queries.

Basic support for Postgres aggregates (WIP).

Closes: #4197, #2238

@codecov
Copy link

codecov bot commented Sep 24, 2017

Codecov Report

Merging #4207 into master will increase coverage by 0.12%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #4207      +/-   ##
==========================================
+ Coverage    92.5%   92.63%   +0.12%     
==========================================
  Files         118      119       +1     
  Lines        8256     8400     +144     
==========================================
+ Hits         7637     7781     +144     
  Misses        619      619
Impacted Files Coverage Δ
src/RestQuery.js 95.47% <ø> (ø) ⬆️
src/Routers/AggregateRouter.js 100% <100%> (ø)
src/Adapters/Storage/Mongo/MongoCollection.js 97.67% <100%> (+0.17%) ⬆️
src/Adapters/Storage/Mongo/MongoStorageAdapter.js 93.33% <100%> (+0.37%) ⬆️
...dapters/Storage/Postgres/PostgresStorageAdapter.js 96.84% <100%> (+0.33%) ⬆️
src/Controllers/DatabaseController.js 94.76% <100%> (+0.08%) ⬆️
src/ParseServer.js 94.61% <100%> (+0.04%) ⬆️
src/RestWrite.js 93.39% <0%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 842343a...9c866c7. Read the comment docs.

@dplewis
Copy link
Member Author

dplewis commented Sep 24, 2017

@flovilmart Can you think of any other tests? For Postgres we can add more functionality as it is requested. Also the pipeline is ordered so we don't have to worry about that.

Copy link
Contributor

@flovilmart flovilmart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests look great, a few nits. I’ll do a larger review later tonight! Awesome job man!

it('group sum query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: { _id: null, total: { $sum: '$score' } },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_id should be transformed (from objectId), which would be true for all keys passed here :)

Copy link
Member Author

@dplewis dplewis Sep 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So group: { objectId: null, total: { $sum: '$score' } }

And it should return [ { objectId: null, total: ... } ] instead of [ { _id: null, total: ... } ]

and group: { objectId: '$name', total: { $sum: '$score' } }

Should return [ { name: ..., total: ... } ]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, in and out, those should not be the internal storage columns but the externals

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should an error be thrown if _id is used?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, most probably!

@@ -1499,6 +1499,14 @@ export class PostgresStorageAdapter {
if (countField) {
results[0][countField] = parseInt(results[0][countField], 10);
}
results.forEach(result => {
if (result.hasOwnProperty('_id')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can’t we use the usual transformers here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just about to change it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can use the usual transformers like buildWhereClause. We might be able to reuse skip, limit, sort that is in find(). It not complete yet but I could try to refactor it in the second iteration

@benishak
Copy link
Contributor

benishak commented Oct 1, 2017

@dplewis great job!
Please add CLP Validation for these operations, default to MasterKey maybe?

You could also add a config attribute for the ALLOWED_KEYS

@dplewis
Copy link
Member Author

dplewis commented Oct 1, 2017

@benishak Thanks, I don't know much about class level permissions. Are CLP needed here or are they overriden when you use masterKey which is required here?

@flovilmart
Copy link
Contributor

CLP don’t really matter for that iteration, as all endpoints are protected by masterKey. It seems unsafe to allow any call to the aggregation pipeline without a masterKey.

@benishak
Copy link
Contributor

benishak commented Oct 1, 2017

yeah I fully agree, default behavior should be masterkey only, but I imagine there could be also use cases where non master user would need to run such query like Data analytics dashboards, CRM ... etc. Therefore I thought to put some flexibility but make sure that the default is masterkey only.

@dplewis
Copy link
Member Author

dplewis commented Oct 6, 2017

@vitaly-t more bad habits, can you look this over?

@vitaly-t
Copy link
Contributor

vitaly-t commented Oct 6, 2017

@dplewis same story, in terms of the connections usage it all seems fine, but in terms of using ES6 strings to format queries - it is all over the place :)

.then((resp) => {
expect(resp.results[0].hasOwnProperty('objectId')).toBe(true);
expect(resp.results[0].objectId).toBe(null);
expect(resp.results[0].total).toBe(50);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious what happens if one of the score values happens to be unset/null? I'm assuming it just omits it but would be nice to know for sure.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just tried. If unset/null it gets ignored

}).catch(done.fail);
});

it('sort ascending query', (done) => {
Copy link
Contributor

@montymxb montymxb Nov 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it duplicates functionality we already have in standard queries, is this needed for something in particular where the original won't do? Read through the rest, nvm this comment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the user builds their aggregate queries they have the option to limit, sort, skip. This test is also there for Postgres support

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I realized that right after I noted this, you're good here.

}).catch(done.fail);
});

it('distint query with where', (done) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo on distint

}).catch(done.fail);
});

it('distint query with where string', (done) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo on distint again

@@ -1366,6 +1370,140 @@ export class PostgresStorageAdapter {
});
}

distinct(className, schema, query, fieldName) {
debug('distinct', className, query);
Copy link
Contributor

@montymxb montymxb Nov 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious what's the debug mark here for? Nvm, logger.

Copy link
Member Author

@dplewis dplewis Nov 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing PARSE_SERVER_LOG_LEVEL=debug

It really helps for testing PG

debug('distinct', className, query);
let field = fieldName;
let column = fieldName;
if (fieldName.indexOf('.') >= 0) {
Copy link
Contributor

@montymxb montymxb Nov 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although this will work the value won't ever be 0, just -1 or > 1. Doesn't mean this needs to be changed, but it could also be >= 1 for be more specific about what's expected.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const str = '.';
str.indexOf('.') will return 0; 

It will never happen but it covers all cases

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright.

if (fieldName.indexOf('.') === -1) {
return results.map(object => object[field]);
}
const child = fieldName.split('.')[1];
Copy link
Contributor

@montymxb montymxb Nov 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I pass a field name like myField. I think this would bug out. Can you check on/add a case to look at nested behavior where the dot is missing following text?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally something like .myField should be invalid. I'm not sure if they are currently prevented though, if they aren't they should probably be screened ahead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't myfield. be an invalid field name?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, just covering bases. Took a look, this is good too.

if (sort[key] === 1) {
return `"${key}" ASC`;
}
return `"${key}" DESC`;
Copy link
Contributor

@montymxb montymxb Nov 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps a bit silly, but you could mark sort with a value of 2 or greater and it would technically fall into the -1 descending category.

body[key]._id = body[key].objectId;
delete body[key].objectId;
}
pipeline.push({ [`$${key}`]: body[key] });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$$? Double checking that this isn't just a working typo...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scratch this, you're good here as well 👍

Copy link
Contributor

@montymxb montymxb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good, just a couple typos from what I could tell.

@montymxb
Copy link
Contributor

montymxb commented Nov 7, 2017

@flovilmart as it stands I think this is ready, but I want to have a second opinion before we go ahead and merge it in.

@dplewis
Copy link
Member Author

dplewis commented Nov 12, 2017

@flovilmart can we get this one in?

@flovilmart
Copy link
Contributor

Yes, let’s go!

@flovilmart flovilmart merged commit 7223add into parse-community:master Nov 12, 2017
@montymxb
Copy link
Contributor

@dplewis just a quick thing, looks like we still need to properly clear out _ prefixed values in the return for aggregation, particularly on the _User class. Seeing things like _created_at and such in the results. Need to take care of that before we can consider the next release. @flovilmart letting you know as well.

@dplewis dplewis deleted the aggregate branch November 15, 2017 00:50
@ranhsd
Copy link
Contributor

ranhsd commented Mar 25, 2018

Hi @dplewis there are some docs on how to use it? What about the client SDK's? for iOS / Android ?

thanks,
Ran.

@dplewis
Copy link
Member Author

dplewis commented Mar 25, 2018

@ranhsd To use this master key is required. iOS and Android cant use master key.

@ranhsd
Copy link
Contributor

ranhsd commented Mar 25, 2018

Hi @dplewis yes just noticed that. so I think we need to mention there that it can be achieved via cloud code.

UnderratedDev pushed a commit to UnderratedDev/parse-server that referenced this pull request Mar 21, 2020
* Support for Aggregate Queries

* improve pg and coverage

* Mongo 3.4 aggregates and tests

* replace _id with objectId

* improve tests for objectId

* project with group query

* typo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants