diff --git a/tests/integration/explain/default/type_join_many_test.go b/tests/integration/explain/default/type_join_many_test.go new file mode 100644 index 0000000000..6ffdbcba09 --- /dev/null +++ b/tests/integration/explain/default/type_join_many_test.go @@ -0,0 +1,186 @@ +// Copyright 2023 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 test_explain_default + +import ( + "testing" + + explainUtils "github.com/sourcenetwork/defradb/tests/integration/explain" +) + +func TestDefaultExplainRequestWithAOneToManyJoin(t *testing.T) { + test := explainUtils.ExplainRequestTestCase{ + + Description: "Explain (default) request with a 1-to-M join.", + + Request: `query @explain { + Author { + articles { + name + } + } + }`, + + Docs: map[int][]string{ + // articles + 0: { + `{ + "name": "After Guantánamo, Another Injustice", + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "To my dear readers", + "author_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04" + }`, + `{ + "name": "Twinklestar's Favourite Xmas Cookie", + "author_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04" + }`, + }, + // books + 1: { + `{ + "name": "Painted House", + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "A Time for Mercy", + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "Theif Lord", + "author_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04" + }`, + }, + // authors + 2: { + // _key: bae-41598f0c-19bc-5da6-813b-e80f14a10df3 + `{ + "name": "John Grisham", + "age": 65, + "verified": true, + "contact_id": "bae-1fe427b8-ab8d-56c3-9df2-826a6ce86fed" + }`, + // _key: bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04 + `{ + "name": "Cornelia Funke", + "age": 62, + "verified": false, + "contact_id": "bae-c0960a29-b704-5c37-9c2e-59e1249e4559" + }`, + }, + // contact + 3: { + // _key: bae-1fe427b8-ab8d-56c3-9df2-826a6ce86fed + // "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + `{ + "cell": "5197212301", + "email": "john_grisham@example.com", + "address_id": "bae-c8448e47-6cd1-571f-90bd-364acb80da7b" + }`, + + // _key: bae-c0960a29-b704-5c37-9c2e-59e1249e4559 + // "author_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04" + `{ + "cell": "5197212302", + "email": "cornelia_funke@example.com", + "address_id": "bae-c0960a29-b704-5c37-9c2e-59e1249e4559" + }`, + }, + + // address + 4: { + // _key: bae-c8448e47-6cd1-571f-90bd-364acb80da7b + // "contact_id": "bae-1fe427b8-ab8d-56c3-9df2-826a6ce86fed" + `{ + "city": "Waterloo", + "country": "Canada" + }`, + + // _key: bae-f01bf83f-1507-5fb5-a6a3-09ecffa3c692 + // "contact_id": "bae-c0960a29-b704-5c37-9c2e-59e1249e4559" + `{ + "city": "Brampton", + "country": "Canada" + }`, + }, + }, + + ExpectedPatterns: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "selectNode": dataMap{ + "typeIndexJoin": normalTypeJoinPattern, + }, + }, + }, + }, + }, + + ExpectedTargets: []explainUtils.PlanNodeTargetCase{ + { + TargetNodeName: "typeIndexJoin", + IncludeChildNodes: false, + ExpectedAttributes: dataMap{ + "joinType": "typeJoinMany", + "rootName": "author", + "subTypeName": "articles", + }, + }, + { + // Note: `root` is not a node but is a special case because for typeIndexJoin we + // restructure to show both `root` and `subType` at the same level. + TargetNodeName: "root", + IncludeChildNodes: true, // We care about checking children nodes. + ExpectedAttributes: dataMap{ + "scanNode": dataMap{ + "filter": nil, + "collectionID": "3", + "collectionName": "Author", + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + }, + { + // Note: `subType` is not a node but is a special case because for typeIndexJoin we + // restructure to show both `root` and `subType` at the same level. + TargetNodeName: "subType", + IncludeChildNodes: true, // We care about checking children nodes. + ExpectedAttributes: dataMap{ + "selectTopNode": dataMap{ + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "filter": nil, + "collectionID": "1", + "collectionName": "Article", + "spans": []dataMap{ + { + "start": "/1", + "end": "/2", + }, + }, + }, + }, + }, + }, + }, + }, + } + + runExplainTest(t, test) +} diff --git a/tests/integration/explain/default/type_join_one_test.go b/tests/integration/explain/default/type_join_one_test.go new file mode 100644 index 0000000000..da6a89b53d --- /dev/null +++ b/tests/integration/explain/default/type_join_one_test.go @@ -0,0 +1,404 @@ +// Copyright 2023 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 test_explain_default + +import ( + "testing" + + explainUtils "github.com/sourcenetwork/defradb/tests/integration/explain" +) + +func TestDefaultExplainRequestWithAOneToOneJoin(t *testing.T) { + test := explainUtils.ExplainRequestTestCase{ + + Description: "Explain (default) request with a 1-to-1 join.", + + Request: `query @explain { + Author { + OnlyEmail: contact { + email + } + } + }`, + + Docs: map[int][]string{ + // articles + 0: { + `{ + "name": "After Guantánamo, Another Injustice", + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "To my dear readers", + "author_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04" + }`, + `{ + "name": "Twinklestar's Favourite Xmas Cookie", + "author_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04" + }`, + }, + // books + 1: { + `{ + "name": "Painted House", + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "A Time for Mercy", + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "Theif Lord", + "author_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04" + }`, + }, + // authors + 2: { + // _key: bae-41598f0c-19bc-5da6-813b-e80f14a10df3 + `{ + "name": "John Grisham", + "age": 65, + "verified": true, + "contact_id": "bae-1fe427b8-ab8d-56c3-9df2-826a6ce86fed" + }`, + // _key: bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04 + `{ + "name": "Cornelia Funke", + "age": 62, + "verified": false, + "contact_id": "bae-c0960a29-b704-5c37-9c2e-59e1249e4559" + }`, + }, + // contact + 3: { + // _key: bae-1fe427b8-ab8d-56c3-9df2-826a6ce86fed + // "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + `{ + "cell": "5197212301", + "email": "john_grisham@example.com", + "address_id": "bae-c8448e47-6cd1-571f-90bd-364acb80da7b" + }`, + + // _key: bae-c0960a29-b704-5c37-9c2e-59e1249e4559 + // "author_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04" + `{ + "cell": "5197212302", + "email": "cornelia_funke@example.com", + "address_id": "bae-c0960a29-b704-5c37-9c2e-59e1249e4559" + }`, + }, + + // address + 4: { + // _key: bae-c8448e47-6cd1-571f-90bd-364acb80da7b + // "contact_id": "bae-1fe427b8-ab8d-56c3-9df2-826a6ce86fed" + `{ + "city": "Waterloo", + "country": "Canada" + }`, + + // _key: bae-f01bf83f-1507-5fb5-a6a3-09ecffa3c692 + // "contact_id": "bae-c0960a29-b704-5c37-9c2e-59e1249e4559" + `{ + "city": "Brampton", + "country": "Canada" + }`, + }, + }, + + ExpectedPatterns: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "selectNode": dataMap{ + "typeIndexJoin": normalTypeJoinPattern, + }, + }, + }, + }, + }, + + ExpectedTargets: []explainUtils.PlanNodeTargetCase{ + { + TargetNodeName: "typeIndexJoin", + IncludeChildNodes: false, + ExpectedAttributes: dataMap{ + "direction": "primary", + "joinType": "typeJoinOne", + "rootName": "author", + "subTypeName": "contact", + }, + }, + { + // Note: `root` is not a node but is a special case because for typeIndexJoin we + // restructure to show both `root` and `subType` at the same level. + TargetNodeName: "root", + IncludeChildNodes: true, // We care about checking children nodes. + ExpectedAttributes: dataMap{ + "scanNode": dataMap{ + "filter": nil, + "collectionID": "3", + "collectionName": "Author", + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + }, + { + // Note: `subType` is not a node but is a special case because for typeIndexJoin we + // restructure to show both `root` and `subType` at the same level. + TargetNodeName: "subType", + IncludeChildNodes: true, // We care about checking children nodes. + ExpectedAttributes: dataMap{ + "selectTopNode": dataMap{ + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "filter": nil, + "collectionID": "4", + "collectionName": "AuthorContact", + "spans": []dataMap{ + { + "start": "/4", + "end": "/5", + }, + }, + }, + }, + }, + }, + }, + }, + } + + runExplainTest(t, test) +} + +func TestDefaultExplainRequestWithTwoLevelDeepNestedJoins(t *testing.T) { + test := explainUtils.ExplainRequestTestCase{ + + Description: "Explain (default) request with two level deep nested joins.", + + Request: `query @explain { + Author { + name + contact { + email + address { + city + } + } + } + }`, + + Docs: map[int][]string{ + // articles + 0: { + `{ + "name": "After Guantánamo, Another Injustice", + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "To my dear readers", + "author_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04" + }`, + `{ + "name": "Twinklestar's Favourite Xmas Cookie", + "author_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04" + }`, + }, + // books + 1: { + `{ + "name": "Painted House", + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "A Time for Mercy", + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "Theif Lord", + "author_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04" + }`, + }, + // authors + 2: { + // _key: bae-41598f0c-19bc-5da6-813b-e80f14a10df3 + `{ + "name": "John Grisham", + "age": 65, + "verified": true, + "contact_id": "bae-1fe427b8-ab8d-56c3-9df2-826a6ce86fed" + }`, + // _key: bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04 + `{ + "name": "Cornelia Funke", + "age": 62, + "verified": false, + "contact_id": "bae-c0960a29-b704-5c37-9c2e-59e1249e4559" + }`, + }, + // contact + 3: { + // _key: bae-1fe427b8-ab8d-56c3-9df2-826a6ce86fed + // "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + `{ + "cell": "5197212301", + "email": "john_grisham@example.com", + "address_id": "bae-c8448e47-6cd1-571f-90bd-364acb80da7b" + }`, + + // _key: bae-c0960a29-b704-5c37-9c2e-59e1249e4559 + // "author_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04" + `{ + "cell": "5197212302", + "email": "cornelia_funke@example.com", + "address_id": "bae-c0960a29-b704-5c37-9c2e-59e1249e4559" + }`, + }, + + // address + 4: { + // _key: bae-c8448e47-6cd1-571f-90bd-364acb80da7b + // "contact_id": "bae-1fe427b8-ab8d-56c3-9df2-826a6ce86fed" + `{ + "city": "Waterloo", + "country": "Canada" + }`, + + // _key: bae-f01bf83f-1507-5fb5-a6a3-09ecffa3c692 + // "contact_id": "bae-c0960a29-b704-5c37-9c2e-59e1249e4559" + `{ + "city": "Brampton", + "country": "Canada" + }`, + }, + }, + + ExpectedPatterns: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "selectNode": dataMap{ + "typeIndexJoin": dataMap{ + "root": dataMap{ + "scanNode": dataMap{}, + }, + "subType": dataMap{ + "selectTopNode": dataMap{ + "selectNode": dataMap{ + "typeIndexJoin": normalTypeJoinPattern, + }, + }, + }, + }, + }, + }, + }, + }, + }, + + ExpectedTargets: []explainUtils.PlanNodeTargetCase{ + { + TargetNodeName: "typeIndexJoin", + OccurancesToSkip: 0, + IncludeChildNodes: false, + ExpectedAttributes: dataMap{ + "direction": "primary", + "joinType": "typeJoinOne", + "rootName": "author", + "subTypeName": "contact", + }, + }, + { + TargetNodeName: "root", + OccurancesToSkip: 0, + IncludeChildNodes: true, // We care about checking children nodes. + ExpectedAttributes: dataMap{ + "scanNode": dataMap{ + "filter": nil, + "collectionID": "3", + "collectionName": "Author", + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + }, + + // Note: the 1st `subType` will contain the entire rest of the graph so we target + // and select only the nodes we care about inside it and not `subType` itself. + + { + TargetNodeName: "typeIndexJoin", + OccurancesToSkip: 1, + IncludeChildNodes: false, + ExpectedAttributes: dataMap{ + "direction": "primary", + "joinType": "typeJoinOne", + "rootName": "contact", + "subTypeName": "address", + }, + }, + { + TargetNodeName: "root", + OccurancesToSkip: 1, + IncludeChildNodes: true, + ExpectedAttributes: dataMap{ + "scanNode": dataMap{ + "filter": nil, + "collectionID": "4", + "collectionName": "AuthorContact", + "spans": []dataMap{ + { + "start": "/4", + "end": "/5", + }, + }, + }, + }, + }, + { + TargetNodeName: "subType", // The last subType (assert everything under it). + OccurancesToSkip: 1, + IncludeChildNodes: true, + ExpectedAttributes: dataMap{ + "selectTopNode": dataMap{ + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "filter": nil, + "collectionID": "5", + "collectionName": "ContactAddress", + "spans": []dataMap{ + { + "start": "/5", + "end": "/6", + }, + }, + }, + }, + }, + }, + }, + }, + } + + runExplainTest(t, test) +} diff --git a/tests/integration/explain/default/type_join_test.go b/tests/integration/explain/default/type_join_test.go index 7ddaaa48ed..b538047fb2 100644 --- a/tests/integration/explain/default/type_join_test.go +++ b/tests/integration/explain/default/type_join_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 Democratized Data Foundation +// Copyright 2023 Democratized Data Foundation // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt. @@ -13,170 +13,35 @@ package test_explain_default import ( "testing" - testUtils "github.com/sourcenetwork/defradb/tests/integration" + explainUtils "github.com/sourcenetwork/defradb/tests/integration/explain" ) -func TestExplainQueryWithAOneToOneJoin(t *testing.T) { - test := testUtils.RequestTestCase{ - - Description: "Explain a one-to-one join relation query, with alias.", - - Request: `query @explain { - Author { - OnlyEmail: contact { - email - } - } - }`, - - Docs: map[int][]string{ - // articles - 0: { - `{ - "name": "After Guantánamo, Another Injustice", - "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" - }`, - `{ - "name": "To my dear readers", - "author_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04" - }`, - `{ - "name": "Twinklestar's Favourite Xmas Cookie", - "author_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04" - }`, - }, - // books - 1: { - `{ - "name": "Painted House", - "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" - }`, - `{ - "name": "A Time for Mercy", - "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" - }`, - `{ - "name": "Theif Lord", - "author_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04" - }`, - }, - // authors - 2: { - // _key: bae-41598f0c-19bc-5da6-813b-e80f14a10df3 - `{ - "name": "John Grisham", - "age": 65, - "verified": true, - "contact_id": "bae-1fe427b8-ab8d-56c3-9df2-826a6ce86fed" - }`, - // _key: bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04 - `{ - "name": "Cornelia Funke", - "age": 62, - "verified": false, - "contact_id": "bae-c0960a29-b704-5c37-9c2e-59e1249e4559" - }`, - }, - // contact - 3: { - // _key: bae-1fe427b8-ab8d-56c3-9df2-826a6ce86fed - // "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" - `{ - "cell": "5197212301", - "email": "john_grisham@example.com", - "address_id": "bae-c8448e47-6cd1-571f-90bd-364acb80da7b" - }`, - - // _key: bae-c0960a29-b704-5c37-9c2e-59e1249e4559 - // "author_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04" - `{ - "cell": "5197212302", - "email": "cornelia_funke@example.com", - "address_id": "bae-c0960a29-b704-5c37-9c2e-59e1249e4559" - }`, - }, - - // address - 4: { - // _key: bae-c8448e47-6cd1-571f-90bd-364acb80da7b - // "contact_id": "bae-1fe427b8-ab8d-56c3-9df2-826a6ce86fed" - `{ - "city": "Waterloo", - "country": "Canada" - }`, - - // _key: bae-f01bf83f-1507-5fb5-a6a3-09ecffa3c692 - // "contact_id": "bae-c0960a29-b704-5c37-9c2e-59e1249e4559" - `{ - "city": "Brampton", - "country": "Canada" - }`, - }, - }, - - Results: []dataMap{ - { - "explain": dataMap{ - "selectTopNode": dataMap{ - "selectNode": dataMap{ - "filter": nil, - "typeIndexJoin": dataMap{ - "direction": "primary", - "joinType": "typeJoinOne", - "rootName": "author", - "root": dataMap{ - "scanNode": dataMap{ - "filter": nil, - "collectionID": "3", - "collectionName": "Author", - "spans": []dataMap{ - { - "start": "/3", - "end": "/4", - }, - }, - }, - }, - "subTypeName": "contact", - "subType": dataMap{ - "selectTopNode": dataMap{ - "selectNode": dataMap{ - "filter": nil, - "scanNode": dataMap{ - "filter": nil, - "collectionID": "4", - "collectionName": "AuthorContact", - "spans": []dataMap{ - { - "start": "/4", - "end": "/5", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, +var normalTypeJoinPattern = dataMap{ + "root": dataMap{ + "scanNode": dataMap{}, + }, + "subType": dataMap{ + "selectTopNode": dataMap{ + "selectNode": dataMap{ + "scanNode": dataMap{}, }, }, - } - - executeTestCase(t, test) + }, } -func TestExplainQueryWithMultipleOneToOneJoins(t *testing.T) { - test := testUtils.RequestTestCase{ +func TestDefaultExplainRequestWith2SingleJoinsAnd1ManyJoin(t *testing.T) { + test := explainUtils.ExplainRequestTestCase{ - Description: "Explain two one-to-one join relation query.", + Description: "Explain (default) request with 2 single joins and 1 many join.", Request: `query @explain { Author { OnlyEmail: contact { email } + articles { + name + } contact { cell email @@ -269,90 +134,20 @@ func TestExplainQueryWithMultipleOneToOneJoins(t *testing.T) { }, }, - Results: []dataMap{ + ExpectedPatterns: []dataMap{ { "explain": dataMap{ "selectTopNode": dataMap{ "selectNode": dataMap{ - "filter": nil, "parallelNode": []dataMap{ { - "typeIndexJoin": dataMap{ - "joinType": "typeJoinOne", - "direction": "primary", - "rootName": "author", - "root": dataMap{ - "scanNode": dataMap{ - "filter": nil, - "collectionID": "3", - "collectionName": "Author", - "spans": []dataMap{ - { - "start": "/3", - "end": "/4", - }, - }, - }, - }, - "subTypeName": "contact", - "subType": dataMap{ - "selectTopNode": dataMap{ - "selectNode": dataMap{ - "filter": nil, - "scanNode": dataMap{ - "filter": nil, - "collectionID": "4", - "collectionName": "AuthorContact", - "spans": []dataMap{ - { - "start": "/4", - "end": "/5", - }, - }, - }, - }, - }, - }, - }, + "typeIndexJoin": normalTypeJoinPattern, }, { - "typeIndexJoin": dataMap{ - "joinType": "typeJoinOne", - "direction": "primary", - "rootName": "author", - "root": dataMap{ - "scanNode": dataMap{ - "filter": nil, - "collectionID": "3", - "collectionName": "Author", - "spans": []dataMap{ - { - "start": "/3", - "end": "/4", - }, - }, - }, - }, - "subTypeName": "contact", - "subType": dataMap{ - "selectTopNode": dataMap{ - "selectNode": dataMap{ - "filter": nil, - "scanNode": dataMap{ - "filter": nil, - "collectionID": "4", - "collectionName": "AuthorContact", - "spans": []dataMap{ - { - "start": "/4", - "end": "/5", - }, - }, - }, - }, - }, - }, - }, + "typeIndexJoin": normalTypeJoinPattern, + }, + { + "typeIndexJoin": normalTypeJoinPattern, }, }, }, @@ -360,180 +155,115 @@ func TestExplainQueryWithMultipleOneToOneJoins(t *testing.T) { }, }, }, - } - executeTestCase(t, test) -} - -func TestExplainQueryWithTwoLeveLDeepNestedJoins(t *testing.T) { - test := testUtils.RequestTestCase{ - - Description: "Explain query with two nested level deep one to one join.", - - Request: `query @explain { - Author { - _key - name - contact { - email - address { - city - } - } - } - }`, - - Docs: map[int][]string{ - // articles - 0: { - `{ - "name": "After Guantánamo, Another Injustice", - "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" - }`, - `{ - "name": "To my dear readers", - "author_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04" - }`, - `{ - "name": "Twinklestar's Favourite Xmas Cookie", - "author_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04" - }`, + ExpectedTargets: []explainUtils.PlanNodeTargetCase{ + // 1st join's assertions. + { + TargetNodeName: "typeIndexJoin", + OccurancesToSkip: 0, + IncludeChildNodes: false, + ExpectedAttributes: dataMap{ + "direction": "primary", + "joinType": "typeJoinOne", + "rootName": "author", + "subTypeName": "contact", + }, }, - // books - 1: { - `{ - "name": "Painted House", - "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" - }`, - `{ - "name": "A Time for Mercy", - "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" - }`, - `{ - "name": "Theif Lord", - "author_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04" - }`, + { + // Note: `root` is not a node but is a special case because for typeIndexJoin we + // restructure to show both `root` and `subType` at the same level. + TargetNodeName: "root", + OccurancesToSkip: 0, + IncludeChildNodes: true, // We care about checking children nodes. + ExpectedAttributes: dataMap{ + "scanNode": dataMap{ + "filter": nil, + "collectionID": "3", + "collectionName": "Author", + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, }, - // authors - 2: { - // _key: bae-41598f0c-19bc-5da6-813b-e80f14a10df3 - `{ - "name": "John Grisham", - "age": 65, - "verified": true, - "contact_id": "bae-1fe427b8-ab8d-56c3-9df2-826a6ce86fed" - }`, - // _key: bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04 - `{ - "name": "Cornelia Funke", - "age": 62, - "verified": false, - "contact_id": "bae-c0960a29-b704-5c37-9c2e-59e1249e4559" - }`, + { + // Note: `subType` is not a node but is a special case because for typeIndexJoin we + // restructure to show both `root` and `subType` at the same level. + TargetNodeName: "subType", + OccurancesToSkip: 0, + IncludeChildNodes: true, // We care about checking children nodes. + ExpectedAttributes: dataMap{ + "selectTopNode": dataMap{ + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "filter": nil, + "collectionID": "4", + "collectionName": "AuthorContact", + "spans": []dataMap{ + { + "start": "/4", + "end": "/5", + }, + }, + }, + }, + }, + }, }, - // contact - 3: { - // _key: bae-1fe427b8-ab8d-56c3-9df2-826a6ce86fed - // "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" - `{ - "cell": "5197212301", - "email": "john_grisham@example.com", - "address_id": "bae-c8448e47-6cd1-571f-90bd-364acb80da7b" - }`, - // _key: bae-c0960a29-b704-5c37-9c2e-59e1249e4559 - // "author_id": "bae-b769708d-f552-5c3d-a402-ccfd7ac7fb04" - `{ - "cell": "5197212302", - "email": "cornelia_funke@example.com", - "address_id": "bae-c0960a29-b704-5c37-9c2e-59e1249e4559" - }`, + // 2nd join's assertions (the one to many join). + { + TargetNodeName: "typeIndexJoin", + OccurancesToSkip: 1, + IncludeChildNodes: false, + ExpectedAttributes: dataMap{ + "joinType": "typeJoinMany", + "rootName": "author", + "subTypeName": "articles", + }, }, - - // address - 4: { - // _key: bae-c8448e47-6cd1-571f-90bd-364acb80da7b - // "contact_id": "bae-1fe427b8-ab8d-56c3-9df2-826a6ce86fed" - `{ - "city": "Waterloo", - "country": "Canada" - }`, - - // _key: bae-f01bf83f-1507-5fb5-a6a3-09ecffa3c692 - // "contact_id": "bae-c0960a29-b704-5c37-9c2e-59e1249e4559" - `{ - "city": "Brampton", - "country": "Canada" - }`, + { + // Note: `root` is not a node but is a special case because for typeIndexJoin we + // restructure to show both `root` and `subType` at the same level. + TargetNodeName: "root", + OccurancesToSkip: 1, + IncludeChildNodes: true, // We care about checking children nodes. + ExpectedAttributes: dataMap{ + "scanNode": dataMap{ + "filter": nil, + "collectionID": "3", + "collectionName": "Author", + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, }, - }, - - Results: []dataMap{ { - "explain": dataMap{ + // Note: `subType` is not a node but is a special case because for typeIndexJoin we + // restructure to show both `root` and `subType` at the same level. + TargetNodeName: "subType", + OccurancesToSkip: 1, + IncludeChildNodes: true, // We care about checking children nodes. + ExpectedAttributes: dataMap{ "selectTopNode": dataMap{ "selectNode": dataMap{ "filter": nil, - "typeIndexJoin": dataMap{ - "joinType": "typeJoinOne", - "direction": "primary", - "rootName": "author", - "root": dataMap{ - "scanNode": dataMap{ - "filter": nil, - "collectionID": "3", - "collectionName": "Author", - "spans": []dataMap{ - { - "start": "/3", - "end": "/4", - }, - }, - }, - }, - "subTypeName": "contact", - "subType": dataMap{ - "selectTopNode": dataMap{ - "selectNode": dataMap{ - "filter": nil, - "typeIndexJoin": dataMap{ - "joinType": "typeJoinOne", - "direction": "primary", - "rootName": "contact", - "root": dataMap{ - "scanNode": dataMap{ - "filter": nil, - "collectionID": "4", - "collectionName": "AuthorContact", - "spans": []dataMap{ - { - "start": "/4", - "end": "/5", - }, - }, - }, - }, - "subTypeName": "address", - "subType": dataMap{ - "selectTopNode": dataMap{ - "selectNode": dataMap{ - "filter": nil, - "scanNode": dataMap{ - "filter": nil, - "collectionID": "5", - "collectionName": "ContactAddress", - "spans": []dataMap{ - { - "start": "/5", - "end": "/6", - }, - }, - }, - }, - }, - }, - }, - }, + "scanNode": dataMap{ + "filter": nil, + "collectionID": "1", + "collectionName": "Article", + "spans": []dataMap{ + { + "start": "/1", + "end": "/2", }, }, }, @@ -541,8 +271,58 @@ func TestExplainQueryWithTwoLeveLDeepNestedJoins(t *testing.T) { }, }, }, + + // 3rd join's assertions (should be same as 1st one, so after `typeIndexJoin` lets just + // assert that the `scanNode`s are valid only. + { + TargetNodeName: "typeIndexJoin", + OccurancesToSkip: 2, + IncludeChildNodes: false, + ExpectedAttributes: dataMap{ + "direction": "primary", + "joinType": "typeJoinOne", + "rootName": "author", + "subTypeName": "contact", + }, + }, + { + // Note: `root` is not a node but is a special case because for typeIndexJoin we + // restructure to show both `root` and `subType` at the same level. + TargetNodeName: "scanNode", + OccurancesToSkip: 4, // As we encountered 2 `scanNode`s per join. + IncludeChildNodes: true, // Shouldn't have any. + ExpectedAttributes: dataMap{ + "filter": nil, + "collectionID": "3", + "collectionName": "Author", + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + { + // Note: `subType` is not a node but is a special case because for typeIndexJoin we + // restructure to show both `root` and `subType` at the same level. + TargetNodeName: "scanNode", + OccurancesToSkip: 5, // As we encountered 2 `scanNode`s per join + 1 in the `root` above. + IncludeChildNodes: true, // Shouldn't have any. + ExpectedAttributes: dataMap{ + "filter": nil, + "collectionID": "4", + "collectionName": "AuthorContact", + "spans": []dataMap{ + { + "start": "/4", + "end": "/5", + }, + }, + }, + }, }, } - executeTestCase(t, test) + runExplainTest(t, test) } diff --git a/tests/integration/explain/default/with_limit_join_test.go b/tests/integration/explain/default/with_limit_join_test.go index cb722bfe39..55d4681cfb 100644 --- a/tests/integration/explain/default/with_limit_join_test.go +++ b/tests/integration/explain/default/with_limit_join_test.go @@ -16,19 +16,6 @@ import ( explainUtils "github.com/sourcenetwork/defradb/tests/integration/explain" ) -var normalTypeJoinPattern = dataMap{ - "root": dataMap{ - "scanNode": dataMap{}, - }, - "subType": dataMap{ - "selectTopNode": dataMap{ - "selectNode": dataMap{ - "scanNode": dataMap{}, - }, - }, - }, -} - var limitTypeJoinPattern = dataMap{ "root": dataMap{ "scanNode": dataMap{},