Skip to content

Commit

Permalink
feat: Add ability to explain-debug all nodes (sourcenetwork#1563)
Browse files Browse the repository at this point in the history
Resolves sourcenetwork#948 

## Description
Implements a new type of explain called `debug` which dumps all plan
nodes (even non-explainable nodes) as a graph (has no attributes).

Usage is similar to other explain types, but would just do
`@explain(type: debug)` instead of `@explain(type: simple)` or
`@explain(type: execute)`.

Example Request:
```
query @Explain(type: debug) {
	Author (groupBy: [age]) {
		age
		_group {
			name
		}
	}
}
```

Response:
```
{
	"data": [
		"explain": {
			"selectTopNode": {
				"groupNode": {
					"selectNode": {
						"pipeNode": {
							"scanNode": {}
						}
					}
				}
			}
		}
	]
}
```
  • Loading branch information
shahzadlone authored Jun 13, 2023
1 parent 7b7f2f9 commit b830614
Show file tree
Hide file tree
Showing 44 changed files with 4,055 additions and 3 deletions.
1 change: 1 addition & 0 deletions client/request/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ type ExplainType string
const (
SimpleExplain ExplainType = "simple"
ExecuteExplain ExplainType = "execute"
DebugExplain ExplainType = "debug"
)
120 changes: 118 additions & 2 deletions planner/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,114 @@ const (
dataLabel = "data"
fieldNameLabel = "fieldName"
filterLabel = "filter"
keysLabel = "_keys"
idsLabel = "ids"
joinRootLabel = "root"
joinSubTypeLabel = "subType"
keysLabel = "_keys"
limitLabel = "limit"
offsetLabel = "offset"
sourcesLabel = "sources"
spansLabel = "spans"
)

// buildDebugExplainGraph dumps the entire plan graph as is, with all the plan nodes.
//
// Note: This also includes plan nodes that aren't "explainable".
func buildDebugExplainGraph(source planNode) (map[string]any, error) {
explainGraph := map[string]any{}

if source == nil {
return explainGraph, nil
}

switch node := source.(type) {
// Walk the multiple children if it is a MultiNode.
case MultiNode:
multiChildExplainGraph := []map[string]any{}
for _, childSource := range node.Children() {
childExplainGraph, err := buildDebugExplainGraph(childSource)
if err != nil {
return nil, err
}
multiChildExplainGraph = append(multiChildExplainGraph, childExplainGraph)
}
nodeLabelTitle := strcase.ToLowerCamel(node.Kind())
explainGraph[nodeLabelTitle] = multiChildExplainGraph

case *typeJoinMany:
var explainGraphBuilder = map[string]any{}

// If root is not the last child then keep walking and explaining the root graph.
if node.root != nil {
indexJoinRootExplainGraph, err := buildDebugExplainGraph(node.root)
if err != nil {
return nil, err
}
// Add the explaination of the rest of the explain graph under the "root" graph.
explainGraphBuilder[joinRootLabel] = indexJoinRootExplainGraph
}

if node.subType != nil {
indexJoinSubTypeExplainGraph, err := buildDebugExplainGraph(node.subType)
if err != nil {
return nil, err
}
// Add the explaination of the rest of the explain graph under the "subType" graph.
explainGraphBuilder[joinSubTypeLabel] = indexJoinSubTypeExplainGraph
}

nodeLabelTitle := strcase.ToLowerCamel(node.Kind())
explainGraph[nodeLabelTitle] = explainGraphBuilder

case *typeJoinOne:
var explainGraphBuilder = map[string]any{}

// If root is not the last child then keep walking and explaining the root graph.
if node.root != nil {
indexJoinRootExplainGraph, err := buildDebugExplainGraph(node.root)
if err != nil {
return nil, err
}
// Add the explaination of the rest of the explain graph under the "root" graph.
explainGraphBuilder[joinRootLabel] = indexJoinRootExplainGraph
} else {
explainGraphBuilder[joinRootLabel] = nil
}

if node.subType != nil {
indexJoinSubTypeExplainGraph, err := buildDebugExplainGraph(node.subType)
if err != nil {
return nil, err
}
// Add the explaination of the rest of the explain graph under the "subType" graph.
explainGraphBuilder[joinSubTypeLabel] = indexJoinSubTypeExplainGraph
} else {
explainGraphBuilder[joinSubTypeLabel] = nil
}

nodeLabelTitle := strcase.ToLowerCamel(node.Kind())
explainGraph[nodeLabelTitle] = explainGraphBuilder

default:
var explainGraphBuilder = map[string]any{}

// If not the last child then keep walking the graph to find more plan nodes.
// Also make sure the next source / child isn't a recursive `topLevelNode`.
if next := node.Source(); next != nil && next.Kind() != topLevelNodeKind {
var err error
explainGraphBuilder, err = buildDebugExplainGraph(next)
if err != nil {
return nil, err
}
}
// Add the graph of the next node under current node.
nodeLabelTitle := strcase.ToLowerCamel(node.Kind())
explainGraph[nodeLabelTitle] = explainGraphBuilder
}

return explainGraph, nil
}

// buildSimpleExplainGraph builds the explainGraph from the given top level plan.
//
// Request:
Expand Down Expand Up @@ -135,7 +235,7 @@ func buildSimpleExplainGraph(source planNode) (map[string]any, error) {
return nil, err
}
// Add the explaination of the rest of the explain graph under the "root" graph.
indexJoinGraph["root"] = indexJoinRootExplainGraph
indexJoinGraph[joinRootLabel] = indexJoinRootExplainGraph
}
// Add this restructured typeIndexJoin explain graph.
explainGraph[strcase.ToLowerCamel(node.Kind())] = indexJoinGraph
Expand Down Expand Up @@ -346,6 +446,22 @@ func (p *Planner) explainRequest(

return explainResult, nil

case request.DebugExplain:
// walks through the plan graph, and outputs the concrete planNodes that should
// be executed, maintaining their order in the plan graph (does not actually execute them).
explainGraph, err := buildDebugExplainGraph(plan)
if err != nil {
return nil, err
}

explainResult := []map[string]any{
{
request.ExplainLabel: explainGraph,
},
}

return explainResult, nil

case request.ExecuteExplain:
return p.executeAndExplainRequest(ctx, plan)

Expand Down
1 change: 0 additions & 1 deletion planner/type_join.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ func (n *typeIndexJoin) simpleExplain() (map[string]any, error) {
joinDirectionLabel = "direction"
joinDirectionPrimaryLabel = "primary"
joinDirectionSecondaryLabel = "secondary"
joinSubTypeLabel = "subType"
joinSubTypeNameLabel = "subTypeName"
joinRootLabel = "rootName"
)
Expand Down
3 changes: 3 additions & 0 deletions request/graphql/parser/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ func parseExplainDirective(astDirective *ast.Directive) (immutable.Option[reques
case schemaTypes.ExplainArgExecute:
return immutable.Some(request.ExecuteExplain), nil

case schemaTypes.ExplainArgDebug:
return immutable.Some(request.DebugExplain), nil

default:
return immutable.None[request.ExplainType](), ErrUnknownExplainType
}
Expand Down
6 changes: 6 additions & 0 deletions request/graphql/schema/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const (
ExplainArgNameType string = "type"
ExplainArgSimple string = "simple"
ExplainArgExecute string = "execute"
ExplainArgDebug string = "debug"
)

var (
Expand Down Expand Up @@ -53,6 +54,11 @@ var (
Value: ExplainArgExecute,
Description: "Deeper explaination - insights gathered by executing the plan graph.",
},

ExplainArgDebug: &gql.EnumValueConfig{
Value: ExplainArgDebug,
Description: "Like simple explain, but more verbose nodes (no attributes).",
},
},
})

