Skip to content

Commit

Permalink
Add support for inline array aggregate filters
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewSisley committed Jul 11, 2022
1 parent 28229df commit c61e322
Show file tree
Hide file tree
Showing 10 changed files with 461 additions and 12 deletions.
13 changes: 8 additions & 5 deletions query/graphql/mapper/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,15 @@ func resolveAggregates(
fieldDesc, isField := desc.GetField(target.hostExternalName)
if isField && !fieldDesc.IsObject() {
// If the hostExternalName matches a non-object field
// we can just take it as a field-requestable as only
// objects are targetable-requestables.
// we don't have to search for it and can just construct the
// targeting info here.
hasHost = true
host = &Field{
Index: int(fieldDesc.ID),
Name: target.hostExternalName,
host = &Targetable{
Field: Field{
Index: int(fieldDesc.ID),
Name: target.hostExternalName,
},
Filter: ToFilter(target.filter, mapping),
}
} else {
childObjectIndex := mapping.FirstIndexOfName(target.hostExternalName)
Expand Down
4 changes: 4 additions & 0 deletions query/graphql/mapper/targetable.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,7 @@ func (t *Targetable) cloneTo(index int) *Targetable {
OrderBy: t.OrderBy,
}
}

func (t *Targetable) AsTargetable() (*Targetable, bool) {
return t, true
}
50 changes: 47 additions & 3 deletions query/graphql/planner/count.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,53 @@ func (n *countNode) Next() (bool, error) {
length := v.Len()

if source.Filter != nil {
docArray, isDocArray := property.([]core.Doc)
if isDocArray {
for _, doc := range docArray {
switch array := property.(type) {
case []core.Doc:
for _, doc := range array {
passed, err := mapper.RunFilter(doc, source.Filter)
if err != nil {
return false, err
}
if passed {
count += 1
}
}

case []bool:
for _, doc := range array {
passed, err := mapper.RunFilter(doc, source.Filter)
if err != nil {
return false, err
}
if passed {
count += 1
}
}

case []int64:
for _, doc := range array {
passed, err := mapper.RunFilter(doc, source.Filter)
if err != nil {
return false, err
}
if passed {
count += 1
}
}

case []float64:
for _, doc := range array {
passed, err := mapper.RunFilter(doc, source.Filter)
if err != nil {
return false, err
}
if passed {
count += 1
}
}

case []string:
for _, doc := range array {
passed, err := mapper.RunFilter(doc, source.Filter)
if err != nil {
return false, err
Expand Down
14 changes: 14 additions & 0 deletions query/graphql/planner/sum.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,24 @@ func (n *sumNode) Next() (bool, error) {
}
case []int64:
for _, childItem := range childCollection {
passed, err := mapper.RunFilter(childItem, source.Filter)
if err != nil {
return false, err
}
if !passed {
continue
}
sum += float64(childItem)
}
case []float64:
for _, childItem := range childCollection {
passed, err := mapper.RunFilter(childItem, source.Filter)
if err != nil {
return false, err
}
if !passed {
continue
}
sum += childItem
}
}
Expand Down
21 changes: 17 additions & 4 deletions query/graphql/schema/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,15 +291,28 @@ func (g *Generator) createExpandedFieldAggregate(
) {
for _, aggregateTarget := range f.Args {
target := aggregateTarget.Name()
var targetType string
var filterTypeName string
if target == parserTypes.GroupFieldName {
targetType = obj.Name()
filterTypeName = obj.Name() + "FilterArg"
} else {
targetType = obj.Fields()[target].Type.Name()
filterType := obj.Fields()[target].Type
if list, isList := filterType.(*gql.List); isList && gql.IsLeafType(list.OfType) {
// If it is a list of leaf types - the filter is just the set of OperatorBlocks
// that are supported by this type - there can be no field selections.
if notNull, isNotNull := list.OfType.(*gql.NonNull); isNotNull {
// GQL does not support '!' in type names, and so we have to manipulate the
// underlying name like this if it is a nullable type.
filterTypeName = fmt.Sprintf("NotNull%sOperatorBlock", notNull.OfType.Name())
} else {
filterTypeName = genTypeName(list.OfType, "OperatorBlock")
}
} else {
filterTypeName = filterType.Name() + "FilterArg"
}
}

expandedField := &gql.InputObjectFieldConfig{
Type: g.manager.schema.TypeMap()[targetType+"FilterArg"],
Type: g.manager.schema.TypeMap()[filterTypeName],
}
aggregateTarget.Type.(*gql.InputObject).AddFieldConfig("filter", expandedField)
}
Expand Down
4 changes: 4 additions & 0 deletions query/graphql/schema/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,15 @@ func defaultTypes() []gql.Type {

// Filter scalar blocks
booleanOperatorBlock,
notNullBooleanOperatorBlock,
dateTimeOperatorBlock,
floatOperatorBlock,
notNullFloatOperatorBlock,
idOperatorBlock,
intOperatorBlock,
notNullIntOperatorBlock,
stringOperatorBlock,
notNullstringOperatorBlock,

schemaTypes.CommitLinkObject,
schemaTypes.CommitObject,
Expand Down
100 changes: 100 additions & 0 deletions query/graphql/schema/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,25 @@ var booleanOperatorBlock = gql.NewInputObject(gql.InputObjectConfig{
},
})

// notNullBooleanOperatorBlock filter block for boolean! types.
var notNullBooleanOperatorBlock = gql.NewInputObject(gql.InputObjectConfig{
Name: "NotNullBooleanOperatorBlock",
Fields: gql.InputObjectConfigFieldMap{
"_eq": &gql.InputObjectFieldConfig{
Type: gql.Boolean,
},
"_ne": &gql.InputObjectFieldConfig{
Type: gql.Boolean,
},
"_in": &gql.InputObjectFieldConfig{
Type: gql.NewList(gql.NewNonNull(gql.Boolean)),
},
"_nin": &gql.InputObjectFieldConfig{
Type: gql.NewList(gql.NewNonNull(gql.Boolean)),
},
},
})

// dateTimeOperatorBlock filter block for DateTime types.
var dateTimeOperatorBlock = gql.NewInputObject(gql.InputObjectConfig{
Name: "DateTimeOperatorBlock",
Expand Down Expand Up @@ -111,6 +130,37 @@ var floatOperatorBlock = gql.NewInputObject(gql.InputObjectConfig{
},
})

// notNullFloatOperatorBlock filter block for Float! types.
var notNullFloatOperatorBlock = gql.NewInputObject(gql.InputObjectConfig{
Name: "NotNullFloatOperatorBlock",
Fields: gql.InputObjectConfigFieldMap{
"_eq": &gql.InputObjectFieldConfig{
Type: gql.Float,
},
"_ne": &gql.InputObjectFieldConfig{
Type: gql.Float,
},
"_gt": &gql.InputObjectFieldConfig{
Type: gql.Float,
},
"_ge": &gql.InputObjectFieldConfig{
Type: gql.Float,
},
"_lt": &gql.InputObjectFieldConfig{
Type: gql.Float,
},
"_le": &gql.InputObjectFieldConfig{
Type: gql.Float,
},
"_in": &gql.InputObjectFieldConfig{
Type: gql.NewList(gql.NewNonNull(gql.Float)),
},
"_nin": &gql.InputObjectFieldConfig{
Type: gql.NewList(gql.NewNonNull(gql.Float)),
},
},
})

// intOperatorBlock filter block for Int types.
var intOperatorBlock = gql.NewInputObject(gql.InputObjectConfig{
Name: "IntOperatorBlock",
Expand Down Expand Up @@ -142,6 +192,37 @@ var intOperatorBlock = gql.NewInputObject(gql.InputObjectConfig{
},
})

// notNullIntOperatorBlock filter block for Int! types.
var notNullIntOperatorBlock = gql.NewInputObject(gql.InputObjectConfig{
Name: "NotNullIntOperatorBlock",
Fields: gql.InputObjectConfigFieldMap{
"_eq": &gql.InputObjectFieldConfig{
Type: gql.Int,
},
"_ne": &gql.InputObjectFieldConfig{
Type: gql.Int,
},
"_gt": &gql.InputObjectFieldConfig{
Type: gql.Int,
},
"_ge": &gql.InputObjectFieldConfig{
Type: gql.Int,
},
"_lt": &gql.InputObjectFieldConfig{
Type: gql.Int,
},
"_le": &gql.InputObjectFieldConfig{
Type: gql.Int,
},
"_in": &gql.InputObjectFieldConfig{
Type: gql.NewList(gql.NewNonNull(gql.Int)),
},
"_nin": &gql.InputObjectFieldConfig{
Type: gql.NewList(gql.NewNonNull(gql.Int)),
},
},
})

// stringOperatorBlock filter block for string types.
var stringOperatorBlock = gql.NewInputObject(gql.InputObjectConfig{
Name: "StringOperatorBlock",
Expand All @@ -164,6 +245,25 @@ var stringOperatorBlock = gql.NewInputObject(gql.InputObjectConfig{
},
})

// notNullstringOperatorBlock filter block for string! types.
var notNullstringOperatorBlock = gql.NewInputObject(gql.InputObjectConfig{
Name: "NotNullStringOperatorBlock",
Fields: gql.InputObjectConfigFieldMap{
"_eq": &gql.InputObjectFieldConfig{
Type: gql.String,
},
"_ne": &gql.InputObjectFieldConfig{
Type: gql.String,
},
"_in": &gql.InputObjectFieldConfig{
Type: gql.NewList(gql.NewNonNull(gql.String)),
},
"_nin": &gql.InputObjectFieldConfig{
Type: gql.NewList(gql.NewNonNull(gql.String)),
},
},
})

// idOperatorBlock filter block for ID types.
var idOperatorBlock = gql.NewInputObject(gql.InputObjectConfig{
Name: "IDOperatorBlock",
Expand Down
71 changes: 71 additions & 0 deletions tests/integration/query/inline_array/with_average_filter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2022 Democratized Data Foundation
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package inline_array

import (
"testing"

testUtils "github.com/sourcenetwork/defradb/tests/integration"
)

func TestQueryInlineIntegerArrayWithsWithAverageWithFilter(t *testing.T) {
test := testUtils.QueryTestCase{
Description: "Simple inline array, filtered average of integer array",
Query: `query {
users {
Name
_avg(FavouriteIntegers: {filter: {_gt: 0}})
}
}`,
Docs: map[int][]string{
0: {
(`{
"Name": "Shahzad",
"FavouriteIntegers": [-1, 2, -1, 1, 0]
}`)},
},
Results: []map[string]interface{}{
{
"Name": "Shahzad",
"_avg": float64(1.5),
},
},
}

executeTestCase(t, test)
}

func TestQueryInlineFloatArrayWithsWithAverageWithFilter(t *testing.T) {
test := testUtils.QueryTestCase{
Description: "Simple inline array, filtered average of float array",
Query: `query {
users {
Name
_avg(FavouriteFloats: {filter: {_lt: 9}})
}
}`,
Docs: map[int][]string{
0: {
(`{
"Name": "Shahzad",
"FavouriteFloats": [3.4, 3.6, 10]
}`)},
},
Results: []map[string]interface{}{
{
"Name": "Shahzad",
"_avg": 3.5,
},
},
}

executeTestCase(t, test)
}
Loading

0 comments on commit c61e322

Please sign in to comment.