-
Notifications
You must be signed in to change notification settings - Fork 50
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: Inline array filters #3028
feat: Inline array filters #3028
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #3028 +/- ##
===========================================
+ Coverage 79.45% 79.46% +0.01%
===========================================
Files 333 336 +3
Lines 25840 26075 +235
===========================================
+ Hits 20530 20720 +190
- Misses 3853 3883 +30
- Partials 1457 1472 +15
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 15 files with indirect coverage changes Continue to review full report in Codecov by Sentry.
|
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.
LGTM! Very nice and thanks for all the tests.
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.
Looks good. I just requested few changes.
// matching if all of them match. | ||
func all(condition, data any) (bool, error) { | ||
switch t := data.(type) { | ||
case []string: |
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.
todo: I think it's better to use reflection here to avoid repetitive code:
func all(condition, data any) {
val := reflect.ValueOf(v)
if val.Kind() != reflect.Slice {
return false, client.NewErrUnhandledType("data", data)
}
for i := 0; i < val.Len(); i++ {
m, err := eq(condition, val.Index(i).Interface())
if err != nil {
return false, err
} else if !m {
return false, nil
}
}
}
return true, nil
}
Or at least use generic:
func allInArr[T any](slice []T, condition any) (bool, error) {
for _, item := range slice {
m, err := eq(condition, item)
if err != nil {
return false, err
}
if !m {
return false, nil
}
}
return true, nil
}
func all(condition, data any) (bool, error) {
switch t := data.(type) {
case []string:
return allInArr(t, condition)
case []immutable.Option[string]:
return allInArr(t, condition)
case []int64:
return allInArr(t, condition)
// ...
This will also improve test coverage.
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.
reflect is a bit slow and not fully supported in wasm so lets try to avoid it for now. I like the generic idea though.
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.
100% agree with Fred, we should avoid reflect, especially in a performance sensitive area like this. Extracting the shared logic to a generic func would be good though.
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.
done
// matching if any of them match. | ||
func anyOp(condition, data any) (bool, error) { | ||
switch t := data.(type) { | ||
case []string: |
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.
todo: please use the same approach as with all
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.
done
internal/connor/eq.go
Outdated
if arr.HasValue() { | ||
data = arr.Value() | ||
} else { | ||
data = nil |
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.
suggestion: this could also be turned into generic code
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.
done
Fields: gql.InputObjectConfigFieldMap{ | ||
"_any": &gql.InputObjectFieldConfig{ | ||
Description: anyOperatorDescription, | ||
Type: op, | ||
}, | ||
"_all": &gql.InputObjectFieldConfig{ | ||
Description: allOperatorDescription, | ||
Type: op, | ||
}, | ||
"_none": &gql.InputObjectFieldConfig{ | ||
Description: noneOperatorDescription, | ||
Type: op, | ||
}, | ||
}, |
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.
suggestion: this can be extracted into a function and used everywhere
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 have mixed feelings about the extraction of the full InputObjectFieldConfig
blocks to a shared func (it couples them together, and hides what each InputObject contains, yet the enforcement of consistency is nice).
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 preference is to keep them as is because it makes modifying each individual type easier.
testUtils "github.com/sourcenetwork/defradb/tests/integration" | ||
) | ||
|
||
func TestQueryInlineStringArrayWithAllFilter(t *testing.T) { |
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.
suggestion: we try to stick to the following test naming convention:
<Category>_<Condition|Prerequisite>_<Result|Expectation>
In this case it could be
TestQueryInlineStringArray_WithAllFilter_Succeeds
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.
nitpick: I did think we agreed to not force that suggestion upon developers though, especially the last block.
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 agree that that last part is optional
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.
done
Users(filter: {testScores: {_all: {_lt: 70}}}) { | ||
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.
suggestion: would be also great to see a reassurance (in form of another test) that this new op works with compound conditions. For example:
filter: {testScores: {_all: {_and: [{_lt: 70}, {_gte: 0}]}}}
And to do the same with other new ops.
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've added two compound tests.
notNullBooleanOpBlock := schemaTypes.NotNullBooleanOperatorBlock() | ||
notNullStringOpBlock := schemaTypes.NotNullStringOperatorBlock() | ||
notNullJSONOpBlock := schemaTypes.NotNullJSONOperatorBlock(jsonScalarType) | ||
notNullBlobOpBlock := schemaTypes.NotNullBlobOperatorBlock(blobScalarType) |
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.
question: I'm curious - why did you pull the existing stuff out of the return and into temp vars? And then add new func calls within the return?
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.
It made sense to me organizationally. Also the temp vars are used as inputs to the calls in the return.
Name: "BooleanListOperatorBlock", | ||
Description: "These are the set of filter operators available for use when filtering on [Boolean] values.", | ||
Fields: gql.InputObjectConfigFieldMap{ | ||
"_any": &gql.InputObjectFieldConfig{ |
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.
suggestion: Independent of Islam's suggestion, it might be nice to extract the names to consts in the client/request package and then also reference them in the connor package as well as 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.
This is out of scope for this PR imo, but I will create an issue for this.
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.
LGTM!
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.
LGTM
Bug bash results: all good |
Relevant issue(s)
Resolves #2857
Description
This PR adds three new filter types for inline array types:
_all
,_any
, and_none
.Tasks
How has this been tested?
Added integration tests
Specify the platform(s) on which this was tested: