diff --git a/tests/integration/explain/default/with_average_join_test.go b/tests/integration/explain/default/with_average_join_test.go index a48a1b97d2..1612455e57 100644 --- a/tests/integration/explain/default/with_average_join_test.go +++ b/tests/integration/explain/default/with_average_join_test.go @@ -347,3 +347,130 @@ func TestDefaultExplainRequestWithAverageOnMultipleJoinedFieldsWithFilter(t *tes explainUtils.ExecuteTestCase(t, test) } + +// This test asserts that only a single index join is used (not parallelNode) because the +// _avg reuses the rendered join as they have matching filters (average adds a ne nil filter). +func TestDefaultExplainRequestOneToManyWithAverageAndChildNeNilFilterSharesJoinField(t *testing.T) { + test := testUtils.TestCase{ + + Description: "Explain (default) 1-to-M relation request from many side with average filter shared.", + + Actions: []any{ + explainUtils.SchemaForExplainTests, + + testUtils.ExplainRequest{ + + Request: `query @explain { + Author { + name + _avg(books: {field: rating}) + books(filter: {rating: {_ne: null}}){ + name + } + } + }`, + + ExpectedPatterns: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "averageNode": dataMap{ + "countNode": dataMap{ + "sumNode": dataMap{ + "selectNode": dataMap{ + "typeIndexJoin": normalTypeJoinPattern, + }, + }, + }, + }, + }, + }, + }, + }, + + ExpectedFullGraph: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "averageNode": dataMap{ + "countNode": dataMap{ + "sources": []dataMap{ + { + "filter": dataMap{ + "rating": dataMap{ + "_ne": nil, + }, + }, + "fieldName": "books", + }, + }, + "sumNode": dataMap{ + "sources": []dataMap{ + { + "filter": dataMap{ + "rating": dataMap{ + "_ne": nil, + }, + }, + "fieldName": "books", + "childFieldName": "rating", + }, + }, + "selectNode": dataMap{ + "_keys": nil, + "filter": nil, + "typeIndexJoin": dataMap{ + "joinType": "typeJoinMany", + "rootName": "author", + "root": dataMap{ + "scanNode": dataMap{ + "filter": nil, + "collectionID": "3", + "collectionName": "Author", + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + "subTypeName": "books", + "subType": dataMap{ + "selectTopNode": dataMap{ + "selectNode": dataMap{ + "_keys": nil, + "filter": nil, + "scanNode": dataMap{ + "filter": dataMap{ + "rating": dataMap{ + "_ne": nil, + }, + }, + "collectionID": "2", + "collectionName": "Book", + "spans": []dataMap{ + { + "start": "/2", + "end": "/3", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + explainUtils.ExecuteTestCase(t, test) +} diff --git a/tests/integration/explain/default/with_count_join_test.go b/tests/integration/explain/default/with_count_join_test.go index 6c116529a7..30ec8011fb 100644 --- a/tests/integration/explain/default/with_count_join_test.go +++ b/tests/integration/explain/default/with_count_join_test.go @@ -260,3 +260,265 @@ func TestDefaultExplainRequestWithCountOnOneToManyJoinedFieldWithManySources(t * explainUtils.ExecuteTestCase(t, test) } + +// This test asserts that only a single index join is used (not parallelNode) because the +// _count reuses the rendered join as they have matching filters. +func TestDefaultExplainRequestOneToManyWithCountWithFilterAndChildFilterSharesJoinField(t *testing.T) { + test := testUtils.TestCase{ + + Description: "Explain (default) 1-to-M relation request from many side with count filter shared.", + + Actions: []any{ + explainUtils.SchemaForExplainTests, + + testUtils.ExplainRequest{ + + Request: `query @explain { + Author { + name + _count(books: {filter: {rating: {_ne: null}}}) + books(filter: {rating: {_ne: null}}){ + name + } + } + }`, + + ExpectedPatterns: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "countNode": dataMap{ + "selectNode": dataMap{ + "typeIndexJoin": normalTypeJoinPattern, + }, + }, + }, + }, + }, + }, + + ExpectedFullGraph: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "countNode": dataMap{ + "sources": []dataMap{ + { + "filter": dataMap{ + "rating": dataMap{ + "_ne": nil, + }, + }, + "fieldName": "books", + }, + }, + "selectNode": dataMap{ + "_keys": nil, + "filter": nil, + "typeIndexJoin": dataMap{ + "joinType": "typeJoinMany", + "rootName": "author", + "root": dataMap{ + "scanNode": dataMap{ + "filter": nil, + "collectionID": "3", + "collectionName": "Author", + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + "subTypeName": "books", + "subType": dataMap{ + "selectTopNode": dataMap{ + "selectNode": dataMap{ + "_keys": nil, + "filter": nil, + "scanNode": dataMap{ + "filter": dataMap{ + "rating": dataMap{ + "_ne": nil, + }, + }, + "collectionID": "2", + "collectionName": "Book", + "spans": []dataMap{ + { + "start": "/2", + "end": "/3", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + explainUtils.ExecuteTestCase(t, test) +} + +// This test asserts that two joins are used (with parallelNode) because _count cannot +// reuse the rendered join as they dont have matching filters. +func TestDefaultExplainRequestOneToManyWithCountAndChildFilterDoesNotShareJoinField(t *testing.T) { + test := testUtils.TestCase{ + + Description: "Explain (default) 1-to-M relation request from many side with count filter not shared.", + + Actions: []any{ + explainUtils.SchemaForExplainTests, + + testUtils.ExplainRequest{ + + Request: `query @explain { + Author { + name + _count(books: {}) + books(filter: {rating: {_ne: null}}){ + name + } + } + }`, + + ExpectedPatterns: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "countNode": dataMap{ + "selectNode": dataMap{ + "parallelNode": []dataMap{ + { + "typeIndexJoin": normalTypeJoinPattern, + }, + { + "typeIndexJoin": normalTypeJoinPattern, + }, + }, + }, + }, + }, + }, + }, + }, + + ExpectedFullGraph: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "countNode": dataMap{ + "sources": []dataMap{ + { + "fieldName": "books", + "filter": nil, + }, + }, + "selectNode": dataMap{ + "_keys": nil, + "filter": nil, + "parallelNode": []dataMap{ + { + "typeIndexJoin": dataMap{ + "joinType": "typeJoinMany", + "rootName": "author", + "root": dataMap{ + "scanNode": dataMap{ + "collectionID": "3", + "collectionName": "Author", + "filter": nil, + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + "subTypeName": "books", + "subType": dataMap{ + "selectTopNode": dataMap{ + "selectNode": dataMap{ + "_keys": nil, + "filter": nil, + "scanNode": dataMap{ + "collectionID": "2", + "collectionName": "Book", + "filter": dataMap{ + "rating": dataMap{ + "_ne": nil, + }, + }, + "spans": []dataMap{ + { + "start": "/2", + "end": "/3", + }, + }, + }, + }, + }, + }, + }, + }, + { + "typeIndexJoin": dataMap{ + "joinType": "typeJoinMany", + "rootName": "author", + "root": dataMap{ + "scanNode": dataMap{ + "collectionID": "3", + "collectionName": "Author", + "filter": nil, + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + "subTypeName": "books", + "subType": dataMap{ + "selectTopNode": dataMap{ + "selectNode": dataMap{ + "_keys": nil, + "filter": nil, + "scanNode": dataMap{ + "collectionID": "2", + "collectionName": "Book", + "filter": nil, + "spans": []dataMap{ + { + "start": "/2", + "end": "/3", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + explainUtils.ExecuteTestCase(t, test) +} diff --git a/tests/integration/explain/fixture.go b/tests/integration/explain/fixture.go index 31b819e650..c531d95a84 100644 --- a/tests/integration/explain/fixture.go +++ b/tests/integration/explain/fixture.go @@ -27,6 +27,7 @@ var SchemaForExplainTests = testUtils.SchemaUpdate{ type Book { name: String author: Author + rating: Float pages: Int chapterPages: [Int!] }