Skip to content

Commit

Permalink
Add and-or support for inline array agg filters
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewSisley committed Sep 8, 2022
1 parent 3e1f2a0 commit 8ccc4a7
Show file tree
Hide file tree
Showing 4 changed files with 1,200 additions and 6 deletions.
68 changes: 65 additions & 3 deletions query/graphql/schema/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ package schema

import (
"context"
"errors"
"fmt"

"github.com/graphql-go/graphql/language/ast"
"github.com/graphql-go/graphql/language/source"

"github.com/sourcenetwork/defradb/client"
"github.com/sourcenetwork/defradb/errors"
"github.com/sourcenetwork/defradb/logging"

gql "github.com/graphql-go/graphql"
Expand Down Expand Up @@ -130,6 +130,11 @@ func (g *Generator) fromAST(ctx context.Context, document *ast.Document) ([]*gql
}

generatedFilterBaseArgs := []*gql.InputObject{}
for _, defaultType := range inlineArrayTypes() {
leafFilterArg := g.genLeafFilterArgInput(defaultType)
generatedFilterBaseArgs = append(generatedFilterBaseArgs, leafFilterArg)
}

for _, t := range g.typeDefs {
generatedFilterBaseArg, hasFilter := g.tryGenTypeFilterBaseArgInput(t)
if !hasFilter {
Expand Down Expand Up @@ -297,9 +302,9 @@ func (g *Generator) createExpandedFieldAggregate(
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())
filterTypeName = fmt.Sprintf("NotNull%sFilterArg", notNull.OfType.Name())
} else {
filterTypeName = genTypeName(list.OfType, "OperatorBlock")
filterTypeName = genTypeName(list.OfType, "FilterArg")
}
} else {
filterTypeName = targeted.Type.Name() + "FilterArg"
Expand Down Expand Up @@ -1129,6 +1134,63 @@ func (g *Generator) genTypeFilterArgInput(obj *gql.Object) *gql.InputObject {
return selfRefType
}

func (g *Generator) genLeafFilterArgInput(obj gql.Type) *gql.InputObject {
var selfRefType *gql.InputObject

var filterTypeName string
if notNull, isNotNull := obj.(*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%s", notNull.OfType.Name())
} else {
filterTypeName = obj.Name()
}

inputCfg := gql.InputObjectConfig{
Name: fmt.Sprintf("%s%s", filterTypeName, "FilterArg"),
}

var fieldThunk gql.InputObjectConfigFieldMapThunk = func() (gql.InputObjectConfigFieldMap, error) {
fields := gql.InputObjectConfigFieldMap{}

compoundListType := &gql.InputObjectFieldConfig{
Type: gql.NewList(selfRefType),
}

fields["_and"] = compoundListType
fields["_or"] = compoundListType

operatorBlockName := fmt.Sprintf("%s%s", filterTypeName, "OperatorBlock")
operatorType, hasOperatorType := g.manager.schema.TypeMap()[operatorBlockName]
if !hasOperatorType {
// This should be impossible
return nil, errors.New("Operator block not found", errors.NewKV("Name", operatorBlockName))
}

operatorObject, isInputObj := operatorType.(*gql.InputObject)
if !isInputObj {
// This should be impossible
return nil, errors.New(
"Invalid cast",
errors.NewKV("Expected type", "*gql.InputObject"),
errors.NewKV("Actual type", fmt.Sprintf("%T", operatorType)),
)
}

for f, field := range operatorObject.Fields() {
fields[f] = &gql.InputObjectFieldConfig{
Type: field.Type,
}
}

return fields, nil
}

inputCfg.Fields = fieldThunk
selfRefType = gql.NewInputObject(inputCfg)
return selfRefType
}

// input {Type.Name}FilterBaseArg { ... }
func (g *Generator) tryGenTypeFilterBaseArgInput(obj *gql.Object) (*gql.InputObject, bool) {
inputCfg := gql.InputObjectConfig{
Expand Down
13 changes: 13 additions & 0 deletions query/graphql/schema/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,19 @@ func defaultDirectivesType() []*gql.Directive {
}
}

func inlineArrayTypes() []gql.Type {
return []gql.Type{
gql.Boolean,
gql.Float,
gql.Int,
gql.String,
gql.NewNonNull(gql.Boolean),
gql.NewNonNull(gql.Float),
gql.NewNonNull(gql.Int),
gql.NewNonNull(gql.String),
}
}

// default type map includes all the native scalar types
func defaultTypes() []gql.Type {
return []gql.Type{
Expand Down
27 changes: 27 additions & 0 deletions tests/integration/query/inline_array/with_count_filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,33 @@ func TestQueryInlineNillableIntegerArrayWithCountWithFilter(t *testing.T) {
executeTestCase(t, test)
}

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

executeTestCase(t, test)
}

func TestQueryInlineFloatArrayWithCountWithFilter(t *testing.T) {
test := testUtils.QueryTestCase{
Description: "Simple inline array, filtered count of float array",
Expand Down
Loading

0 comments on commit 8ccc4a7

Please sign in to comment.