Skip to content
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

feat: Filter alias target #3201

Merged
merged 5 commits into from
Nov 6, 2024

Conversation

nasdf
Copy link
Member

@nasdf nasdf commented Nov 1, 2024

Relevant issue(s)

Resolves #3194

Description

This PR adds alias targeting in filters.

Aggregate targets are not included in this PR as they require more changes.

Tasks

  • I made sure the code is well commented, particularly hard-to-understand areas.
  • I made sure the repository-held documentation is changed accordingly.
  • I made sure the pull request title adheres to the conventional commit style (the subset used in the project can be found in tools/configs/chglog/config.yml).
  • I made sure to discuss its limitations such as threats to validity, vulnerability to mistake and misuse, robustness to invalidation of assumptions, resource requirements, ...

How has this been tested?

Added and updated integration tests.

Specify the platform(s) on which this was tested:

  • MacOS

@nasdf nasdf added the feature New feature or request label Nov 1, 2024
@nasdf nasdf added this to the DefraDB v0.15 milestone Nov 1, 2024
@nasdf nasdf self-assigned this Nov 1, 2024
Copy link

codecov bot commented Nov 1, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 77.57%. Comparing base (2b0b862) to head (cfd3ada).
Report is 1 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #3201      +/-   ##
===========================================
+ Coverage    77.41%   77.57%   +0.15%     
===========================================
  Files          375      375              
  Lines        34756    34790      +34     
