Skip to content

Support for Aggregate Queries#4207

Merged
flovilmart merged 8 commits into
parse-community:masterfrom
dplewis:aggregate
Nov 12, 2017
Merged

Support for Aggregate Queries#4207
flovilmart merged 8 commits into
parse-community:masterfrom
dplewis:aggregate

Conversation

@dplewis

@dplewis dplewis commented Sep 24, 2017

Copy link
Copy Markdown
Member

Adds a new endpoint for distinct and aggregate queries.

Basic support for Postgres aggregates (WIP).

Closes: #4197, #2238

@codecov

codecov Bot commented Sep 24, 2017

Copy link
Copy Markdown

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

dplewis commented Sep 24, 2017

Copy link
Copy Markdown
Member Author

@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.

@flovilmart flovilmart left a comment

Copy link
Copy Markdown
Contributor

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!

Comment thread spec/ParseQuery.Aggregate.spec.js Outdated
it('group sum query', (done) => {
const options = Object.assign({}, masterKeyOptions, {
body: {
group: { _id: null, total: { $sum: '$score' } },

Copy link
Copy Markdown
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 :)

@dplewis dplewis Sep 24, 2017

Copy link
Copy Markdown
Member Author

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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yep, most probably!

results[0][countField] = parseInt(results[0][countField], 10);
}
results.forEach(result => {
if (result.hasOwnProperty('_id')) {

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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

benishak commented Oct 1, 2017

Copy link
Copy Markdown
Contributor

@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

dplewis commented Oct 1, 2017

Copy link
Copy Markdown
Member Author

@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
Copy Markdown
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

benishak commented Oct 1, 2017

Copy link
Copy Markdown
Contributor

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

dplewis commented Oct 6, 2017

Copy link
Copy Markdown
Member Author

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

@vitaly-t

vitaly-t commented Oct 6, 2017

Copy link
Copy Markdown
Contributor

@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
Copy Markdown
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
Copy Markdown
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) => {

@montymxb montymxb Nov 4, 2017

Copy link
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
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.

Comment thread spec/ParseQuery.Aggregate.spec.js Outdated
}).catch(done.fail);
});

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

typo on distint

Comment thread spec/ParseQuery.Aggregate.spec.js Outdated
}).catch(done.fail);
});

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

Copy link
Copy Markdown
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

}

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

@montymxb montymxb Nov 4, 2017

Copy link
Copy Markdown
Contributor

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.

@dplewis dplewis Nov 4, 2017

Copy link
Copy Markdown
Member Author

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) {

@montymxb montymxb Nov 4, 2017

Copy link
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
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];

@montymxb montymxb Nov 4, 2017

Copy link
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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`;

@montymxb montymxb Nov 4, 2017

Copy link
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
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 👍

@montymxb montymxb left a comment

Copy link
Copy Markdown
Contributor

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

montymxb commented Nov 7, 2017

Copy link
Copy Markdown
Contributor

@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

dplewis commented Nov 12, 2017

Copy link
Copy Markdown
Member Author

@flovilmart can we get this one in?

@flovilmart

Copy link
Copy Markdown
Contributor

Yes, let’s go!

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

Copy link
Copy Markdown
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

ranhsd commented Mar 25, 2018

Copy link
Copy Markdown
Contributor

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

thanks,
Ran.

@dplewis

dplewis commented Mar 25, 2018

Copy link
Copy Markdown
Member Author

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

@ranhsd

ranhsd commented Mar 25, 2018

Copy link
Copy Markdown
Contributor

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