Expand Down
41 changes: 41 additions & 0 deletions tests/integration/explain/debug/basic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 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_debug

import (
"testing"

testUtils "github.com/sourcenetwork/defradb/tests/integration"
explainUtils "github.com/sourcenetwork/defradb/tests/integration/explain"
)

func TestDebugExplainRequest(t *testing.T) {
test := testUtils.TestCase{
Description: "Explain (debug) a basic request, assert full graph.",

Actions: []any{
explainUtils.SchemaForExplainTests,

testUtils.ExplainRequest{
Request: `query @explain(type: debug) {
Author {
name
age
}
}`,

ExpectedFullGraph: []dataMap{basicPattern},
},
},
}

explainUtils.ExecuteTestCase(t, test)
}
78 changes: 78 additions & 0 deletions tests/integration/explain/debug/create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// 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_debug

import (
"testing"

testUtils "github.com/sourcenetwork/defradb/tests/integration"
explainUtils "github.com/sourcenetwork/defradb/tests/integration/explain"
)

var createPattern = dataMap{
"explain": dataMap{
"createNode": dataMap{
"selectTopNode": dataMap{
"selectNode": dataMap{
"scanNode": dataMap{},
},
},
},
},
}

func TestDebugExplainMutationRequestWithCreate(t *testing.T) {
test := testUtils.TestCase{
Description: "Explain (debug) mutation request with create.",

Actions: []any{
explainUtils.SchemaForExplainTests,

testUtils.ExplainRequest{

Request: `mutation @explain(type: debug) {
create_Author(data: "{\"name\": \"Shahzad Lone\",\"age\": 27,\"verified\": true}") {
name
age
}
}`,

ExpectedPatterns: []dataMap{createPattern},
},
},
}

explainUtils.ExecuteTestCase(t, test)
}

func TestDebugExplainMutationRequestDoesNotCreateDocGivenDuplicate(t *testing.T) {
test := testUtils.TestCase{
Description: "Explain (debug) mutation request with create, document exists.",

Actions: []any{
explainUtils.SchemaForExplainTests,

testUtils.ExplainRequest{

Request: `mutation @explain(type: debug) {
create_Author(data: "{\"name\": \"Shahzad Lone\",\"age\": 27}") {
name
age
}
}`,

ExpectedPatterns: []dataMap{createPattern},
},
},
}

explainUtils.ExecuteTestCase(t, test)
}
Loading

0 comments on commit b830614

Please sign in to comment.