===========================================
+ Hits         26906    26986      +80     
+ Misses        6237     6203      -34     
+ Partials      1613     1601      -12     
Flag Coverage Δ
all-tests 77.57% <100.00%> (+0.15%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
internal/connor/connor.go 100.00% <100.00%> (ø)
internal/connor/eq.go 92.31% <100.00%> (+0.64%) ⬆️
internal/core/doc.go 94.62% <100.00%> (+0.26%) ⬆️
internal/planner/filter/copy_field.go 96.67% <100.00%> (+0.44%) ⬆️
internal/planner/mapper/errors.go 66.67% <100.00%> (+16.67%) ⬆️
internal/planner/mapper/mapper.go 87.35% <100.00%> (+0.14%) ⬆️
internal/planner/mapper/targetable.go 75.24% <100.00%> (-0.69%) ⬇️
internal/request/graphql/parser/filter.go 60.00% <100.00%> (ø)
internal/request/graphql/schema/generate.go 86.93% <100.00%> (+0.05%) ⬆️

... and 17 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

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

@nasdf nasdf requested a review from a team November 1, 2024 18:11
Copy link
Contributor

@AndrewSisley AndrewSisley left a comment

Choose a reason for hiding this comment

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

Aggregate targets are not included in this PR as they require more changes.

question: I thought the aggregate filters were of the same type as normal filters, in which case they will now have a (presumably broken) alias property on them. Have you checked what happens when someone tries to use it, and when do you plan on adding support for it?

praise: Looks really good Keenan, just a couple of localised suggestions for you - will be really good to have this in - we've been talking about it for a long time now lol and the JSON-type solution is really nice IMO :)

@@ -85,6 +85,9 @@ func (k *ObjectProperty) GetProp(data any) any {
if data == nil {
return nil
}
if _, ok := data.(core.Doc); ok {
Copy link
Contributor

Choose a reason for hiding this comment

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

question: Why are you checking for this particular type?

suggestion: Depending on the answer to the above question, it might be better to instead check the success of the cast below:

object, ok := data.(map[string]any)
if !ok {
  return nil // this can happen when an alias target does not exist
}

Copy link
Member Author

Choose a reason for hiding this comment

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

In past reviews you had a preference for specific type checks instead of a single type assertion. I thought I could anticipate this one before review.

Copy link
Contributor

Choose a reason for hiding this comment

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

My preferences probably vary by context. Here you are casting to the type you actually want the line below, it doesn't seem to make sense to care about some other type as well.

@@ -992,6 +992,12 @@ func resolveInnerFilterDependencies(
newFields := []Requestable{}

for key, value := range source {
// alias fields are guarenteed to be resolved
// because they refer to existing fields
Copy link
Contributor

Choose a reason for hiding this comment

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

praise: Thanks for this explanation :)

@@ -93,20 +93,20 @@ func parseFilterFieldsForDescriptionMap(
fields := make([]client.FieldDefinition, 0)
for k, v := range conditions {
switch k {
case "_or", "_and":
case request.FilterOpOr, request.FilterOpAnd:
Copy link
Contributor

Choose a reason for hiding this comment

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

praise: Thanks for these cleanups :)

},
testUtils.Request{
Request: `query {
Users(filter: {_alias: {}}) {
Copy link
Contributor

Choose a reason for hiding this comment

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

question: Is this consistent with the behaviour of other 'empty' filter properties?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, there's no existing tests but I double checked

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice, thanks Keenan :)

},
testUtils.Request{
Request: `query {
Users(filter: {_alias: null}) {
Copy link
Contributor

Choose a reason for hiding this comment

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

question: I thought we could make input params not-null without making them required?

suggestion: If so, please make the alias param not-null, and change this test so that it expects an error :)

Copy link
Member Author

@nasdf nasdf Nov 4, 2024

Choose a reason for hiding this comment

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

I think we can have not null inputs but we need a default value. There will probably be a lot of other inputs that need a similar update.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah okay, must be a memory of the variable stuff then - thanks :)


func TestQuerySimple_WithNonAliasedField_Succeeds(t *testing.T) {
test := testUtils.TestCase{
Description: "Simple query with non aliased filter",
Copy link
Contributor

Choose a reason for hiding this comment

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

praise: Thank you for adding this test, and the ones for the compound filters!

},
testUtils.Request{
Request: `query {
Users(filter: {_alias: {UserAge: {_eq: 21}}}) {
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: Would it not be better to error in this case? This looks like it would mainly come about through a user-programming error, in which case they would likely much prefer an error over a silent failure.

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 have a preference to not return errors from otherwise validly typed queries. I also don't think the added complexity of checking aliases would be worth the benefits.

Copy link
Contributor

Choose a reason for hiding this comment

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

The added complexity for us should pretty minimal no (might even be just be converting a continue to a return NewErrorFoo)? And the potential time/money savings for our users could be very large.

Copy link
Member Author

Choose a reason for hiding this comment

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

If you see a solution that simple I'd definitely consider it.

It would be really difficult to tell whether a filter property is targeting a relationship field or a nested JSON property without adding a good amount of new validation logic.

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 found a kind of simple solution for this. Let me know what you think.

Doc: `{
"name": "Painted House",
"rating": 4.9,
"author_id": "bae-e1ea288f-09fa-55fa-b0b5-0ac8941ea35b"
Copy link
Contributor

Choose a reason for hiding this comment

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

todo: Please use DocIndex for these IDs instead of strings, it reduces the maintenance and readability overhead quite a bit IMO, it will also force the test to create the docs in a 'correct' order :)

Copy link
Member Author

Choose a reason for hiding this comment

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

done

Copy link
Contributor

@islamaliev islamaliev left a comment

Choose a reason for hiding this comment

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

It's a nice feature. Good job Keenan.
I left some minor suggestions/concerns.

internal/connor/connor.go Outdated Show resolved Hide resolved
internal/planner/filter/copy_field.go Show resolved Hide resolved
@@ -62,7 +66,7 @@ func matchWith(op string, conditions, data any) (bool, error) {
return anyOp(conditions, data)
case AllOp:
return all(conditions, data)
case EqualOp:
case EqualOp, request.FilterOpAlias:
Copy link
Collaborator

Choose a reason for hiding this comment

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

suggestion: I think it's fine to duplicate the const for _alias here and avoid the added dependency.

Copy link
Member Author

Choose a reason for hiding this comment

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

done

Copy link
Contributor

@islamaliev islamaliev left a comment

Choose a reason for hiding this comment

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

LGTM! Good work, Keenan.

@nasdf nasdf requested a review from AndrewSisley November 6, 2024 17:06
Copy link
Contributor

@AndrewSisley AndrewSisley left a comment

Choose a reason for hiding this comment

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

LGTM - thanks Keenan :)

Remaining suggestion from me is very optional.


func (k *ObjectProperty) GetOperatorOrDefault(defaultOp string) string {
return defaultOp
_, ok := data.(core.Doc)
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: Made me smile a little to see you do this again in this PR - and I'm curious as to why/where you think I prefer this 😁 (are you thinking back to me preferring nil checks vs failed casting?)

My suggestion is the same as the last, code is simpler IMO, and avoids having to care about an extra, somewhat irrelevant type:

docMap, ok := data.(map[string]any)
if !ok {
  return nil, defaultOp, NewErrFieldOrAliasNotFound(k.Name)
}
return docMap[k.Name], defaultOp, nil

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 think I burned that one into my brain. Should be fixed now.

@nasdf nasdf merged commit c8fd3b1 into sourcenetwork:develop Nov 6, 2024
42 of 43 checks passed
@islamaliev
Copy link
Contributor

bug bash result: ran all tests from integration tests and many other related tests. Everything is good.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Filter alias targeting
4 participants