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 the response plan. #385

Merged
merged 24 commits into from
Jun 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0717914
wip: Add parsing of the `@explain` directive successfully.
shahzadlone May 20, 2022
a9e2de6
wip: Add explaining implementation using Kind() method (no attributes…
shahzadlone May 20, 2022
c728829
wip: Add ability to return the explain result in JSON.
shahzadlone May 20, 2022
e5b6b75
wip: Add integration tests for explaining `topSelectNode`, `selectNod…
shahzadlone May 20, 2022
0d06435
wip: Adhere to the review suggestions #1.
shahzadlone May 19, 2022
2af878e
wip: Fix `valuesNode` to satisfy the `planNode` interface.
shahzadlone May 20, 2022
2e86e6e
wip: Fix unit test to adhere to the plain key-able JSON style.
shahzadlone May 20, 2022
a1ed8b5
wip: Remove anon-structs and use nano-sub package for types.
shahzadlone May 20, 2022
f0444b6
wip: Adhere to the review suggestions #2.
shahzadlone May 20, 2022
79e49b6
wip: Add ability to explain `deleteNode` and handle errors for
shahzadlone May 22, 2022
fbe20f8
wip: Add deleteNode explain support and test deleteNode and
shahzadlone May 24, 2022
6ede865
wip: Convert if statements to a type-switch.
shahzadlone May 24, 2022
eea17ba
wip: Add ability to explain `typeIndexJoin` nodes.
shahzadlone May 25, 2022
3ab25c8
wip: Handle wrapping plan.Close() error instead of logging it.
shahzadlone May 25, 2022
68ad962
wip: Implement all the explainable nodes and move them all to their
shahzadlone May 26, 2022
32d4d08
wip: Handle wrapping errors of `executeRequest` function with a helper
shahzadlone May 26, 2022
3da94c5
wip: Link issue number to todos or the ones that aren't needed.
shahzadlone May 27, 2022
5229476
wip: Adhere to code-review #3
shahzadlone May 27, 2022
a2cb0fd
wip: Keep the plan nil checks inside the inner functions.
shahzadlone May 31, 2022
de07728
wip: type alias `dataMap` to `map[string]interface{}` in tests.
shahzadlone May 31, 2022
cc4b5df
wip: Leave the types inside planner package rather than `types/types.…
shahzadlone May 31, 2022
d940f7b
wip: Fix #486, wrapped scanNode bug by making multiscanNode adhere to
shahzadlone May 31, 2022
2a45eef
wip: Make a `parseExplainDirective` function that iterates through all
shahzadlone Jun 1, 2022
ea2c0cb
wip: Support return of a `nil` from Explain to signal empty attributes.
shahzadlone Jun 1, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,18 @@ cross-build:
start: build
./build/defradb start

.PHONY: dump
dump: build
.PHONY: dev\:start
dev\:start: build
DEFRA_ENV=dev ./build/defradb start

.PHONY: client\:dump
client\:dump:
./build/defradb client dump

.PHONY: client\:add-schema
client\:add-schema:
./build/defradb client schema add -f cli/defradb/examples/bookauthpub.graphql

.PHONY: deps\:lint
deps\:lint:
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b ${GOPATH}/bin latest
Expand Down
6 changes: 3 additions & 3 deletions cli/defradb/examples/address.graphql
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
type address {
street: String
number: Int
city: String
street: String
number: Int
city: String
country: String
}
20 changes: 20 additions & 0 deletions cli/defradb/examples/bookauthpub.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
type book {
name: String
rating: Float
author: author
publisher: publisher
}

type author {
name: String
age: Int
verified: Boolean
wrote: book @primary
}

type publisher {
name: String
address: String
favouritePageNumbers: [Int]
published: [book]
}
3 changes: 1 addition & 2 deletions cli/defradb/examples/booksmany.graphql
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

type book {
name: String
rating: Float
Expand All @@ -10,4 +9,4 @@ type author {
age: Int
verified: Boolean
published: [book]
}
}
4 changes: 2 additions & 2 deletions cli/defradb/examples/booksone.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ type author {
name: String
age: Int
verified: Boolean
published: book
}
published: book
}
8 changes: 4 additions & 4 deletions cli/defradb/examples/user.graphql
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
type user {
name: String
age: Int
verified: Boolean
points: Float
name: String
age: Int
verified: Boolean
points: Float
}
4 changes: 2 additions & 2 deletions client/descriptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ func (col CollectionDescription) GetField(name string) (FieldDescription, bool)
return FieldDescription{}, false
}

