-
-
Notifications
You must be signed in to change notification settings - Fork 127
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
Feature generic query builder #71
Conversation
Continuing to play around on this branch, the last commit may be of interest to you @johannes-scharlach as it allows creating filter aggregations using this generic api. See the last test for an example: https://github.com/danpaz/bodybuilder/pull/71/files#diff-507f3cb6e87ded24d8ab794a830ded03R110. |
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 like the idea of being more generic and totally see the necessity for it to be able to keep up with the changes in the DSL.
However I don't think this is the approach I would go down with:
To me there are 3 elements to this library: The QueryBuilder (only for filter
and query
), the AggregationBuilder and the BodyBuilder (that brings everything together, is responsible for size
, etc.). I'll see if I can come up with a concept MR, similar to this one, so that we can compare approaches :)
t.plan(1) | ||
|
||
const result = new QueryBuilder().aggregation('filter', null, 'red_products', (b) => { | ||
return b.filter('term', 'color', 'red').aggregation('avg', 'price', 'avg_price') |
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.
hmmm.... b.filter
makes sense for filters
and filter
aggregations, but otherwise not. Should that really be available by default?
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.
My thought was to be as flexible as possible, so this approach allows combining queries, filters and aggregations arbitrarily. Here are a few more examples where this type of composition is needed:
Is your concern that this approach opens the possibility for someone to build an invalid query?
let newAggregation = this._buildAggregation(...args) | ||
|
||
if (_.isFunction(nested)) { | ||
let clause = newAggregation[_.findKey(newAggregation)] |
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.
btw: This is the same as
const clause = _.find(newAggregation)
|
||
return { | ||
[name]: { | ||
[type]: (() => _.assign({field}, opts))() |
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.
in this case field
can be overwritten by opts
. Would rather expect
Object.assign({}, opts, {field})
(I also think I understood that _.assign
is deprecated in favour of Object.assign
, but I might be mistaken)
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.
@johannes-scharlach (No, you are correct. It is.)
let clause = {} | ||
|
||
if (field && value && opts) { | ||
clause = _.merge({[field]: value}, opts) |
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.
Also here the Object.assign({}, opts, {[field]: value})
approach?
Really appreciate your feedback @johannes-scharlach, I think it's important to get this abstraction right. Looking forward to your PR! |
I've found a problem when doing something like:
What happens is that the first two queries in the chain ( If I reverse the order so that I have the complex query at the top of the chain:
I get a wrong logical grouping where all the queries are processed but instead of having something like:
I get
I believe your recursion needs looking into when there is multi level nesting of queries/filters. This is a real-world (albeit redacted a bit) example query which caused the problems:
So to do this I would do (excluding _source, from and size):
When running this only the last (nested -> path: tests -> ...) is returned. The rest gets discarded.
Of course, maybe I am doing something wrong :) |
Also, I tried to reproduce the example about quering nested objects given on the official elasticsearch site, and it fails. Here I reproduce the test case so you can just cut and paste it into
This is the output:
|
…npaz/bodybuilder into feature-generic-query-builder # Conflicts: # test/query-builder.js
@msanguineti thanks for testing this out, looks like there's more work needed to get the nesting right. |
Made some updates |
Now I remember why I've been using _.assign instead of Object.assign: this is only available in node >= 4 (see failing tests). That's fine, I think we can drop support for node 0.10 and 0.12 soon considering they're both reaching end of their maintenance periods. |
split into individual builders
For experimentation, not meant to merge.
Linked to #65 #70. Following patterns established in #64 and #16 (thanks @nfantone and @johannes-scharlach!).
Introduces a generic QueryBuilder that
query
function.nested
,has_child
andhas_parent
) by passing a function as the last argument toquery
.See tests for usage. Run tests using
babel-tape-runner test/query-builder.js | tap-spec
.🙇