From 667bc3dadd4310b97fdab774d5df8ec147d2132d Mon Sep 17 00:00:00 2001 From: Shahzad Lone Date: Sun, 17 Jul 2022 11:40:10 -0400 Subject: [PATCH 1/4] wip: Store the field key name with the index, i.e. use `mapper.Field`. --- query/graphql/mapper/mapper.go | 10 ++++++--- query/graphql/mapper/targetable.go | 2 +- query/graphql/planner/arbitrary_join.go | 27 +++++++++++++++---------- query/graphql/planner/group.go | 16 +++++++-------- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/query/graphql/mapper/mapper.go b/query/graphql/mapper/mapper.go index 001f7ce750..eacfe574a6 100644 --- a/query/graphql/mapper/mapper.go +++ b/query/graphql/mapper/mapper.go @@ -780,18 +780,22 @@ func toGroupBy(source *parserTypes.GroupBy, mapping *core.DocumentMapping) *Grou return nil } - indexes := make([]int, len(source.Fields)) + fields := make([]Field, len(source.Fields)) for i, fieldName := range source.Fields { // If there are multiple properties of the same name we can just take the first as // we have no other reasonable way of identifying which property they mean if multiple // consumer specified requestables are available. Aggregate dependencies should not // impact this as they are added after selects. key := mapping.FirstIndexOfName(fieldName) - indexes[i] = key + + fields[i] = Field{ + Index: key, + Name: fieldName, + } } return &GroupBy{ - FieldIndexes: indexes, + Fields: fields, } } diff --git a/query/graphql/mapper/targetable.go b/query/graphql/mapper/targetable.go index c0d9598cfa..838bfd15f5 100644 --- a/query/graphql/mapper/targetable.go +++ b/query/graphql/mapper/targetable.go @@ -84,7 +84,7 @@ type Limit struct { // GroupBy represents a grouping instruction on a query. type GroupBy struct { // The indexes of fields by which documents should be grouped. Ordered. - FieldIndexes []int + Fields []Field } type SortDirection string diff --git a/query/graphql/planner/arbitrary_join.go b/query/graphql/planner/arbitrary_join.go index 3ec2c207d5..3cbe0f8b1f 100644 --- a/query/graphql/planner/arbitrary_join.go +++ b/query/graphql/planner/arbitrary_join.go @@ -15,6 +15,7 @@ import ( "strings" "github.com/sourcenetwork/defradb/core" + "github.com/sourcenetwork/defradb/query/graphql/mapper" ) // A data-source that may yield child items, parent items, or both depending on configuration @@ -111,7 +112,7 @@ func (n *dataSource) Source() planNode { } func (source *dataSource) mergeParent( - keyIndexes []int, + keyFields []mapper.Field, destination *orderedMap, childIndexes []int, ) (bool, error) { @@ -132,7 +133,7 @@ func (source *dataSource) mergeParent( } value := source.parentSource.Value() - key := generateKey(value, keyIndexes) + key := generateKey(value, keyFields) destination.mergeParent(key, childIndexes, value) @@ -140,7 +141,7 @@ func (source *dataSource) mergeParent( } func (source *dataSource) appendChild( - keyIndexes []int, + keyFields []mapper.Field, valuesByKey *orderedMap, mapping *core.DocumentMapping, ) (bool, error) { @@ -165,14 +166,18 @@ func (source *dataSource) appendChild( // the same order - we need to treat it as a new item, regenerating the key and potentially caching // it without yet receiving the parent-level details value := source.childSource.Value() - key := generateKey(value, keyIndexes) + key := generateKey(value, keyFields) valuesByKey.appendChild(key, source.childIndex, value, mapping) return true, nil } -func join(sources []*dataSource, keyIndexes []int, mapping *core.DocumentMapping) (*orderedMap, error) { +func join( + sources []*dataSource, + keyFields []mapper.Field, + mapping *core.DocumentMapping, +) (*orderedMap, error) { result := orderedMap{ values: []core.Doc{}, indexesByKey: map[string]int{}, @@ -190,14 +195,14 @@ func join(sources []*dataSource, keyIndexes []int, mapping *core.DocumentMapping for hasNextParent || hasNextChild { if hasNextParent { - hasNextParent, err = source.mergeParent(keyIndexes, &result, childIndexes) + hasNextParent, err = source.mergeParent(keyFields, &result, childIndexes) if err != nil { return nil, err } } if hasNextChild { - hasNextChild, err = source.appendChild(keyIndexes, &result, mapping) + hasNextChild, err = source.appendChild(keyFields, &result, mapping) if err != nil { return nil, err } @@ -208,11 +213,11 @@ func join(sources []*dataSource, keyIndexes []int, mapping *core.DocumentMapping return &result, nil } -func generateKey(doc core.Doc, keyIndexes []int) string { +func generateKey(doc core.Doc, keyFields []mapper.Field) string { keyBuilder := strings.Builder{} - for _, keyField := range keyIndexes { - keyBuilder.WriteString(fmt.Sprint(keyField)) - keyBuilder.WriteString(fmt.Sprintf("_%v_", doc.Fields[keyField])) + for _, keyField := range keyFields { + keyBuilder.WriteString(fmt.Sprint(keyField.Index)) + keyBuilder.WriteString(fmt.Sprintf("_%v_", doc.Fields[keyField.Index])) } return keyBuilder.String() } diff --git a/query/graphql/planner/group.go b/query/graphql/planner/group.go index 0adb70b4ed..4db621c096 100644 --- a/query/graphql/planner/group.go +++ b/query/graphql/planner/group.go @@ -29,7 +29,7 @@ type groupNode struct { // The fields to group by - this must be an ordered collection and // will include any parent group-by fields (if any) - groupByFieldIndexes []int + groupByFields []mapper.Field // The data sources that this node will draw data from. dataSources []*dataSource @@ -61,17 +61,17 @@ func (p *Planner) GroupBy(n *mapper.GroupBy, parsed *mapper.Select, childSelects if childSelect.GroupBy != nil { // group by fields have to be propagated downwards to ensure correct sub-grouping, otherwise child // groups will only group on the fields they explicitly reference - childSelect.GroupBy.FieldIndexes = append(childSelect.GroupBy.FieldIndexes, n.FieldIndexes...) + childSelect.GroupBy.Fields = append(childSelect.GroupBy.Fields, n.Fields...) } dataSources = append(dataSources, newDataSource(childSelect.Index)) } groupNodeObj := groupNode{ - p: p, - childSelects: childSelects, - groupByFieldIndexes: n.FieldIndexes, - dataSources: dataSources, - docMapper: docMapper{&parsed.DocumentMapping}, + p: p, + childSelects: childSelects, + groupByFields: n.Fields, + dataSources: dataSources, + docMapper: docMapper{&parsed.DocumentMapping}, } return &groupNodeObj, nil } @@ -127,7 +127,7 @@ func (n *groupNode) Source() planNode { return n.dataSources[0].Source() } func (n *groupNode) Next() (bool, error) { if n.values == nil { - values, err := join(n.dataSources, n.groupByFieldIndexes, n.documentMapping) + values, err := join(n.dataSources, n.groupByFields, n.documentMapping) if err != nil { return false, err } From 81462de8e68b037764a27406528af86ea5c0e75e Mon Sep 17 00:00:00 2001 From: Shahzad Lone Date: Sun, 17 Jul 2022 18:36:15 -0400 Subject: [PATCH 2/4] wip: Make `groupNode` explainable. --- query/graphql/planner/explain.go | 1 + query/graphql/planner/group.go | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/query/graphql/planner/explain.go b/query/graphql/planner/explain.go index 5a418b608f..ba3ccb1d52 100644 --- a/query/graphql/planner/explain.go +++ b/query/graphql/planner/explain.go @@ -27,6 +27,7 @@ var ( _ explainablePlanNode = (*createNode)(nil) _ explainablePlanNode = (*dagScanNode)(nil) _ explainablePlanNode = (*deleteNode)(nil) + _ explainablePlanNode = (*groupNode)(nil) _ explainablePlanNode = (*hardLimitNode)(nil) _ explainablePlanNode = (*orderNode)(nil) _ explainablePlanNode = (*renderLimitNode)(nil) diff --git a/query/graphql/planner/group.go b/query/graphql/planner/group.go index 4db621c096..8d07cffa10 100644 --- a/query/graphql/planner/group.go +++ b/query/graphql/planner/group.go @@ -169,3 +169,9 @@ func (n *groupNode) Next() (bool, error) { return false, nil } + +// Explain method returns a map containing all attributes of this node that +// are to be explained, subscribes / opts-in this node to be an explainablePlanNode. +func (n *groupNode) Explain() (map[string]interface{}, error) { + return map[string]interface{}{}, nil +} From dd80b97ff1df5981b4ce9f38b87c02e1cb644e56 Mon Sep 17 00:00:00 2001 From: Shahzad Lone Date: Sun, 17 Jul 2022 22:32:02 -0400 Subject: [PATCH 3/4] wip: Implement the explanation of the groupNode attribute(s). --- query/graphql/planner/group.go | 105 ++++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/query/graphql/planner/group.go b/query/graphql/planner/group.go index 8d07cffa10..ecffbaed0e 100644 --- a/query/graphql/planner/group.go +++ b/query/graphql/planner/group.go @@ -11,6 +11,8 @@ package planner import ( + "fmt" + "github.com/sourcenetwork/defradb/core" "github.com/sourcenetwork/defradb/query/graphql/mapper" @@ -173,5 +175,106 @@ func (n *groupNode) Next() (bool, error) { // Explain method returns a map containing all attributes of this node that // are to be explained, subscribes / opts-in this node to be an explainablePlanNode. func (n *groupNode) Explain() (map[string]interface{}, error) { - return map[string]interface{}{}, nil + explainerMap := map[string]interface{}{} + + // Get the parent level groupBy attribute(s). + groupByFields := []string{} + for _, field := range n.groupByFields { + groupByFields = append( + groupByFields, + field.Name, + ) + } + explainerMap["groupByFields"] = groupByFields + + // Get the inner group (child) selection attribute(s). + if len(n.childSelects) == 0 { + explainerMap["childSelects"] = nil + } else { + childSelects := make([]map[string]interface{}, 0, len(n.childSelects)) + for _, child := range n.childSelects { + if child == nil { + continue + } + + childExplainGraph := map[string]interface{}{} + + childExplainGraph[collectionNameLabel] = child.CollectionName + + c := child.Targetable + + // Get targetable attribute(s) of this child. + + if c.DocKeys.HasValue { + childExplainGraph["docKeys"] = c.DocKeys.Value + } else { + childExplainGraph["docKeys"] = nil + } + + if c.Filter == nil || c.Filter.ExternalConditions == nil { + childExplainGraph[filterLabel] = nil + } else { + childExplainGraph[filterLabel] = c.Filter.ExternalConditions + } + + if c.Limit != nil { + childExplainGraph[limitLabel] = map[string]interface{}{ + limitLabel: c.Limit.Limit, + offsetLabel: c.Limit.Offset, + } + } else { + childExplainGraph[limitLabel] = nil + } + + if c.OrderBy != nil { + innerOrderings := []map[string]interface{}{} + for _, condition := range c.OrderBy.Conditions { + orderFieldNames := []string{} + + for _, orderFieldIndex := range condition.FieldIndexes { + // Try to find the name of this index. + fieldName, found := n.documentMapping.TryToFindNameFromIndex(orderFieldIndex) + if !found { + return nil, fmt.Errorf( + "No order field name (for grouping) was found for index =%d", + orderFieldIndex, + ) + } + + orderFieldNames = append(orderFieldNames, fieldName) + } + // Put it all together for this order element. + innerOrderings = append(innerOrderings, + map[string]interface{}{ + "fields": orderFieldNames, + "direction": string(condition.Direction), + }, + ) + } + + childExplainGraph["orderBy"] = innerOrderings + } else { + childExplainGraph["orderBy"] = nil + } + + if c.GroupBy != nil { + innerGroupByFields := []string{} + for _, fieldOfChildGroupBy := range c.GroupBy.Fields { + innerGroupByFields = append( + innerGroupByFields, + fieldOfChildGroupBy.Name, + ) + } + childExplainGraph["groupBy"] = innerGroupByFields + } else { + childExplainGraph["groupBy"] = nil + } + + childSelects = append(childSelects, childExplainGraph) + } + + explainerMap["childSelects"] = childSelects + } + + return explainerMap, nil } From 08a13d1e4d389b48fe66e2759d71138e6b110d90 Mon Sep 17 00:00:00 2001 From: Shahzad Lone Date: Fri, 22 Jul 2022 04:47:39 -0400 Subject: [PATCH 4/4] wip: Add tests for groupBy explain cases. --- tests/integration/query/explain/group_test.go | 161 ++++++ .../query/explain/group_with_average_test.go | 492 ++++++++++++++++++ .../query/explain/group_with_dockey_test.go | 187 +++++++ .../query/explain/group_with_filter_test.go | 172 ++++++ .../query/explain/group_with_limit_test.go | 411 +++++++++++++++ .../query/explain/group_with_order_test.go | 406 +++++++++++++++ 6 files changed, 1829 insertions(+) create mode 100644 tests/integration/query/explain/group_test.go create mode 100644 tests/integration/query/explain/group_with_average_test.go create mode 100644 tests/integration/query/explain/group_with_dockey_test.go create mode 100644 tests/integration/query/explain/group_with_filter_test.go create mode 100644 tests/integration/query/explain/group_with_limit_test.go create mode 100644 tests/integration/query/explain/group_with_order_test.go diff --git a/tests/integration/query/explain/group_test.go b/tests/integration/query/explain/group_test.go new file mode 100644 index 0000000000..b8df10a79b --- /dev/null +++ b/tests/integration/query/explain/group_test.go @@ -0,0 +1,161 @@ +// 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 test_explain + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +func TestExplainSimpleGroupByOnParent(t *testing.T) { + test := testUtils.QueryTestCase{ + Description: "Explain a grouping on parent.", + Query: `query @explain { + author (groupBy: [age]) { + age + _group { + name + } + } + }`, + + Docs: map[int][]string{ + //authors + 2: { + `{ + "name": "John Grisham", + "age": 65 + }`, + + `{ + "name": "Cornelia Funke", + "age": 62 + }`, + + `{ + "name": "John's Twin", + "age": 65 + }`, + }, + }, + + Results: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "groupNode": dataMap{ + "groupByFields": []string{"age"}, + "childSelects": []dataMap{ + { + "collectionName": "author", + "docKeys": nil, + "groupBy": nil, + "limit": nil, + "orderBy": nil, + "filter": nil, + }, + }, + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "collectionID": "3", + "collectionName": "author", + "filter": nil, + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestExplainGroupByTwoFieldsOnParent(t *testing.T) { + test := testUtils.QueryTestCase{ + Description: "Explain a grouping by two fields.", + Query: `query @explain { + author (groupBy: [age, name]) { + age + _group { + name + } + } + }`, + + Docs: map[int][]string{ + //authors + 2: { + `{ + "name": "John Grisham", + "age": 65 + }`, + + `{ + "name": "Cornelia Funke", + "age": 62 + }`, + + `{ + "name": "John's Twin", + "age": 65 + }`, + }, + }, + + Results: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "groupNode": dataMap{ + "groupByFields": []string{"age", "name"}, + "childSelects": []dataMap{ + { + "collectionName": "author", + "docKeys": nil, + "groupBy": nil, + "limit": nil, + "orderBy": nil, + "filter": nil, + }, + }, + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "collectionID": "3", + "collectionName": "author", + "filter": nil, + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} diff --git a/tests/integration/query/explain/group_with_average_test.go b/tests/integration/query/explain/group_with_average_test.go new file mode 100644 index 0000000000..fbf95e1028 --- /dev/null +++ b/tests/integration/query/explain/group_with_average_test.go @@ -0,0 +1,492 @@ +// 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 test_explain + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +func TestExplainGroupByWithAverageOnAnInnerField(t *testing.T) { + test := testUtils.QueryTestCase{ + Description: "Explain a groupBy with average on an field.", + Query: `query @explain { + author (groupBy: [name]) { + name + _avg(_group: {field: age}) + } + }`, + + Docs: map[int][]string{ + //authors + 2: { + `{ + "name": "John Grisham", + "verified": true, + "age": 65 + }`, + `{ + "name": "John Grisham", + "verified": false, + "age": 2 + }`, + `{ + "name": "John Grisham", + "verified": true, + "age": 50 + }`, + `{ + "name": "Cornelia Funke", + "verified": true, + "age": 62 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + }, + }, + + Results: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "averageNode": dataMap{ + "countNode": dataMap{ + "sources": []dataMap{ + { + "fieldName": "_group", + "filter": dataMap{ + "age": dataMap{ + "_ne": nil, + }, + }, + }, + }, + "sumNode": dataMap{ + "sources": []dataMap{ + { + "childFieldName": "age", + "fieldName": "_group", + "filter": dataMap{ + "age": dataMap{ + "_ne": nil, + }, + }, + }, + }, + "groupNode": dataMap{ + "childSelects": []dataMap{ + { + "collectionName": "author", + "docKeys": nil, + "filter": dataMap{ + "age": dataMap{ + "_ne": nil, + }, + }, + "groupBy": nil, + "limit": nil, + "orderBy": nil, + }, + { + "collectionName": "author", + "docKeys": nil, + "filter": dataMap{ + "age": dataMap{ + "_ne": nil, + }, + }, + "groupBy": nil, + "limit": nil, + "orderBy": nil, + }, + { + "collectionName": "author", + "docKeys": nil, + "groupBy": nil, + "limit": nil, + "orderBy": nil, + "filter": dataMap{ + "age": dataMap{ + "_ne": nil, + }, + }, + }, + }, + "groupByFields": []string{"name"}, + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "collectionID": "3", + "collectionName": "author", + "filter": nil, + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestExplainGroupByWithAnAverageInsideTheInnerGroupOnAField(t *testing.T) { + test := testUtils.QueryTestCase{ + Description: "Explain a groupBy with average of inside the inner group (on a field).", + Query: `query @explain { + author (groupBy: [name]) { + name + _avg(_group: {field: _avg}) + _group(groupBy: [verified]) { + verified + _avg(_group: {field: age}) + } + } + }`, + + Docs: map[int][]string{ + //authors + 2: { + `{ + "name": "John Grisham", + "verified": true, + "age": 65 + }`, + `{ + "name": "John Grisham", + "verified": false, + "age": 2 + }`, + `{ + "name": "John Grisham", + "verified": true, + "age": 50 + }`, + `{ + "name": "Cornelia Funke", + "verified": true, + "age": 62 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + }, + }, + + Results: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "averageNode": dataMap{ + "countNode": dataMap{ + "sources": []dataMap{ + { + "fieldName": "_group", + "filter": nil, + }, + }, + "sumNode": dataMap{ + "sources": []dataMap{ + { + "childFieldName": "_avg", + "fieldName": "_group", + "filter": nil, + }, + }, + "groupNode": dataMap{ + "childSelects": []dataMap{ + { + "collectionName": "author", + "groupBy": []string{"verified", "name"}, + "docKeys": nil, + "filter": nil, + "limit": nil, + "orderBy": nil, + }, + }, + "groupByFields": []string{"name"}, + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "collectionID": "3", + "collectionName": "author", + "filter": nil, + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestExplainGroupByWithAnAverageInsideTheInnerGroupAndNestedGroupBy(t *testing.T) { + test := testUtils.QueryTestCase{ + Description: "Explain a groupBy with average of inside the inner group with nested groupBy.", + Query: `query @explain { + author (groupBy: [name]) { + name + _avg(_group: {field: _avg}) + _group(groupBy: [verified]) { + verified + _avg(_group: {field: age}) + _group (groupBy: [age]){ + age + } + } + } + }`, + + Docs: map[int][]string{ + //authors + 2: { + `{ + "name": "John Grisham", + "verified": true, + "age": 65 + }`, + `{ + "name": "John Grisham", + "verified": false, + "age": 2 + }`, + `{ + "name": "John Grisham", + "verified": true, + "age": 50 + }`, + `{ + "name": "Cornelia Funke", + "verified": true, + "age": 62 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + }, + }, + + Results: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "averageNode": dataMap{ + "countNode": dataMap{ + "sources": []dataMap{ + { + "fieldName": "_group", + "filter": nil, + }, + }, + "sumNode": dataMap{ + "sources": []dataMap{ + { + "childFieldName": "_avg", + "fieldName": "_group", + "filter": nil, + }, + }, + "groupNode": dataMap{ + "childSelects": []dataMap{ + { + "collectionName": "author", + "groupBy": []string{"verified", "name"}, + "docKeys": nil, + "filter": nil, + "limit": nil, + "orderBy": nil, + }, + }, + "groupByFields": []string{"name"}, + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "collectionID": "3", + "collectionName": "author", + "filter": nil, + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestExplainGroupByWihAnAverageInsideTheInnerGroupAndNestedGroupByWithAnAverage(t *testing.T) { + test := testUtils.QueryTestCase{ + Description: "Explain a groupBy with average of inside the inner group with nested groupBy with and average.", + Query: `query @explain { + author (groupBy: [name]) { + name + _avg(_group: {field: _avg}) + _group(groupBy: [verified]) { + verified + _avg(_group: {field: age}) + _group (groupBy: [age]){ + age + _avg(_group: {field: age}) + } + } + } + }`, + + Docs: map[int][]string{ + //authors + 2: { + `{ + "name": "John Grisham", + "verified": true, + "age": 65 + }`, + `{ + "name": "John Grisham", + "verified": false, + "age": 2 + }`, + `{ + "name": "John Grisham", + "verified": true, + "age": 50 + }`, + `{ + "name": "Cornelia Funke", + "verified": true, + "age": 62 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + }, + }, + + Results: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "averageNode": dataMap{ + "countNode": dataMap{ + "sources": []dataMap{ + { + "fieldName": "_group", + "filter": nil, + }, + }, + "sumNode": dataMap{ + "sources": []dataMap{ + { + "childFieldName": "_avg", + "fieldName": "_group", + "filter": nil, + }, + }, + "groupNode": dataMap{ + "childSelects": []dataMap{ + { + "collectionName": "author", + "groupBy": []string{"verified", "name"}, + "docKeys": nil, + "filter": nil, + "limit": nil, + "orderBy": nil, + }, + }, + "groupByFields": []string{"name"}, + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "collectionID": "3", + "collectionName": "author", + "filter": nil, + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} diff --git a/tests/integration/query/explain/group_with_dockey_test.go b/tests/integration/query/explain/group_with_dockey_test.go new file mode 100644 index 0000000000..982401a141 --- /dev/null +++ b/tests/integration/query/explain/group_with_dockey_test.go @@ -0,0 +1,187 @@ +// 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 test_explain + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +func TestExplainQueryWithDockeyOnParentGroupBy(t *testing.T) { + test := testUtils.QueryTestCase{ + Description: "Explain query with a dockey on parent groupBy.", + + Query: `query @explain { + author( + groupBy: [age], + dockey: "bae-6a4c5bc5-b044-5a03-a868-8260af6f2254" + ) { + age + _group { + name + } + } + }`, + + Docs: map[int][]string{ + //authors + 2: { + // dockey: "bae-21a6ad4a-1cd8-5613-807c-a90c7c12f880" + `{ + "name": "John Grisham", + "age": 12 + }`, + + // dockey: "bae-6a4c5bc5-b044-5a03-a868-8260af6f2254" + `{ + "name": "Cornelia Funke", + "age": 20 + }`, + + // dockey: "bae-4ea9d148-13f3-5a48-a0ef-9ffd344caeed" + `{ + "name": "John's Twin", + "age": 65 + }`, + }, + }, + + Results: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "groupNode": dataMap{ + "childSelects": []dataMap{ + { + "collectionName": "author", + "docKeys": nil, + "filter": nil, + "groupBy": nil, + "limit": nil, + "orderBy": nil, + }, + }, + "groupByFields": []string{"age"}, + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "collectionID": "3", + "collectionName": "author", + "filter": nil, + "spans": []dataMap{ + { + "start": "/3/bae-6a4c5bc5-b044-5a03-a868-8260af6f2254", + "end": "/3/bae-6a4c5bc5-b044-5a03-a868-8260af6f2255", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestExplainQuerySimpleWithDockeysAndFilter(t *testing.T) { + test := testUtils.QueryTestCase{ + Description: "Explain query with a dockeys and filter on parent groupBy.", + + Query: `query @explain { + author( + groupBy: [age], + filter: {age: {_eq: 20}}, + dockeys: [ + "bae-6a4c5bc5-b044-5a03-a868-8260af6f2254", + "bae-4ea9d148-13f3-5a48-a0ef-9ffd344caeed" + ] + ) { + age + _group { + name + } + } + }`, + + Docs: map[int][]string{ + //authors + 2: { + // dockey: "bae-21a6ad4a-1cd8-5613-807c-a90c7c12f880" + `{ + "name": "John Grisham", + "age": 12 + }`, + + // dockey: "bae-6a4c5bc5-b044-5a03-a868-8260af6f2254" + `{ + "name": "Cornelia Funke", + "age": 20 + }`, + + // dockey: "bae-4ea9d148-13f3-5a48-a0ef-9ffd344caeed" + `{ + "name": "John's Twin", + "age": 65 + }`, + }, + }, + + Results: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "groupNode": dataMap{ + "childSelects": []dataMap{ + { + "collectionName": "author", + "docKeys": nil, + "groupBy": nil, + "limit": nil, + "orderBy": nil, + "filter": nil, + }, + }, + "groupByFields": []string{"age"}, + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "collectionID": "3", + "collectionName": "author", + "filter": dataMap{ + "age": dataMap{ + "_eq": int64(20), + }, + }, + "spans": []dataMap{ + { + "start": "/3/bae-6a4c5bc5-b044-5a03-a868-8260af6f2254", + "end": "/3/bae-6a4c5bc5-b044-5a03-a868-8260af6f2255", + }, + { + "start": "/3/bae-4ea9d148-13f3-5a48-a0ef-9ffd344caeed", + "end": "/3/bae-4ea9d148-13f3-5a48-a0ef-9ffd344caeee", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} diff --git a/tests/integration/query/explain/group_with_filter_test.go b/tests/integration/query/explain/group_with_filter_test.go new file mode 100644 index 0000000000..5f07227c90 --- /dev/null +++ b/tests/integration/query/explain/group_with_filter_test.go @@ -0,0 +1,172 @@ +// 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 test_explain + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +func TestExplainGroupByWithFilterOnParent(t *testing.T) { + test := testUtils.QueryTestCase{ + Description: "Explain a grouping with filter on parent.", + Query: `query @explain { + author ( + groupBy: [age], + filter: {age: {_gt: 63}} + ) { + age + _group { + name + } + } + }`, + + Docs: map[int][]string{ + //authors + 2: { + `{ + "name": "John Grisham", + "age": 65 + }`, + + `{ + "name": "Cornelia Funke", + "age": 62 + }`, + + `{ + "name": "John's Twin", + "age": 65 + }`, + }, + }, + + Results: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "groupNode": dataMap{ + "groupByFields": []string{"age"}, + "childSelects": []dataMap{ + { + "collectionName": "author", + "docKeys": nil, + "groupBy": nil, + "limit": nil, + "orderBy": nil, + "filter": nil, + }, + }, + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "collectionID": "3", + "collectionName": "author", + "filter": dataMap{ + "age": dataMap{ + "_gt": int64(63), + }, + }, + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestExplainGroupByWithFilterOnInnerGroupSelection(t *testing.T) { + test := testUtils.QueryTestCase{ + Description: "Explain a grouping with filter on the inner group selection.", + Query: `query @explain { + author (groupBy: [age]) { + age + _group(filter: {age: {_gt: 63}}) { + name + } + } + }`, + + Docs: map[int][]string{ + //authors + 2: { + `{ + "name": "John Grisham", + "age": 65 + }`, + + `{ + "name": "Cornelia Funke", + "age": 62 + }`, + + `{ + "name": "John's Twin", + "age": 65 + }`, + }, + }, + + Results: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "groupNode": dataMap{ + "groupByFields": []string{"age"}, + "childSelects": []dataMap{ + { + "collectionName": "author", + "docKeys": nil, + "groupBy": nil, + "limit": nil, + "orderBy": nil, + "filter": dataMap{ + "age": dataMap{ + "_gt": int64(63), + }, + }, + }, + }, + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "filter": nil, + "collectionID": "3", + "collectionName": "author", + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} diff --git a/tests/integration/query/explain/group_with_limit_test.go b/tests/integration/query/explain/group_with_limit_test.go new file mode 100644 index 0000000000..44de5aea72 --- /dev/null +++ b/tests/integration/query/explain/group_with_limit_test.go @@ -0,0 +1,411 @@ +// 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 test_explain + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +func TestExplainGroupByWithGroupLimitAndOffsetOnParentGroupBy(t *testing.T) { + test := testUtils.QueryTestCase{ + + Description: "Explain query with limit and offset on parent groupBy.", + + Query: `query @explain { + author( + groupBy: [name], + limit: 1, + offset: 1 + ) { + age + _group { + name + } + } + }`, + + Docs: map[int][]string{ + //authors + 2: { + `{ + "name": "John Grisham", + "verified": true, + "age": 65 + }`, + `{ + "name": "John Grisham", + "verified": false, + "age": 2 + }`, + `{ + "name": "John Grisham", + "verified": true, + "age": 50 + }`, + `{ + "name": "Cornelia Funke", + "verified": true, + "age": 62 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + }, + }, + + Results: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "hardLimitNode": dataMap{ + "limit": int64(1), + "offset": int64(1), + "groupNode": dataMap{ + "groupByFields": []string{"name"}, + "childSelects": []dataMap{ + { + "collectionName": "author", + "orderBy": nil, + "docKeys": nil, + "groupBy": nil, + "limit": nil, + "filter": nil, + }, + }, + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "collectionID": "3", + "collectionName": "author", + "filter": nil, + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestExplainGroupByWithGroupLimitAndOffsetOnChild(t *testing.T) { + test := testUtils.QueryTestCase{ + + Description: "Explain query with limit and offset on child groupBy.", + + Query: `query @explain { + author(groupBy: [name]) { + age + _group(limit: 2, offset: 1) { + name + } + } + }`, + + Docs: map[int][]string{ + //authors + 2: { + `{ + "name": "John Grisham", + "verified": true, + "age": 65 + }`, + `{ + "name": "John Grisham", + "verified": false, + "age": 2 + }`, + `{ + "name": "John Grisham", + "verified": true, + "age": 50 + }`, + `{ + "name": "Cornelia Funke", + "verified": true, + "age": 62 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + }, + }, + + Results: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "groupNode": dataMap{ + "childSelects": []dataMap{ + { + "collectionName": "author", + "limit": dataMap{ + "limit": int64(2), + "offset": int64(1), + }, + "docKeys": nil, + "filter": nil, + "groupBy": nil, + "orderBy": nil, + }, + }, + "groupByFields": []string{"name"}, + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "collectionID": "3", + "collectionName": "author", + "filter": nil, + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestExplainGroupByWithGroupLimitOnChildMultipleRendered(t *testing.T) { + test := testUtils.QueryTestCase{ + Description: "Explain query with limit on child groupBy (multiple rendered).", + Query: `query @explain { + author(groupBy: [name]) { + name + innerFirstGroup: _group(limit: 1, offset: 2) { + age + } + innerSecondGroup: _group(limit: 2) { + age + } + } + }`, + + Docs: map[int][]string{ + //authors + 2: { + `{ + "name": "John Grisham", + "verified": true, + "age": 65 + }`, + `{ + "name": "John Grisham", + "verified": false, + "age": 2 + }`, + `{ + "name": "John Grisham", + "verified": true, + "age": 50 + }`, + `{ + "name": "Cornelia Funke", + "verified": true, + "age": 62 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + }, + }, + + Results: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "groupNode": dataMap{ + "childSelects": []dataMap{ + { + "collectionName": "author", + "limit": dataMap{ + "limit": int64(1), + "offset": int64(2), + }, + "docKeys": nil, + "filter": nil, + "groupBy": nil, + "orderBy": nil, + }, + { + "collectionName": "author", + "limit": dataMap{ + "limit": int64(2), + "offset": int64(0), + }, + "docKeys": nil, + "filter": nil, + "groupBy": nil, + "orderBy": nil, + }, + }, + "groupByFields": []string{"name"}, + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "collectionID": "3", + "collectionName": "author", + "filter": nil, + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestExplainGroupByWithGroupLimitOnParentAndChild(t *testing.T) { + test := testUtils.QueryTestCase{ + Description: "Explain query with limit on parent and child groupBy.", + Query: `query @explain { + author( + groupBy: [name], + limit: 1 + ) { + name + _group(limit: 2) { + age + } + } + }`, + + Docs: map[int][]string{ + //authors + 2: { + `{ + "name": "John Grisham", + "verified": true, + "age": 65 + }`, + `{ + "name": "John Grisham", + "verified": false, + "age": 2 + }`, + `{ + "name": "John Grisham", + "verified": true, + "age": 50 + }`, + `{ + "name": "Cornelia Funke", + "verified": true, + "age": 62 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + }, + }, + + Results: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "hardLimitNode": dataMap{ + "limit": int64(1), + "offset": int64(0), + "groupNode": dataMap{ + "groupByFields": []string{"name"}, + "childSelects": []dataMap{ + { + "collectionName": "author", + "limit": dataMap{ + "limit": int64(2), + "offset": int64(0), + }, + "orderBy": nil, + "docKeys": nil, + "groupBy": nil, + "filter": nil, + }, + }, + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "collectionID": "3", + "collectionName": "author", + "filter": nil, + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} diff --git a/tests/integration/query/explain/group_with_order_test.go b/tests/integration/query/explain/group_with_order_test.go new file mode 100644 index 0000000000..23f8dfb46f --- /dev/null +++ b/tests/integration/query/explain/group_with_order_test.go @@ -0,0 +1,406 @@ +// 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 test_explain + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +func TestExplainGroupByWithOrderOnParentGroup(t *testing.T) { + test := testUtils.QueryTestCase{ + Description: "Explain query with ordered parent groupBy.", + Query: `query @explain { + author(groupBy: [name], order: {name: DESC}) { + name + _group { + age + } + } + }`, + + Docs: map[int][]string{ + //authors + 2: { + `{ + "name": "John Grisham", + "verified": true, + "age": 65 + }`, + `{ + "name": "John Grisham", + "verified": false, + "age": 2 + }`, + `{ + "name": "John Grisham", + "verified": true, + "age": 50 + }`, + `{ + "name": "Cornelia Funke", + "verified": true, + "age": 62 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + }, + }, + + Results: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "orderNode": dataMap{ + "orderings": []dataMap{ + { + "direction": "DESC", + "fields": []string{"name"}, + }, + }, + "groupNode": dataMap{ + "groupByFields": []string{"name"}, + "childSelects": []dataMap{ + { + "collectionName": "author", + "docKeys": nil, + "orderBy": nil, + "groupBy": nil, + "limit": nil, + "filter": nil, + }, + }, + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "collectionID": "3", + "collectionName": "author", + "filter": nil, + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestExplainGroupByWithOrderOnTheChildGroup(t *testing.T) { + test := testUtils.QueryTestCase{ + Description: "Explain query with groupBy string, and child order ascending.", + Query: `query @explain { + author(groupBy: [name]) { + name + _group (order: {age: ASC}){ + age + } + } + }`, + + Docs: map[int][]string{ + //authors + 2: { + `{ + "name": "John Grisham", + "verified": true, + "age": 65 + }`, + `{ + "name": "John Grisham", + "verified": false, + "age": 2 + }`, + `{ + "name": "John Grisham", + "verified": true, + "age": 50 + }`, + `{ + "name": "Cornelia Funke", + "verified": true, + "age": 62 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + }, + }, + + Results: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "groupNode": dataMap{ + "groupByFields": []string{"name"}, + "childSelects": []dataMap{ + { + "collectionName": "author", + "orderBy": []dataMap{ + { + "direction": "ASC", + "fields": []string{"age"}, + }, + }, + "docKeys": nil, + "groupBy": nil, + "limit": nil, + "filter": nil, + }, + }, + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "collectionID": "3", + "collectionName": "author", + "filter": nil, + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestExplainGroupByWithOrderOnTheChildGroupAndOnParentGroup(t *testing.T) { + test := testUtils.QueryTestCase{ + Description: "Explain query with parent groupBy order, and child order.", + Query: `query @explain { + author(groupBy: [name], order: {name: DESC}) { + name + _group (order: {age: ASC}){ + age + } + } + }`, + + Docs: map[int][]string{ + //authors + 2: { + `{ + "name": "John Grisham", + "verified": true, + "age": 65 + }`, + `{ + "name": "John Grisham", + "verified": false, + "age": 2 + }`, + `{ + "name": "John Grisham", + "verified": true, + "age": 50 + }`, + `{ + "name": "Cornelia Funke", + "verified": true, + "age": 62 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + }, + }, + + Results: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "orderNode": dataMap{ + "orderings": []dataMap{ + { + "direction": "DESC", + "fields": []string{"name"}, + }, + }, + "groupNode": dataMap{ + "groupByFields": []string{"name"}, + "childSelects": []dataMap{ + { + "collectionName": "author", + "orderBy": []dataMap{ + { + "direction": "ASC", + "fields": []string{"age"}, + }, + }, + "docKeys": nil, + "groupBy": nil, + "limit": nil, + "filter": nil, + }, + }, + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "collectionID": "3", + "collectionName": "author", + "filter": nil, + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestExplainGroupByWithOrderOnTheNestedChildOfChildGroup(t *testing.T) { + test := testUtils.QueryTestCase{ + Description: "Explain query with parent groupBy order, and child order.", + Query: `query @explain { + author(groupBy: [name]) { + name + _group ( + groupBy: [verified], + order: {verified: ASC} + ){ + verified + _group (order: {age: DESC}) { + age + } + } + } + }`, + + Docs: map[int][]string{ + //authors + 2: { + `{ + "name": "John Grisham", + "verified": true, + "age": 65 + }`, + `{ + "name": "John Grisham", + "verified": false, + "age": 2 + }`, + `{ + "name": "John Grisham", + "verified": true, + "age": 50 + }`, + `{ + "name": "Cornelia Funke", + "verified": true, + "age": 62 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + `{ + "name": "Twin", + "verified": true, + "age": 63 + }`, + }, + }, + + Results: []dataMap{ + { + "explain": dataMap{ + "selectTopNode": dataMap{ + "groupNode": dataMap{ + "groupByFields": []string{"name"}, + "childSelects": []dataMap{ + { + "collectionName": "author", + "orderBy": []dataMap{ + { + "direction": "ASC", + "fields": []string{"verified"}, + }, + }, + "groupBy": []string{"verified", "name"}, + "docKeys": nil, + "limit": nil, + "filter": nil, + }, + }, + "selectNode": dataMap{ + "filter": nil, + "scanNode": dataMap{ + "collectionID": "3", + "collectionName": "author", + "filter": nil, + "spans": []dataMap{ + { + "start": "/3", + "end": "/4", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +}