| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 | /* * EliasDB * * Copyright 2016 Matthias Ladkau. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */package interpreterimport (	"fmt"	"devt.de/krotik/common/errorutil"	"devt.de/krotik/common/lang/graphql/parser"	"devt.de/krotik/eliasdb/graph")// Runtime definition// ==================/*Instance function for runtime components*/type runtimeInst func(*GraphQLRuntimeProvider, *parser.ASTNode) parser.Runtime/*Runtime map for runtime components*/var runtimeProviderMap = map[string]runtimeInst{	parser.NodeEOF:                  invalidRuntimeInst,	parser.NodeDocument:             documentRuntimeInst,	parser.NodeExecutableDefinition: executableDefinitionRuntimeInst,	parser.NodeFragmentDefinition:   fragmentDefinitionRuntimeInst,	parser.NodeInlineFragment:       inlineFragmentDefinitionRuntimeInst,	parser.NodeOperationDefinition:  operationDefinitionRuntimeInst,	parser.NodeSelectionSet:         selectionSetRuntimeInst,	parser.NodeField:                fieldRuntimeInst,	parser.NodeDirective:            argumentExpressionRuntimeInst,	parser.NodeObjectValue:  valueRuntimeInst,	parser.NodeValue:        valueRuntimeInst,	parser.NodeDefaultValue: valueRuntimeInst,	parser.NodeEnumValue:    valueRuntimeInst,	parser.NodeListValue:    valueRuntimeInst,	parser.NodeVariable:     valueRuntimeInst,}// General runtime provider// ========================/*QueryType is a know GraphQL query type*/type QueryType string/*All known query types*/const (	QueryTypeQuery        QueryType = "query"	QueryTypeMutation               = "mutation"	QueryTypeSubscription           = "subscription")/*GraphQLRuntimeProvider defines the main interpreterdatastructure and all functions for general evaluation.*/type GraphQLRuntimeProvider struct {	Name           string                 // Name to identify the input	QueryType      QueryType              // Query type (query, mutation, subscription)	OperationName  string                 // Name of operation to execute	VariableValues map[string]interface{} // Values of variables	ErrorKeys      []string               // List of error hashes (used for deduplication)	Errors         []*RuntimeError        // List of errors	ErrorPaths     [][]string             // List of error paths	part                string                      // Graph partition to query	gm                  *graph.Manager              // GraphManager to operate on	callbackHandler     SubscriptionCallbackHandler // Subscription callback handler for updates	subscriptionHandler *subscriptionHandler        // Subscription handler forwarding event is the callback object	readOnly  bool                                  // Flag if only read operations are allowed	operation *parser.ASTNode                       // Operation to execute	fragments map[string]*fragmentDefinitionRuntime // Fragment definitions}/*NewGraphQLRuntimeProvider creates a new GraphQLRuntimeProvider object.*/func NewGraphQLRuntimeProvider(name string, part string, gm *graph.Manager,	op string, vars map[string]interface{}, callbackHandler SubscriptionCallbackHandler,	readOnly bool) *GraphQLRuntimeProvider {	return &GraphQLRuntimeProvider{name, "", op, vars, []string{}, []*RuntimeError{},		[][]string{}, part, gm, callbackHandler, nil, readOnly, nil,		make(map[string]*fragmentDefinitionRuntime)}}/*CheckWritePermission checks if the current query is allowed to modify data.Returns true if data can be modified.*/func (rtp *GraphQLRuntimeProvider) CheckWritePermission(path []string, node *parser.ASTNode) bool {	if rtp.readOnly {		rtp.handleRuntimeError(fmt.Errorf("Can only perform read operations"),			path, node)		return false	}	if rtp.QueryType != QueryTypeMutation {		rtp.handleRuntimeError(fmt.Errorf("Operation must be a mutation to modify data"),			path, node)		return false	}	return true}/*Initialise data structures.*/func (rtp *GraphQLRuntimeProvider) init() error {	rtp.QueryType = ""	rtp.operation = nil	return nil}/*Runtime returns a runtime component for a given ASTNode.*/func (rtp *GraphQLRuntimeProvider) Runtime(node *parser.ASTNode) parser.Runtime {	if pinst, ok := runtimeProviderMap[node.Name]; ok {		return pinst(rtp, node)	}	return invalidRuntimeInst(rtp, node)}/*TraverseAST traverses the AST starting with a given root and executes a givenvisitor function on each node. An accumulator is given to track state. A pathis given to track selection sets.*/func (rtp *GraphQLRuntimeProvider) TraverseAST(root *parser.ASTNode,	visitor func(*parser.ASTNode)) {	visitor(root)	for _, child := range root.Children {		rtp.TraverseAST(child, visitor)	}}// Document Runtime// ================type documentRuntime struct {	rtp  *GraphQLRuntimeProvider	node *parser.ASTNode}/*documentRuntimeInst creates a new document runtime instance.*/func documentRuntimeInst(rtp *GraphQLRuntimeProvider, node *parser.ASTNode) parser.Runtime {	return &documentRuntime{rtp, node}}/* Validate and reset this runtime component and all its child components.*/func (rt *documentRuntime) Validate() error {	err := rt.rtp.init()	for _, c := range rt.node.Children {		if err == nil {			err = c.Runtime.Validate()		}	}	if rt.rtp.operation == nil {		// We didn't find an operation to execute		if rt.rtp.OperationName == "" {			err = rt.rtp.newFatalRuntimeError(ErrMissingOperation,				"No executable expression found", rt.node)		} else {			err = rt.rtp.newFatalRuntimeError(ErrMissingOperation,				fmt.Sprintf("Operation %s not found", rt.rtp.OperationName), rt.node)		}	}	if err == nil && rt.rtp.QueryType == "" {		rt.rtp.QueryType = QueryTypeQuery	}	if err == nil {		// Check variables - types are not checked		ort := rt.rtp.operation.Runtime.(*operationDefinitionRuntime)		declared, defaultValues, _ := ort.DeclaredVariables()		// Build up variable values		vals := rt.rtp.VariableValues		rt.rtp.VariableValues = make(map[string]interface{})		for _, name := range declared {			val, ok := vals[name]			if ok {				rt.rtp.VariableValues[name] = val			} else {				rt.rtp.VariableValues[name] = defaultValues[name]			}		}	}	if err == nil {		// Collect fragment definitions		rt.rtp.TraverseAST(rt.node, func(n *parser.ASTNode) {			if err == nil && n.Name == parser.NodeFragmentDefinition {				fr := n.Runtime.(*fragmentDefinitionRuntime)				if _, ok := rt.rtp.fragments[fr.Name()]; ok {					err = rt.rtp.newFatalRuntimeError(ErrAmbiguousDefinition,						fmt.Sprintf("Fragment %s defined multiple times",							fr.Name()), rt.node)				}				if err == nil {					rt.rtp.fragments[fr.Name()] = fr				}			}		})		if err == nil {			// Validate that all fragment spreads can be resolved			rt.rtp.TraverseAST(rt.node, func(n *parser.ASTNode) {				if err == nil && n.Name == parser.NodeFragmentSpread {					name := n.Token.Val					if _, ok := rt.rtp.fragments[name]; !ok {						err = rt.rtp.newFatalRuntimeError(ErrInvalidConstruct,							fmt.Sprintf("Fragment %s is not defined",								name), rt.node)					}				}			})		}	}	return err}/*Eval evaluate this runtime component.*/func (rt *documentRuntime) Eval() (map[string]interface{}, error) {	var err error	// First validate the query and reset the runtime provider datastructures	if rt.rtp.QueryType == "" {		if err = rt.Validate(); err != nil {			return nil, err		}	}	// Validate must have found the query type and the operation to execute	errorutil.AssertTrue(rt.rtp.QueryType != "", "Unknown querytype")	errorutil.AssertTrue(rt.rtp.operation != nil, "Unknown operation")	if rt.rtp.QueryType == QueryTypeSubscription && rt.rtp.callbackHandler != nil {		rt.rtp.InitSubscription(rt)	}	return rt.rtp.operation.Runtime.Eval()}// ExecutableDefinition Runtime// ============================type executableDefinitionRuntime struct {	*invalidRuntime	rtp  *GraphQLRuntimeProvider	node *parser.ASTNode}/*executableDefinitionRuntimeInst creates a new document runtime instance.*/func executableDefinitionRuntimeInst(rtp *GraphQLRuntimeProvider, node *parser.ASTNode) parser.Runtime {	return &executableDefinitionRuntime{&invalidRuntime{rtp, node}, rtp, node}}/* Validate and reset this runtime component and all its child components.*/func (rt *executableDefinitionRuntime) Validate() error {	if rt.rtp.operation == nil {		// Choose an operation to execute		if rt.node.Children[0].Name == parser.NodeOperationDefinition {			if rt.rtp.OperationName == "" {				// No operation name defined - take the first available operation				rt.rtp.operation = rt.node.Children[0]				// Check the operation type				if rt.node.Children[0].Children[0].Name == parser.NodeOperationType {					if rt.node.Children[0].Children[0].Token.Val == "mutation" {						rt.rtp.QueryType = QueryTypeMutation					} else if rt.node.Children[0].Children[0].Token.Val == "subscription" {						rt.rtp.QueryType = QueryTypeSubscription					}				}			} else {				// If an operation name is defined we must not have a query shorthand				if rt.node.Children[0].Children[0].Name == parser.NodeOperationType {					name := rt.node.Children[0].Children[1].Token.Val					if rt.rtp.OperationName == name {						// We found the operation to execture						if rt.node.Children[0].Children[0].Name == parser.NodeOperationType {							// See what type it is							if rt.node.Children[0].Children[0].Token.Val == "mutation" {								rt.rtp.QueryType = QueryTypeMutation							} else if rt.node.Children[0].Children[0].Token.Val == "subscription" {								rt.rtp.QueryType = QueryTypeSubscription							}						}						rt.rtp.operation = rt.node.Children[0]					}				}			}		}	}	return nil}// OperationDefinition Runtime// ============================type operationDefinitionRuntime struct {	*invalidRuntime	rtp  *GraphQLRuntimeProvider	node *parser.ASTNode}/*operationDefinitionRuntimeInst creates a new operation definition runtime instance.*/func operationDefinitionRuntimeInst(rtp *GraphQLRuntimeProvider, node *parser.ASTNode) parser.Runtime {	return &operationDefinitionRuntime{&invalidRuntime{rtp, node}, rtp, node}}/*Eval evaluate this runtime component.*/func (rt *operationDefinitionRuntime) Eval() (map[string]interface{}, error) {	res := make(map[string]interface{})	// Execute the selection set	data, err := rt.node.Children[len(rt.node.Children)-1].Runtime.Eval()	res["data"] = data	// Collect errors	resErr := make([]map[string]interface{}, 0)	for i, rterr := range rt.rtp.Errors {		resErr = append(resErr, map[string]interface{}{			"message": rterr.Detail,			"locations": []map[string]interface{}{				{					"line":   rterr.Line,					"column": rterr.Pos,				},			},			"path": rt.rtp.ErrorPaths[i],		})	}	if len(resErr) > 0 {		// Only add errors if there are any (@spec 7.1.2)		res["errors"] = resErr	}	return res, err}/*DeclaredVariables returns all declared variables as list and their defaultvalues (if defined) and their type as maps.*/func (rt *operationDefinitionRuntime) DeclaredVariables() ([]string, map[string]interface{}, map[string]string) {	declared := make([]string, 0)	defValues := make(map[string]interface{})	types := make(map[string]string)	for _, c := range rt.node.Children {		if c.Name == parser.NodeVariableDefinitions {			for _, vardef := range c.Children {				name := vardef.Children[0].Token.Val				declared = append(declared, name)				if len(vardef.Children) > 2 {					defValues[name] = vardef.Children[2].Runtime.(*valueRuntime).Value()				}				types[name] = vardef.Children[1].Token.Val			}		}	}	return declared, defValues, types}// FragmentDefinition Runtime// ==========================type fragmentDefinitionRuntime struct {	*invalidRuntime	rtp  *GraphQLRuntimeProvider	node *parser.ASTNode}/*fragmentDefinitionRuntimeInst creates a new fragment definition runtime instance.*/func fragmentDefinitionRuntimeInst(rtp *GraphQLRuntimeProvider, node *parser.ASTNode) parser.Runtime {	return &fragmentDefinitionRuntime{&invalidRuntime{rtp, node}, rtp, node}}/*Name returns the name of the fragment definition.*/func (rt *fragmentDefinitionRuntime) Name() string {	return rt.node.Children[0].Token.Val}/*TypeCondition returns the type condition of the fragment definition.*/func (rt *fragmentDefinitionRuntime) TypeCondition() string {	return rt.node.Children[1].Token.Val}/*SelectionSet returns the selection set of the fragment definition.*/func (rt *fragmentDefinitionRuntime) SelectionSet() *parser.ASTNode {	return rt.node.Children[len(rt.node.Children)-1]}// InlineFragmentDefinition Runtime// ================================type inlineFragmentDefinitionRuntime struct {	*invalidRuntime	rtp  *GraphQLRuntimeProvider	node *parser.ASTNode}/*fragmentDefinitionRuntimeInst creates a new inline fragment definition runtime instance.*/func inlineFragmentDefinitionRuntimeInst(rtp *GraphQLRuntimeProvider, node *parser.ASTNode) parser.Runtime {	return &inlineFragmentDefinitionRuntime{&invalidRuntime{rtp, node}, rtp, node}}/*TypeCondition returns the type condition of the inline fragment definition.*/func (rt *inlineFragmentDefinitionRuntime) TypeCondition() string {	return rt.node.Children[0].Token.Val}/*SelectionSet returns the selection set of the inline fragment definition.*/func (rt *inlineFragmentDefinitionRuntime) SelectionSet() *parser.ASTNode {	return rt.node.Children[len(rt.node.Children)-1]}
 |