-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Conversation
Codecov Report
@@ 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
Continue to review full report at Codecov.
|
@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. |
There was a problem hiding this 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!
spec/ParseQuery.Aggregate.spec.js
Outdated
it('group sum query', (done) => { | ||
const options = Object.assign({}, masterKeyOptions, { | ||
body: { | ||
group: { _id: null, total: { $sum: '$score' } }, |
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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: ... } ]
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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')) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
@dplewis great job! You could also add a config attribute for the |
@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? |
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. |
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. |
@vitaly-t more bad habits, can you look this over? |
@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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
spec/ParseQuery.Aggregate.spec.js
Outdated
}).catch(done.fail); | ||
}); | ||
|
||
it('distint query with where', (done) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo on distint
spec/ParseQuery.Aggregate.spec.js
Outdated
}).catch(done.fail); | ||
}); | ||
|
||
it('distint query with where string', (done) => { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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 Nvm, logger.debug
mark here for?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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]; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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`; |
There was a problem hiding this comment.
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] }); |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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 👍
There was a problem hiding this 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.
@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. |
@flovilmart can we get this one in? |
Yes, let’s go! |
@dplewis just a quick thing, looks like we still need to properly clear out |
Hi @dplewis there are some docs on how to use it? What about the client SDK's? for iOS / Android ? thanks, |
@ranhsd To use this master key is required. iOS and Android cant use master key. |
Hi @dplewis yes just noticed that. so I think we need to mention there that it can be achieved via cloud code. |
* 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
Adds a new endpoint for distinct and aggregate queries.
Basic support for Postgres aggregates (WIP).
Closes: #4197, #2238