func (c CollectionDescription) GetPrimaryIndex() IndexDescription {
return c.Indexes[0]
func (col CollectionDescription) GetPrimaryIndex() IndexDescription {
return col.Indexes[0]
}

// IndexDescription describes an Index on a Collection
Expand Down
6 changes: 3 additions & 3 deletions db/container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ func (c *DocumentContainer) AddDoc(doc map[string]interface{}) error {
return nil
}
// append to docs slice
copyDoc := copyMap(doc)
c.docs = append(c.docs, copyDoc)
c.docs = append(c.docs, copyMap(doc))
c.numDocs++
return nil
}
Expand All @@ -70,9 +69,10 @@ func (c *DocumentContainer) Swap(i, j int) {
c.docs[j] = tmp
}

func (c *DocumentContainer) Close() {
func (c *DocumentContainer) Close() error {
c.docs = nil
c.numDocs = 0
return nil
}

func copyMap(m map[string]interface{}) map[string]interface{} {
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ require (
require (
github.com/fatih/color v1.13.0
github.com/go-chi/chi/v5 v5.0.7
github.com/iancoleman/strcase v0.2.0
github.com/pkg/errors v0.9.1
golang.org/x/text v0.3.7
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,8 @@ github.com/huin/goupnp v1.0.2/go.mod h1:0dxJBVBHqTMjIUMkESDTNgOOx/Mw5wYIfyFmdzSa
github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ=
github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y=
github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o=
github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/improbable-eng/grpc-web v0.14.1/go.mod h1:zEjGHa8DAlkoOXmswrNvhUGEYQA9UI7DhrGeHR1DMGU=
Expand Down
13 changes: 9 additions & 4 deletions query/graphql/parser/mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import (
"errors"
"strings"

parserTypes "github.com/sourcenetwork/defradb/query/graphql/parser/types"

"github.com/graphql-go/graphql/language/ast"

parserTypes "github.com/sourcenetwork/defradb/query/graphql/parser/types"
)

type MutationType int
Expand Down Expand Up @@ -134,16 +134,21 @@ func (m Mutation) ToSelect() *Select {
}
}

// parseOperationDefinition parses the individual GraphQL
// 'query' operations, which there may be multiple of.
// parseMutationOperationDefinition parses the individual GraphQL
// 'mutation' operations, which there may be multiple of.
func parseMutationOperationDefinition(def *ast.OperationDefinition) (*OperationDefinition, error) {

qdef := &OperationDefinition{
Statement: def,
Selections: make([]Selection, len(def.SelectionSet.Selections)),
}

if def.Name != nil {
qdef.Name = def.Name.Value
}

qdef.IsExplain = parseExplainDirective(def.Directives)

for i, selection := range qdef.Statement.SelectionSet.Selections {
switch node := selection.(type) {
case *ast.Field:
Expand Down
38 changes: 30 additions & 8 deletions query/graphql/parser/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ func (q Query) GetStatement() ast.Node {
type OperationDefinition struct {
Name string
Selections []Selection

Statement *ast.OperationDefinition
Statement *ast.OperationDefinition
IsExplain bool
shahzadlone marked this conversation as resolved.
Show resolved Hide resolved
}

func (q OperationDefinition) GetStatement() ast.Node {
Expand Down Expand Up @@ -194,17 +194,19 @@ func ParseQuery(doc *ast.Document) (*Query, error) {
Queries: make([]*OperationDefinition, 0),
Mutations: make([]*OperationDefinition, 0),
}

for _, def := range q.Statement.Definitions {
switch node := def.(type) {
case *ast.OperationDefinition:
if node.Operation == "query" {
// parse query or mutation operation definition
// parse query operation definition.
qdef, err := parseQueryOperationDefinition(node)
if err != nil {
return nil, err
}
q.Queries = append(q.Queries, qdef)
} else if node.Operation == "mutation" {
// parse mutation operation definition.
mdef, err := parseMutationOperationDefinition(node)
if err != nil {
return nil, err
Expand All @@ -219,25 +221,45 @@ func ParseQuery(doc *ast.Document) (*Query, error) {
return q, nil
}

// parseOperationDefinition parses the individual GraphQL
// parseExplainDirective returns true if we parsed / detected the explain directive label
// in this ast, and false otherwise.
func parseExplainDirective(directives []*ast.Directive) bool {

// Iterate through all directives and ensure that the directive is at there.
// - Note: the location we don't need to worry about as the schema takes care of it, as when
// request is made there will be a syntax error for directive usage at the wrong location,
// unless we add another directive named `@explain` at another location (which we should not).
for _, directive := range directives {
// The arguments pased to the directive are at `directive.Arguments`.
if directive.Name.Value == parserTypes.ExplainLabel {
return true
}
}

return false
}

// parseQueryOperationDefinition parses the individual GraphQL
// 'query' operations, which there may be multiple of.
func parseQueryOperationDefinition(def *ast.OperationDefinition) (*OperationDefinition, error) {

shahzadlone marked this conversation as resolved.
Show resolved Hide resolved
qdef := &OperationDefinition{
Statement: def,
Selections: make([]Selection, len(def.SelectionSet.Selections)),
}

if def.Name != nil {
qdef.Name = def.Name.Value
}

qdef.IsExplain = parseExplainDirective(def.Directives)

for i, selection := range qdef.Statement.SelectionSet.Selections {
var parsed Selection
var err error
switch node := selection.(type) {
case *ast.Field:
// which query type is this
// database API query
// object query
// etc
// which query type is this database API query object query etc.
_, exists := dbAPIQueryNames[node.Name.Value]
if exists {
// the query matches a reserved DB API query name
Expand Down
2 changes: 2 additions & 0 deletions query/graphql/parser/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ const (
SumFieldName = "_sum"
VersionFieldName = "_version"

ExplainLabel = "explain"
shahzadlone marked this conversation as resolved.
Show resolved Hide resolved

ASC = SortDirection("ASC")
DESC = SortDirection("DESC")
)
Expand Down
7 changes: 7 additions & 0 deletions query/graphql/planner/average.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func (n *averageNode) Init() error {
return n.plan.Init()
}

func (n *averageNode) Kind() string { return "averageNode" }
func (n *averageNode) Start() error { return n.plan.Start() }
func (n *averageNode) Spans(spans core.Spans) { n.plan.Spans(spans) }
func (n *averageNode) Close() error { return n.plan.Close() }
Expand Down Expand Up @@ -90,3 +91,9 @@ func (n *averageNode) Next() (bool, error) {
}

func (n *averageNode) SetPlan(p planNode) { n.plan = p }

// 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 *averageNode) Explain() (map[string]interface{}, error) {
return map[string]interface{}{}, nil
}
65 changes: 39 additions & 26 deletions query/graphql/planner/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,36 @@ import (
"github.com/sourcenetwork/defradb/query/graphql/parser"
)

// commitSelectTopNode is a wrapper for the selectTopNode
// in the case where the select is actually a CommitSelect
type commitSelectTopNode struct {
p *Planner
plan planNode
}

func (n *commitSelectTopNode) Kind() string { return "commitSelectTopNode" }

func (n *commitSelectTopNode) Init() error { return n.plan.Init() }

func (n *commitSelectTopNode) Start() error { return n.plan.Start() }

func (n *commitSelectTopNode) Next() (bool, error) { return n.plan.Next() }

func (n *commitSelectTopNode) Spans(spans core.Spans) { n.plan.Spans(spans) }

func (n *commitSelectTopNode) Value() map[string]interface{} { return n.plan.Value() }

func (n *commitSelectTopNode) Source() planNode { return n.plan }

func (n *commitSelectTopNode) Close() error {
if n.plan == nil {
return nil
}
return n.plan.Close()
}

func (n *commitSelectTopNode) Append() bool { return true }

type commitSelectNode struct {
documentIterator

Expand All @@ -29,6 +59,10 @@ type commitSelectNode struct {
subRenderInfo map[string]renderInfo
}

func (n *commitSelectNode) Kind() string {
return "commitSelectNode"
}

func (n *commitSelectNode) Init() error {
return n.source.Init()
}
Expand Down Expand Up @@ -58,10 +92,11 @@ func (n *commitSelectNode) Source() planNode {
return n.source
}

// AppendNode implements appendNode
// func (n *commitSelectNode) AppendNode() bool {
// return true
// }
// 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 *commitSelectNode) Explain() (map[string]interface{}, error) {
return map[string]interface{}{}, nil
}

func (p *Planner) CommitSelect(parsed *parser.CommitSelect) (planNode, error) {
// check type of commit select (all, latest, one)
Expand Down Expand Up @@ -159,25 +194,3 @@ func (p *Planner) commitSelectAll(parsed *parser.CommitSelect) (*commitSelectNod

return commit, nil
}

// commitSelectTopNode is a wrapper for the selectTopNode
// in the case where the select is actually a CommitSelect
type commitSelectTopNode struct {
p *Planner
plan planNode
}

func (n *commitSelectTopNode) Init() error { return n.plan.Init() }
func (n *commitSelectTopNode) Start() error { return n.plan.Start() }
func (n *commitSelectTopNode) Next() (bool, error) { return n.plan.Next() }
func (n *commitSelectTopNode) Spans(spans core.Spans) { n.plan.Spans(spans) }
func (n *commitSelectTopNode) Value() map[string]interface{} { return n.plan.Value() }
func (n *commitSelectTopNode) Source() planNode { return n.plan }
func (n *commitSelectTopNode) Close() error {
if n.plan == nil {
return nil
}
return n.plan.Close()
}

func (n *commitSelectTopNode) Append() bool { return true }
Loading