diff --git a/planner/explain.go b/planner/explain.go index f4494fcf72..0f669fd55e 100644 --- a/planner/explain.go +++ b/planner/explain.go @@ -56,6 +56,7 @@ const ( dataLabel = "data" fieldNameLabel = "fieldName" filterLabel = "filter" + keysLabel = "_keys" idsLabel = "ids" limitLabel = "limit" offsetLabel = "offset" diff --git a/planner/scan.go b/planner/scan.go index 5ca16bb0ce..5fe4b3c047 100644 --- a/planner/scan.go +++ b/planner/scan.go @@ -39,7 +39,6 @@ type scanNode struct { desc client.CollectionDescription fields []*client.FieldDescription - docKey []byte showDeleted bool @@ -106,7 +105,7 @@ func (n *scanNode) Next() (bool, error) { // keep scanning until we find a doc that passes the filter for { var err error - n.docKey, n.currentValue, err = n.fetcher.FetchNextDoc(n.p.ctx, n.documentMapping) + _, n.currentValue, err = n.fetcher.FetchNextDoc(n.p.ctx, n.documentMapping) if err != nil { return false, err } diff --git a/planner/select.go b/planner/select.go index 009131ec4b..4fb9b143f2 100644 --- a/planner/select.go +++ b/planner/select.go @@ -115,7 +115,7 @@ type selectNode struct { // are defined in the subtype scan node. filter *mapper.Filter - docKeys immutable.Option[[]string] + keys immutable.Option[[]string] selectReq *mapper.Select groupSelects []*mapper.Select @@ -167,9 +167,9 @@ func (n *selectNode) Next() (bool, error) { n.execInfo.filterMatches++ - if n.docKeys.HasValue() { + if n.keys.HasValue() { docKey := n.currentValue.GetKey() - for _, key := range n.docKeys.Value() { + for _, key := range n.keys.Value() { if docKey == key { return true, nil } @@ -200,6 +200,13 @@ func (n *selectNode) simpleExplain() (map[string]any, error) { simpleExplainMap[filterLabel] = n.filter.ToMap(n.documentMapping) } + // Add the keys attribute if it exists. + if !n.keys.HasValue() { + simpleExplainMap[keysLabel] = nil + } else { + simpleExplainMap[keysLabel] = n.keys.Value() + } + return simpleExplainMap, nil } @@ -406,7 +413,7 @@ func (p *Planner) SelectFromSource( selectReq: selectReq, docMapper: docMapper{selectReq.DocumentMapping}, filter: selectReq.Filter, - docKeys: selectReq.DocKeys, + keys: selectReq.DocKeys, } limit := selectReq.Limit orderBy := selectReq.OrderBy @@ -461,7 +468,7 @@ func (p *Planner) Select(selectReq *mapper.Select) (planNode, error) { s := &selectNode{ planner: p, filter: selectReq.Filter, - docKeys: selectReq.DocKeys, + keys: selectReq.DocKeys, selectReq: selectReq, docMapper: docMapper{selectReq.DocumentMapping}, } diff --git a/tests/integration/explain/default/basic_test.go b/tests/integration/explain/default/basic_test.go index 083437af27..2d7f515d9e 100644 --- a/tests/integration/explain/default/basic_test.go +++ b/tests/integration/explain/default/basic_test.go @@ -64,6 +64,7 @@ func TestDefaultExplainRequestWithFullBasicGraph(t *testing.T) { "explain": dataMap{ "selectTopNode": dataMap{ "selectNode": dataMap{ + "_keys": nil, "filter": nil, "scanNode": dataMap{ "filter": nil, diff --git a/tests/integration/explain/default/type_join_many_test.go b/tests/integration/explain/default/type_join_many_test.go index 581430a0ff..9fa66bf76a 100644 --- a/tests/integration/explain/default/type_join_many_test.go +++ b/tests/integration/explain/default/type_join_many_test.go @@ -84,6 +84,7 @@ func TestDefaultExplainRequestWithAOneToManyJoin(t *testing.T) { ExpectedAttributes: dataMap{ "selectTopNode": dataMap{ "selectNode": dataMap{ + "_keys": nil, "filter": nil, "scanNode": dataMap{ "filter": nil, diff --git a/tests/integration/explain/default/type_join_one_test.go b/tests/integration/explain/default/type_join_one_test.go index 34dc60ab0e..472a6f2164 100644 --- a/tests/integration/explain/default/type_join_one_test.go +++ b/tests/integration/explain/default/type_join_one_test.go @@ -85,6 +85,7 @@ func TestDefaultExplainRequestWithAOneToOneJoin(t *testing.T) { ExpectedAttributes: dataMap{ "selectTopNode": dataMap{ "selectNode": dataMap{ + "_keys": nil, "filter": nil, "scanNode": dataMap{ "filter": nil, @@ -224,6 +225,7 @@ func TestDefaultExplainRequestWithTwoLevelDeepNestedJoins(t *testing.T) { ExpectedAttributes: dataMap{ "selectTopNode": dataMap{ "selectNode": dataMap{ + "_keys": nil, "filter": nil, "scanNode": dataMap{ "filter": nil, diff --git a/tests/integration/explain/default/type_join_test.go b/tests/integration/explain/default/type_join_test.go index ceec61c7e2..c3ca250565 100644 --- a/tests/integration/explain/default/type_join_test.go +++ b/tests/integration/explain/default/type_join_test.go @@ -119,6 +119,7 @@ func TestDefaultExplainRequestWith2SingleJoinsAnd1ManyJoin(t *testing.T) { ExpectedAttributes: dataMap{ "selectTopNode": dataMap{ "selectNode": dataMap{ + "_keys": nil, "filter": nil, "scanNode": dataMap{ "filter": nil, @@ -176,6 +177,7 @@ func TestDefaultExplainRequestWith2SingleJoinsAnd1ManyJoin(t *testing.T) { ExpectedAttributes: dataMap{ "selectTopNode": dataMap{ "selectNode": dataMap{ + "_keys": nil, "filter": nil, "scanNode": dataMap{ "filter": nil, diff --git a/tests/integration/explain/default/type_join_with_filter_and_key_test.go b/tests/integration/explain/default/type_join_with_filter_and_key_test.go new file mode 100644 index 0000000000..2290de03e6 --- /dev/null +++ b/tests/integration/explain/default/type_join_with_filter_and_key_test.go @@ -0,0 +1,195 @@ +// 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" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" + explainUtils "github.com/sourcenetwork/defradb/tests/integration/explain" +) + +func TestDefaultExplainRequestWithRelatedAndRegularFilterAndKeys(t *testing.T) { + test := testUtils.TestCase{ + + Description: "Explain (default) request with related and regular filter + keys.", + + Actions: []any{ + explainUtils.SchemaForExplainTests, + + testUtils.ExplainRequest{ + + Request: `query @explain { + Author( + filter: { + name: {_eq: "John Grisham"}, + books: {name: {_eq: "Painted House"}} + }, + dockeys: [ + "bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f9d", + "bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f8e" + ] + ) { + name + age + } + }`, + + ExpectedPatterns: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "selectNode": dataMap{ + "typeIndexJoin": normalTypeJoinPattern, + }, + }, + }, + }, + }, + + ExpectedTargets: []testUtils.PlanNodeTargetCase{ + { + TargetNodeName: "selectNode", + ExpectedAttributes: dataMap{ + "_keys": []string{ + "bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f9d", + "bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f8e", + }, + "filter": dataMap{ + "books": dataMap{ + "name": dataMap{ + "_eq": "Painted House", + }, + }, + }, + }, + }, + { + TargetNodeName: "scanNode", + IncludeChildNodes: true, // should be last node, so will have no child nodes. + ExpectedAttributes: dataMap{ + "collectionID": "3", + "collectionName": "Author", + "filter": dataMap{ + "name": dataMap{ + "_eq": "John Grisham", + }, + }, + "spans": []dataMap{ + { + "start": "/3/bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f9d", + "end": "/3/bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f9e", + }, + { + "start": "/3/bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f8e", + "end": "/3/bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f8f", + }, + }, + }, + }, + }, + }, + }, + } + + explainUtils.ExecuteTestCase(t, test) +} + +func TestDefaultExplainRequestWithManyRelatedFiltersAndKey(t *testing.T) { + test := testUtils.TestCase{ + + Description: "Explain (default) request with many related filters + key.", + + Actions: []any{ + explainUtils.SchemaForExplainTests, + + testUtils.ExplainRequest{ + + Request: `query @explain { + Author( + filter: { + name: {_eq: "Cornelia Funke"}, + articles: {name: {_eq: "To my dear readers"}}, + books: {name: {_eq: "Theif Lord"}} + }, + dockeys: ["bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f9d"] + ) { + name + age + } + }`, + + ExpectedPatterns: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "selectNode": dataMap{ + "parallelNode": []dataMap{ + { + "typeIndexJoin": normalTypeJoinPattern, + }, + { + "typeIndexJoin": normalTypeJoinPattern, + }, + }, + }, + }, + }, + }, + }, + + ExpectedTargets: []testUtils.PlanNodeTargetCase{ + { + TargetNodeName: "selectNode", + ExpectedAttributes: dataMap{ + "_keys": []string{ + "bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f9d", + }, + "filter": dataMap{ + "articles": dataMap{ + "name": dataMap{ + "_eq": "To my dear readers", + }, + }, + "books": dataMap{ + "name": dataMap{ + "_eq": "Theif Lord", + }, + }, + }, + }, + }, + { + TargetNodeName: "scanNode", + IncludeChildNodes: true, // should be last node, so will have no child nodes. + ExpectedAttributes: dataMap{ + "collectionID": "3", + "collectionName": "Author", + "filter": dataMap{ + "name": dataMap{ + "_eq": "Cornelia Funke", + }, + }, + "spans": []dataMap{ + { + "start": "/3/bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f9d", + "end": "/3/bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f9e", + }, + }, + }, + }, + }, + }, + }, + } + + explainUtils.ExecuteTestCase(t, test) +} diff --git a/tests/integration/explain/default/type_join_with_filter_test.go b/tests/integration/explain/default/type_join_with_filter_test.go index b5ef4e8898..799ad2677d 100644 --- a/tests/integration/explain/default/type_join_with_filter_test.go +++ b/tests/integration/explain/default/type_join_with_filter_test.go @@ -55,6 +55,7 @@ func TestDefaultExplainRequestWithRelatedAndRegularFilter(t *testing.T) { { TargetNodeName: "selectNode", ExpectedAttributes: dataMap{ + "_keys": nil, "filter": dataMap{ "books": dataMap{ "name": dataMap{ @@ -137,6 +138,7 @@ func TestDefaultExplainRequestWithManyRelatedFilters(t *testing.T) { { TargetNodeName: "selectNode", ExpectedAttributes: dataMap{ + "_keys": nil, "filter": dataMap{ "articles": dataMap{ "name": dataMap{ diff --git a/tests/integration/explain/default/with_filter_key_test.go b/tests/integration/explain/default/with_filter_key_test.go index 8c603faa55..7f181a07f5 100644 --- a/tests/integration/explain/default/with_filter_key_test.go +++ b/tests/integration/explain/default/with_filter_key_test.go @@ -37,6 +37,15 @@ func TestDefaultExplainRequestWithDocKeyFilter(t *testing.T) { ExpectedPatterns: []dataMap{basicPattern}, ExpectedTargets: []testUtils.PlanNodeTargetCase{ + { + TargetNodeName: "selectNode", + ExpectedAttributes: dataMap{ + "_keys": []string{ + "bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f9d", + }, + "filter": nil, + }, + }, { TargetNodeName: "scanNode", IncludeChildNodes: true, // should be last node, so will have no child nodes. @@ -80,6 +89,15 @@ func TestDefaultExplainRequestWithDocKeysFilterUsingOneKey(t *testing.T) { ExpectedPatterns: []dataMap{basicPattern}, ExpectedTargets: []testUtils.PlanNodeTargetCase{ + { + TargetNodeName: "selectNode", + ExpectedAttributes: dataMap{ + "_keys": []string{ + "bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f9d", + }, + "filter": nil, + }, + }, { TargetNodeName: "scanNode", IncludeChildNodes: true, // should be last node, so will have no child nodes. @@ -128,6 +146,16 @@ func TestDefaultExplainRequestWithDocKeysFilterUsingMultipleButDuplicateKeys(t * ExpectedPatterns: []dataMap{basicPattern}, ExpectedTargets: []testUtils.PlanNodeTargetCase{ + { + TargetNodeName: "selectNode", + ExpectedAttributes: dataMap{ + "_keys": []string{ + "bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f9d", + "bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f9d", + }, + "filter": nil, + }, + }, { TargetNodeName: "scanNode", IncludeChildNodes: true, // should be last node, so will have no child nodes. @@ -180,6 +208,16 @@ func TestDefaultExplainRequestWithDocKeysFilterUsingMultipleUniqueKeys(t *testin ExpectedPatterns: []dataMap{basicPattern}, ExpectedTargets: []testUtils.PlanNodeTargetCase{ + { + TargetNodeName: "selectNode", + ExpectedAttributes: dataMap{ + "_keys": []string{ + "bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f9d", + "bae-bfbfc89c-0d63-5ea4-81a3-3ebd295be67f", + }, + "filter": nil, + }, + }, { TargetNodeName: "scanNode", IncludeChildNodes: true, // should be last node, so will have no child nodes. @@ -218,7 +256,13 @@ func TestDefaultExplainRequestWithMatchingKeyFilter(t *testing.T) { testUtils.ExplainRequest{ Request: `query @explain { - Author(filter: {_key: {_eq: "bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f9d"}}) { + Author( + filter: { + _key: { + _eq: "bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f9d" + } + } + ) { name age } @@ -227,6 +271,13 @@ func TestDefaultExplainRequestWithMatchingKeyFilter(t *testing.T) { ExpectedPatterns: []dataMap{basicPattern}, ExpectedTargets: []testUtils.PlanNodeTargetCase{ + { + TargetNodeName: "selectNode", + ExpectedAttributes: dataMap{ + "_keys": nil, + "filter": nil, + }, + }, { TargetNodeName: "scanNode", IncludeChildNodes: true, // should be last node, so will have no child nodes. diff --git a/tests/integration/explain/simple/basic_test.go b/tests/integration/explain/simple/basic_test.go index e518093b92..9920458952 100644 --- a/tests/integration/explain/simple/basic_test.go +++ b/tests/integration/explain/simple/basic_test.go @@ -40,6 +40,7 @@ func TestSimpleExplainRequest(t *testing.T) { "explain": dataMap{ "selectTopNode": dataMap{ "selectNode": dataMap{ + "_keys": nil, "filter": nil, "scanNode": dataMap{ "filter": nil, diff --git a/tests/integration/query/one_to_many/with_average_filter_test.go b/tests/integration/query/one_to_many/with_average_filter_test.go index a6c52c9e71..1404be5962 100644 --- a/tests/integration/query/one_to_many/with_average_filter_test.go +++ b/tests/integration/query/one_to_many/with_average_filter_test.go @@ -59,6 +59,7 @@ func TestQueryOneToManyWithAverageAndChildNeNilFilterSharesJoinField(t *testing. }, }, "selectNode": dataMap{ + "_keys": nil, "filter": nil, "typeIndexJoin": dataMap{ "joinType": "typeJoinMany", @@ -80,6 +81,7 @@ func TestQueryOneToManyWithAverageAndChildNeNilFilterSharesJoinField(t *testing. "subType": dataMap{ "selectTopNode": dataMap{ "selectNode": dataMap{ + "_keys": nil, "filter": nil, "scanNode": dataMap{ "filter": dataMap{ diff --git a/tests/integration/query/one_to_many/with_count_filter_test.go b/tests/integration/query/one_to_many/with_count_filter_test.go index 008b262981..9deecae01f 100644 --- a/tests/integration/query/one_to_many/with_count_filter_test.go +++ b/tests/integration/query/one_to_many/with_count_filter_test.go @@ -184,6 +184,7 @@ func TestQueryOneToManyWithCountWithFilterAndChildFilterSharesJoinField(t *testi }, }, "selectNode": dataMap{ + "_keys": nil, "filter": nil, "typeIndexJoin": dataMap{ "joinType": "typeJoinMany", @@ -205,6 +206,7 @@ func TestQueryOneToManyWithCountWithFilterAndChildFilterSharesJoinField(t *testi "subType": dataMap{ "selectTopNode": dataMap{ "selectNode": dataMap{ + "_keys": nil, "filter": nil, "scanNode": dataMap{ "filter": dataMap{ @@ -256,6 +258,7 @@ func TestQueryOneToManyWithCountAndChildFilterDoesNotShareJoinField(t *testing.T "selectTopNode": dataMap{ "countNode": dataMap{ "selectNode": dataMap{ + "_keys": nil, "filter": nil, "parallelNode": []dataMap{ { @@ -278,6 +281,7 @@ func TestQueryOneToManyWithCountAndChildFilterDoesNotShareJoinField(t *testing.T "subType": dataMap{ "selectTopNode": dataMap{ "selectNode": dataMap{ + "_keys": nil, "filter": nil, "scanNode": dataMap{ "collectionID": "1", @@ -320,6 +324,7 @@ func TestQueryOneToManyWithCountAndChildFilterDoesNotShareJoinField(t *testing.T "subType": dataMap{ "selectTopNode": dataMap{ "selectNode": dataMap{ + "_keys": nil, "filter": nil, "scanNode": dataMap{ "collectionID": "1",