Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add ability to explain-debug all nodes #1563

Merged
merged 14 commits into from
Jun 13, 2023
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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (out of scope): These are all public, and I think they should live in client/request/explain.go - might be worth adding a ticket if you agree.

Same also goes for the attribute tags and node names (if they are in planner atm)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate this suggestion! Would be much nicer. Will open a ticket.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ticket: #1571

)

// 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