Skip to content

Commit

Permalink
feat: Add ability to input simple explain type arg (sourcenetwork#1039)
Browse files Browse the repository at this point in the history
- Resolves sourcenetwork#972 

- Description: This PR lays the groundwork that was missing to push the work that implements the other remaining explain types.

- Usage:
```
@Explain
@Explain(type: simple)
```
  • Loading branch information
shahzadlone authored Jan 21, 2023
1 parent 44e8d17 commit 62c1307
Show file tree
Hide file tree
Showing 39 changed files with 571 additions and 276 deletions.
19 changes: 19 additions & 0 deletions client/request/explain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// 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 request

// ExplainType does not represent which type is currently the default explain request type.
type ExplainType string

// Types of explain requests.
const (
SimpleExplain ExplainType = "simple"
)
15 changes: 14 additions & 1 deletion client/request/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@

package request

import (
"github.com/sourcenetwork/immutable"
)

type Request struct {
Queries []*OperationDefinition
Mutations []*OperationDefinition
Expand All @@ -18,7 +22,16 @@ type Request struct {

type Selection any

// Directives contains all the optional and non-optional
// directives (and their additional data) that a request can have.
//
// An optional directive has a value if it's found in the request.
type Directives struct {
// ExplainType is an optional directive (`@explain`) and it's type information.
ExplainType immutable.Option[ExplainType]
}

type OperationDefinition struct {
Selections []Selection
IsExplain bool
Directives Directives
}
9 changes: 8 additions & 1 deletion planner/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ package planner
import "github.com/sourcenetwork/defradb/errors"

const (
errUnknownDependency string = "The given field does not exist"
errUnknownDependency string = "given field does not exist"
errFailedToClosePlan string = "failed to close the plan"
)

var (
Expand All @@ -22,13 +23,19 @@ var (
ErrMissingQueryOrMutation = errors.New("query is missing query or mutation statements")
ErrOperationDefinitionMissingSelection = errors.New("operationDefinition is missing selections")
ErrFailedToFindGroupSource = errors.New("failed to identify group source")
ErrCantExplainSubscriptionRequest = errors.New("can not explain a subscription request")
ErrGroupOutsideOfGroupBy = errors.New("_group may only be referenced when within a groupBy query")
ErrMissingChildSelect = errors.New("expected child select but none was found")
ErrMissingChildValue = errors.New("expected child value, however none was yielded")
ErrUnknownRelationType = errors.New("failed sub selection, unknown relation type")
ErrUnknownExplainRequestType = errors.New("can not explain request of unknown type")
ErrUnknownDependency = errors.New(errUnknownDependency)
)

func NewErrUnknownDependency(name string) error {
return errors.New(errUnknownDependency, errors.NewKV("Name", name))
}

func NewErrFailedToClosePlan(inner error, location string) error {
return errors.Wrap(errFailedToClosePlan, inner, errors.NewKV("Location", location))
}
12 changes: 6 additions & 6 deletions planner/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const (
spansLabel = "spans"
)

// buildExplainGraph builds the explainGraph from the given top level plan.
// buildSimpleExplainGraph builds the explainGraph from the given top level plan.
//
// Request:
// query @explain {
Expand Down Expand Up @@ -80,7 +80,7 @@ const (
// }
// ]
// }
func buildExplainGraph(source planNode) (map[string]any, error) {
func buildSimpleExplainGraph(source planNode) (map[string]any, error) {
explainGraph := map[string]any{}

if source == nil {
Expand All @@ -94,7 +94,7 @@ func buildExplainGraph(source planNode) (map[string]any, error) {
// List to store all explain graphs of explainable children of MultiNode.
multiChildExplainGraph := []map[string]any{}
for _, childSource := range node.Children() {
childExplainGraph, err := buildExplainGraph(childSource)
childExplainGraph, err := buildSimpleExplainGraph(childSource)
if err != nil {
return nil, err
}
Expand All @@ -116,7 +116,7 @@ func buildExplainGraph(source planNode) (map[string]any, error) {
// If not the last child then keep walking and explaining the root graph,
// as long as there are more explainable nodes left under root.
if node.Source() != nil {
indexJoinRootExplainGraph, err := buildExplainGraph(node.Source())
indexJoinRootExplainGraph, err := buildSimpleExplainGraph(node.Source())
if err != nil {
return nil, err
}
Expand All @@ -142,7 +142,7 @@ func buildExplainGraph(source planNode) (map[string]any, error) {
// If not the last child then keep walking the graph to find more explainable nodes.
// 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)
nextExplainGraph, err := buildSimpleExplainGraph(next)
if err != nil {
return nil, err
}
Expand All @@ -158,7 +158,7 @@ func buildExplainGraph(source planNode) (map[string]any, error) {
default:
// Node is neither a MultiNode nor an "explainable" node. Skip over it but walk it's child(ren).
var err error
explainGraph, err = buildExplainGraph(source.Source())
explainGraph, err = buildSimpleExplainGraph(source.Source())
if err != nil {
return nil, err
}
Expand Down
102 changes: 54 additions & 48 deletions planner/planner.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@ package planner

import (
"context"
"fmt"

"github.com/sourcenetwork/defradb/client"
"github.com/sourcenetwork/defradb/client/request"
"github.com/sourcenetwork/defradb/core"
"github.com/sourcenetwork/defradb/datastore"
"github.com/sourcenetwork/defradb/errors"
"github.com/sourcenetwork/defradb/logging"
"github.com/sourcenetwork/defradb/planner/mapper"
)
Expand Down Expand Up @@ -445,19 +443,26 @@ func walkAndFindPlanType[T planNode](plan planNode) (T, bool) {
func (p *Planner) explainRequest(
ctx context.Context,
plan planNode,
explainType request.ExplainType,
) ([]map[string]any, error) {
explainGraph, err := buildExplainGraph(plan)
if err != nil {
return nil, multiErr(err, plan.Close())
}
switch explainType {
case request.SimpleExplain:
explainGraph, err := buildSimpleExplainGraph(plan)
if err != nil {
return nil, err
}

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

return topExplainGraph, plan.Close()
return explainResult, nil

default:
return nil, ErrUnknownExplainRequestType
}
}

// executeRequest executes the plan graph that represents the request that was made.
Expand All @@ -466,12 +471,12 @@ func (p *Planner) executeRequest(
plan planNode,
) ([]map[string]any, error) {
if err := plan.Start(); err != nil {
return nil, multiErr(err, plan.Close())
return nil, err
}

next, err := plan.Next()
if err != nil {
return nil, multiErr(err, plan.Close())
return nil, err
}

docs := []map[string]any{}
Expand All @@ -483,70 +488,71 @@ func (p *Planner) executeRequest(

next, err = plan.Next()
if err != nil {
return nil, multiErr(err, plan.Close())
return nil, err
}
}

if err = plan.Close(); err != nil {
return nil, err
}

return docs, err
}

// RunRequest plans how to run the request, then attempts to run the request and returns the results.
// RunRequest classifies the type of request to run, runs it, and then returns the result(s).
func (p *Planner) RunRequest(
ctx context.Context,
query *request.Request,
) ([]map[string]any, error) {
plan, err := p.makePlan(query)

req *request.Request,
) (result []map[string]any, err error) {
plan, err := p.makePlan(req)
if err != nil {
return nil, err
}

isAnExplainRequest :=
(len(query.Queries) > 0 && query.Queries[0].IsExplain) ||
(len(query.Mutations) > 0 && query.Mutations[0].IsExplain)
defer func() {
if e := plan.Close(); e != nil {
err = NewErrFailedToClosePlan(e, "running request")
}
}()

// Ensure subscription request doesn't ever end up with an explain directive.
if len(req.Subscription) > 0 && req.Subscription[0].Directives.ExplainType.HasValue() {
return nil, ErrCantExplainSubscriptionRequest
}

if isAnExplainRequest {
return p.explainRequest(ctx, plan)
if len(req.Queries) > 0 && req.Queries[0].Directives.ExplainType.HasValue() {
return p.explainRequest(ctx, plan, req.Queries[0].Directives.ExplainType.Value())
}

// This won't execute if it's an explain request.
if len(req.Mutations) > 0 && req.Mutations[0].Directives.ExplainType.HasValue() {
return p.explainRequest(ctx, plan, req.Mutations[0].Directives.ExplainType.Value())
}

// This won't / should NOT execute if it's any kind of explain request.
return p.executeRequest(ctx, plan)
}

// RunSubscriptionRequest plans a request specific to a subscription and returns the result.
func (p *Planner) RunSubscriptionRequest(
ctx context.Context,
query *request.Select,
) ([]map[string]any, error) {
) (result []map[string]any, err error) {
plan, err := p.makePlan(query)
if err != nil {
return nil, err
}

defer func() {
if e := plan.Close(); e != nil {
err = NewErrFailedToClosePlan(e, "running subscription request")
}
}()

return p.executeRequest(ctx, plan)
}

// MakePlan makes a plan from the parsed query. @TODO {defradb/issues/368}: Test this exported function.
// MakePlan makes a plan from the parsed query.
//
// Note: Caller is responsible to call the `Close()` method to free the allocated
// resources of the returned plan.
//
// @TODO {defradb/issues/368}: Test this exported function.
func (p *Planner) MakePlan(query *request.Request) (planNode, error) {
return p.makePlan(query)
}

// multiErr wraps all the non-nil errors and returns the wrapped error result.
func multiErr(errorsToWrap ...error) error {
var errs error
for _, err := range errorsToWrap {
if err == nil {
continue
}
if errs == nil {
errs = errors.New(err.Error())
continue
}
errs = errors.Wrap(fmt.Sprintf("%s", errs), err)
}
return errs
}
5 changes: 2 additions & 3 deletions planner/type_join.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ func (p *Planner) makeTypeIndexJoin(
desc := parent.sourceInfo.collectionDescription
typeFieldDesc, ok := desc.GetField(subType.Name)
if !ok {
// return nil, errors.Wrap("Unknown field on sub selection")
return nil, client.NewErrFieldNotExist(subType.Name)
}

Expand Down Expand Up @@ -162,7 +161,7 @@ func (n *typeIndexJoin) Explain() (map[string]any, error) {
explainerMap[joinRootLabel] = joinType.subTypeFieldName
explainerMap[joinSubTypeNameLabel] = joinType.subTypeName

subTypeExplainGraph, err := buildExplainGraph(joinType.subType)
subTypeExplainGraph, err := buildSimpleExplainGraph(joinType.subType)
if err != nil {
return nil, err
}
Expand All @@ -175,7 +174,7 @@ func (n *typeIndexJoin) Explain() (map[string]any, error) {
explainerMap[joinRootLabel] = joinType.rootName
explainerMap[joinSubTypeNameLabel] = joinType.subTypeName

subTypeExplainGraph, err := buildExplainGraph(joinType.subType)
subTypeExplainGraph, err := buildSimpleExplainGraph(joinType.subType)
if err != nil {
return nil, err
}
Expand Down
3 changes: 3 additions & 0 deletions query/graphql/parser/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,8 @@ var (
ErrFailedToParseConditionValue = errors.New("failed to parse condition value from query filter statement")
ErrEmptyDataPayload = errors.New("given data payload is empty")
ErrUnknownMutationName = errors.New("unknown mutation name")
ErrInvalidExplainTypeArg = errors.New("invalid explain request type argument")
ErrInvalidNumberOfExplainArgs = errors.New("invalid number of arguments to an explain request")
ErrUnknownExplainType = errors.New("invalid / unknown explain type")
ErrUnknownGQLOperation = errors.New("unknown GraphQL operation type")
)
2 changes: 0 additions & 2 deletions query/graphql/parser/mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ func parseMutationOperationDefinition(
Selections: make([]request.Selection, len(def.SelectionSet.Selections)),
}

qdef.IsExplain = parseExplainDirective(def.Directives)

for i, selection := range def.SelectionSet.Selections {
switch node := selection.(type) {
case *ast.Field:
Expand Down
Loading

0 comments on commit 62c1307

Please sign in to comment.