Skip to content

Commit

Permalink
feat: Explain topLevelNode like a MultiNode plan (#749)
Browse files Browse the repository at this point in the history
- Resolves #748 

Wire-up `topLevelNode` such that we can traverse its children using the same `MultiNode` logic. In case explainable nodes (mostly the 3 aggregate nodes) encounter a child where the child is recursively referring back to `topLevelNode`, then the explain stops the explain graph there (to avoid stack overflow and endless looping).
  • Loading branch information
shahzadlone authored Aug 18, 2022
1 parent cfbc4cb commit f6ed210
Show file tree
Hide file tree
Showing 7 changed files with 376 additions and 26 deletions.
16 changes: 9 additions & 7 deletions query/graphql/planner/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (

type explainablePlanNode interface {
planNode
Explain() (map[string]interface{}, error)
Explain() (map[string]any, error)
}

// Compile time check for all planNodes that should be explainable (satisfy explainablePlanNode).
Expand All @@ -35,6 +35,7 @@ var (
_ explainablePlanNode = (*selectNode)(nil)
_ explainablePlanNode = (*selectTopNode)(nil)
_ explainablePlanNode = (*sumNode)(nil)
_ explainablePlanNode = (*topLevelNode)(nil)
_ explainablePlanNode = (*typeIndexJoin)(nil)
_ explainablePlanNode = (*updateNode)(nil)
)
Expand Down Expand Up @@ -81,8 +82,8 @@ const (
// }
// ]
// }
func buildExplainGraph(source planNode) (map[string]interface{}, error) {
explainGraph := map[string]interface{}{}
func buildExplainGraph(source planNode) (map[string]any, error) {
explainGraph := map[string]any{}

if source == nil {
return explainGraph, nil
Expand All @@ -93,7 +94,7 @@ func buildExplainGraph(source planNode) (map[string]interface{}, error) {
// Note: MultiNode nodes are not explainable but we use them to wrap the children under them.
case MultiNode:
// List to store all explain graphs of explainable children of MultiNode.
multiChildExplainGraph := []map[string]interface{}{}
multiChildExplainGraph := []map[string]any{}
for _, childSource := range node.Children() {
childExplainGraph, err := buildExplainGraph(childSource)
if err != nil {
Expand Down Expand Up @@ -137,12 +138,13 @@ func buildExplainGraph(source planNode) (map[string]interface{}, error) {

// Support nil to signal as if there are no attributes to explain for that node.
if explainGraphBuilder == nil {
explainGraphBuilder = map[string]interface{}{}
explainGraphBuilder = map[string]any{}
}

// If not the last child then keep walking the graph to find more explainable nodes.
if node.Source() != nil {
nextExplainGraph, err := buildExplainGraph(node.Source())
// Also make sure the next source / child isn't a recursive `topLevelNode`.
if next := node.Source(); next != nil && next.Kind() != topLevelNodeKind {
nextExplainGraph, err := buildExplainGraph(next)
if err != nil {
return nil, err
}
Expand Down
5 changes: 4 additions & 1 deletion query/graphql/planner/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,23 @@ var (
_ planNode = (*hardLimitNode)(nil)
_ planNode = (*headsetScanNode)(nil)
_ planNode = (*multiScanNode)(nil)
_ planNode = (*orderNode)(nil)
_ planNode = (*parallelNode)(nil)
_ planNode = (*pipeNode)(nil)
_ planNode = (*renderLimitNode)(nil)
_ planNode = (*scanNode)(nil)
_ planNode = (*selectNode)(nil)
_ planNode = (*selectTopNode)(nil)
_ planNode = (*orderNode)(nil)
_ planNode = (*sumNode)(nil)
_ planNode = (*topLevelNode)(nil)
_ planNode = (*typeIndexJoin)(nil)
_ planNode = (*typeJoinMany)(nil)
_ planNode = (*typeJoinOne)(nil)
_ planNode = (*updateNode)(nil)
_ planNode = (*valuesNode)(nil)

_ MultiNode = (*parallelNode)(nil)
_ MultiNode = (*topLevelNode)(nil)
)

// type joinNode struct {
Expand Down
23 changes: 12 additions & 11 deletions query/graphql/planner/planner.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,17 +219,6 @@ func (p *Planner) expandPlan(plan planNode, parentPlan *selectTopNode) error {
}
}
return nil
case MultiNode:
return p.expandMultiNode(n, parentPlan)

case *updateNode:
return p.expandPlan(n.results, parentPlan)

case *createNode:
return p.expandPlan(n.results, parentPlan)

case *deleteNode:
return p.expandPlan(n.source, parentPlan)

case *topLevelNode:
for _, child := range n.children {
Expand All @@ -248,6 +237,18 @@ func (p *Planner) expandPlan(plan planNode, parentPlan *selectTopNode) error {
}
return nil

case MultiNode:
return p.expandMultiNode(n, parentPlan)

case *updateNode:
return p.expandPlan(n.results, parentPlan)

case *createNode:
return p.expandPlan(n.results, parentPlan)

case *deleteNode:
return p.expandPlan(n.source, parentPlan)

default:
return nil
}
Expand Down
9 changes: 8 additions & 1 deletion query/graphql/planner/top.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
parserTypes "github.com/sourcenetwork/defradb/query/graphql/parser/types"
)

const topLevelNodeKind string = "topLevelNode"

// topLevelNode is a special node that represents the very top of the
// plan graph. It has no source, and will only yield a single item
// containing all of its children.
Expand Down Expand Up @@ -50,7 +52,7 @@ func (n *topLevelNode) Spans(spans core.Spans) {
}

func (n *topLevelNode) Kind() string {
return "topLevelNode"
return topLevelNodeKind
}

func (n *topLevelNode) Init() error {
Expand Down Expand Up @@ -115,6 +117,11 @@ func (n *topLevelNode) Source() planNode {
return nil
}

// Children() makes topLevelNode into a MultiNode.
func (p *topLevelNode) Children() []planNode {
return p.children
}

func (n *topLevelNode) Explain() (map[string]any, error) {
return map[string]any{}, nil
}
Expand Down
207 changes: 205 additions & 2 deletions tests/integration/query/explain/top_with_average_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,106 @@ func TestExplainTopLevelAverageQuery(t *testing.T) {
Results: []dataMap{
{
"explain": dataMap{
"topLevelNode": dataMap{},
"topLevelNode": []dataMap{
{
"selectTopNode": dataMap{
"selectNode": dataMap{
"filter": nil,
"scanNode": dataMap{
"collectionID": "3",
"collectionName": "author",
"filter": dataMap{
"age": dataMap{
"_ne": nil,
},
},
"spans": []dataMap{
{
"end": "/4",
"start": "/3",
},
},
},
},
},
},
{
"selectTopNode": dataMap{
"selectNode": dataMap{
"filter": nil,
"scanNode": dataMap{
"collectionID": "3",
"collectionName": "author",
"filter": dataMap{
"age": dataMap{
"_ne": nil,
},
},
"spans": []dataMap{
{
"end": "/4",
"start": "/3",
},
},
},
},
},
},
{
"selectTopNode": dataMap{
"selectNode": dataMap{
"filter": nil,
"scanNode": dataMap{
"collectionID": "3",
"collectionName": "author",
"filter": dataMap{
"age": dataMap{
"_ne": nil,
},
},
"spans": []dataMap{
{
"end": "/4",
"start": "/3",
},
},
},
},
},
},
{
"sumNode": dataMap{
"sources": []dataMap{
{
"childFieldName": "age",
"fieldName": "author",
"filter": dataMap{
"age": dataMap{
"_ne": nil,
},
},
},
},
},
},
{
"countNode": dataMap{
"sources": []dataMap{
{
"fieldName": "author",
"filter": dataMap{
"age": dataMap{
"_ne": nil,
},
},
},
},
},
},
{
"averageNode": dataMap{},
},
},
},
},
},
Expand Down Expand Up @@ -96,7 +195,111 @@ func TestExplainTopLevelAverageQueryWithFilter(t *testing.T) {
Results: []dataMap{
{
"explain": dataMap{
"topLevelNode": dataMap{},
"topLevelNode": []dataMap{
{
"selectTopNode": dataMap{
"selectNode": dataMap{
"filter": nil,
"scanNode": dataMap{
"collectionID": "3",
"collectionName": "author",
"filter": dataMap{
"age": dataMap{
"_gt": int64(26),
"_ne": nil,
},
},
"spans": []dataMap{
{
"end": "/4",
"start": "/3",
},
},
},
},
},
},
{
"selectTopNode": dataMap{
"selectNode": dataMap{
"filter": nil,
"scanNode": dataMap{
"collectionID": "3",
"collectionName": "author",
"filter": dataMap{
"age": dataMap{
"_gt": int64(26),
"_ne": nil,
},
},
"spans": []dataMap{
{
"end": "/4",
"start": "/3",
},
},
},
},
},
},
{
"selectTopNode": dataMap{
"selectNode": dataMap{
"filter": nil,
"scanNode": dataMap{
"collectionID": "3",
"collectionName": "author",
"filter": dataMap{
"age": dataMap{
"_ne": nil,
"_gt": int64(26),
},
},
"spans": []dataMap{
{
"end": "/4",
"start": "/3",
},
},
},
},
},
},
{
"sumNode": dataMap{
"sources": []dataMap{
{
"childFieldName": "age",
"fieldName": "author",
"filter": dataMap{
"age": dataMap{
"_gt": int64(26),
"_ne": nil,
},
},
},
},
},
},
{
"countNode": dataMap{
"sources": []dataMap{
{
"fieldName": "author",
"filter": dataMap{
"age": dataMap{
"_gt": int64(26),
"_ne": nil,
},
},
},
},
},
},
{
"averageNode": dataMap{},
},
},
},
},
},
Expand Down
Loading

0 comments on commit f6ed210

Please sign in to comment.