/* * 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 interpreter import ( "fmt" "strconv" "strings" "devt.de/krotik/eliasdb/eql/parser" "devt.de/krotik/eliasdb/graph" "devt.de/krotik/eliasdb/graph/data" ) /* allowMultiEval allows multiple calls to eval of runtime components without resetting state (used for testing) */ var allowMultiEval = false // Special flags which can be set by with statements type withFlags struct { ordering []byte // Result ordering orderingCol []int // Columns which should be ordered notnullCol []int // Columns which must not be null uniqueCol []int // Columns which will only contain unique values uniqueColCnt []bool // Flag if unique values should be counted } const ( withOrderingAscending = 0x1 withOrderingDescending = 0x2 ) /* GroupNodeKind is a special group node kind */ const GroupNodeKind = "group" // General runtime provider // ======================== /* eqlRuntimeProvider defines the main interpreter datastructure and all functions for general evaluation. */ type eqlRuntimeProvider struct { name string // Name to identify the input part string // Graph partition to query gm *graph.Manager // GraphManager to operate on ni NodeInfo // NodeInfo to use for formatting groupScope string // Group scope for query allowNilTraversal bool // Flag if empty traversals should be included in the result withFlags *withFlags // Special flags which can be set by with statements primaryKind string // Primary node kind nextStartKey func() (string, error) // Function to get the next start key traversals []*parser.ASTNode // Array of all top level query traversals where *parser.ASTNode // First where clause show *parser.ASTNode // Show clause node specs []string // Flat list of traversals of this query attrsNodes []map[string]string // Attributes for nodes to query on each traversal attrsEdges []map[string]string // Attributes for nodes to query on each traversal rowNode []data.Node // Current row of nodes which is evaluated rowEdge []data.Edge // Current row of edges which is evaluated colLabels []string // Labels for columns colFormat []string // Format for columns colData []string // Data for columns colFunc []FuncShow // Function to transform column value _attrsNodesFetch [][]string // Internal copy of attrsNodes better suited for fetchPart calls _attrsEdgesFetch [][]string // Internal copy of attrsEdges better suited for fetchPart calls } /* Initialise and validate data structures. */ func (p *eqlRuntimeProvider) init(startKind string, rootChildren []*parser.ASTNode) error { // By default we don't include empty traversals in the result p.allowNilTraversal = false // Clear any with flags p.withFlags = &withFlags{make([]byte, 0), make([]int, 0), make([]int, 0), make([]int, 0), make([]bool, 0)} // Reinitialise datastructures p.groupScope = "" p.traversals = make([]*parser.ASTNode, 0) p.where = nil p.show = nil p.specs = make([]string, 0) p.attrsNodes = make([]map[string]string, 0) p.attrsEdges = make([]map[string]string, 0) p.rowNode = nil p.rowEdge = nil p._attrsNodesFetch = nil p._attrsEdgesFetch = nil p.colLabels = make([]string, 0) p.colFormat = make([]string, 0) p.colData = make([]string, 0) p.colFunc = make([]FuncShow, 0) p.primaryKind = "" p.specs = append(p.specs, startKind) p.attrsNodes = append(p.attrsNodes, make(map[string]string)) p.attrsEdges = append(p.attrsEdges, make(map[string]string)) // With clause is interpreted straight after finishing the columns var withChild *parser.ASTNode // Go through the children, check if they are valid and initialise them for _, child := range rootChildren { if child.Name == parser.NodeWHERE { // Check if the show clause or some traversals are already populated if p.show != nil || len(p.traversals) > 0 { return p.newRuntimeError(ErrInvalidConstruct, "condition must be before show clause and traversals", child) } // Reset state of where and store it if err := child.Runtime.Validate(); err != nil { return err } p.where = child } else if child.Name == parser.NodeTRAVERSE { // Check if show clause or where clause is already populated if p.show != nil { return p.newRuntimeError(ErrInvalidConstruct, "traversals must be before show clause", child) } // Reset state of traversal and add it to the traversal list if err := child.Runtime.Validate(); err != nil { return err } p.traversals = append(p.traversals, child) } else if child.Name == parser.NodeSHOW { p.show = child } else if child.Name == parser.NodeFROM { // Set the group state p.groupScope = child.Children[0].Children[0].Token.Val } else if child.Name == parser.NodePRIMARY { pk := child.Children[0].Token.Val for _, nk := range p.gm.NodeKinds() { if nk == pk { p.primaryKind = pk } } if p.primaryKind == "" { return p.newRuntimeError(ErrUnknownNodeKind, pk, child.Children[0]) } } else if child.Name == parser.NodeWITH { withChild = child } else { return p.newRuntimeError(ErrInvalidConstruct, child.Name, child) } } // Populate column related attributes nodeKindPos, edgeKindPos, err := p.initCols() if err != nil { return err } // Interpret with clause straight after populating the columns if withChild != nil { if err := p.initWithFlags(withChild, nodeKindPos, edgeKindPos); err != nil { return err } } if p.primaryKind == "" { p.primaryKind = startKind } return nil } /* initWithFlags populates the withFlags datastructure. It is assumed that the columns have been populated before calling this function. */ func (p *eqlRuntimeProvider) initWithFlags(withNode *parser.ASTNode, nodeKindPos map[string][]int, edgeKindPos map[string][]int) error { // Helper function to find a specified column findColumn := func(colData string, node *parser.ASTNode) (int, error) { col := -1 colDataSplit := strings.SplitN(colData, ":", 3) switch len(colDataSplit) { case 1: // Find the first column which displays the given attribute for i, cd := range p.colData { cds := strings.SplitN(cd, ":", 3) if cds[2] == colDataSplit[0] { col = i } } case 2: // Search for first kind / attribute occurrence kind := colDataSplit[0] attr := colDataSplit[1] searchColData := func(pos int, t string) { cstr := fmt.Sprint(pos+1, ":", t, ":", attr) for i, c := range p.colData { if c == cstr { col = i } } } if poslist, ok := nodeKindPos[kind]; ok { searchColData(poslist[0], "n") } else if poslist, ok := edgeKindPos[kind]; ok { searchColData(poslist[0], "e") } else { return -1, p.newRuntimeError(ErrInvalidConstruct, "Cannot determine column for with term: "+colData, node) } case 3: // Search for exact specification for i, c := range p.colData { if c == colData { col = i } } } if col == -1 { return -1, p.newRuntimeError(ErrInvalidConstruct, "Cannot determine column for with term: "+colData, node) } return col, nil } // Go through all children and initialise the withFlags data structure for _, child := range withNode.Children { if child.Name == parser.NodeNULLTRAVERSAL && child.Children[0].Name == parser.NodeTRUE { p.allowNilTraversal = true } else if child.Name == parser.NodeFILTERING { for _, child := range child.Children { if child.Name == parser.NodeISNOTNULL || child.Name == parser.NodeUNIQUE || child.Name == parser.NodeUNIQUECOUNT { c, err := findColumn(child.Children[0].Token.Val, child) if err != nil { return err } if child.Name == parser.NodeISNOTNULL { p.withFlags.notnullCol = append(p.withFlags.notnullCol, c) } else if child.Name == parser.NodeUNIQUE { p.withFlags.uniqueCol = append(p.withFlags.uniqueCol, c) p.withFlags.uniqueColCnt = append(p.withFlags.uniqueColCnt, false) } else if child.Name == parser.NodeUNIQUECOUNT { p.withFlags.uniqueCol = append(p.withFlags.uniqueCol, c) p.withFlags.uniqueColCnt = append(p.withFlags.uniqueColCnt, true) } } else { return p.newRuntimeError(ErrInvalidConstruct, child.Token.Val, child) } } } else if child.Name == parser.NodeORDERING { for _, child := range child.Children { if child.Name == parser.NodeASCENDING || child.Name == parser.NodeDESCENDING { c, err := findColumn(child.Children[0].Token.Val, child) if err != nil { return err } if child.Name == parser.NodeASCENDING { p.withFlags.ordering = append(p.withFlags.ordering, withOrderingAscending) } else { p.withFlags.ordering = append(p.withFlags.ordering, withOrderingDescending) } p.withFlags.orderingCol = append(p.withFlags.orderingCol, c) } else { return p.newRuntimeError(ErrInvalidConstruct, child.Token.Val, child) } } } else { return p.newRuntimeError(ErrInvalidConstruct, child.Token.Val, child) } } return nil } /* initCols populates the column related attributes. This function assumes that specs is filled with all necessary traversals. The following formats for a show term are allowed: :: - Attribute from whatever is at the given traversal step : - First matching kind in a row provides the attribute - Show attribute from root node kind */ func (p *eqlRuntimeProvider) initCols() (map[string][]int, map[string][]int, error) { // Fill lookup maps for traversal kind positions // Show term match by kind uses these nodeKindPos := make(map[string][]int) edgeKindPos := make(map[string][]int) addPos := func(kmap map[string][]int, kind string, pos int) { if l, ok := kmap[kind]; ok { kmap[kind] = append(l, pos) } else { kmap[kind] = []int{pos} } } for i, spec := range p.specs { if i == 0 { addPos(nodeKindPos, spec, i) } else { sspec := strings.Split(spec, ":") if sspec[1] != "" { addPos(edgeKindPos, sspec[1], i) } if sspec[3] != "" { addPos(nodeKindPos, sspec[3], i) } } } // Fill up column lists if p.show == nil || len(p.show.Children) == 0 { // If no show clause is defined ask the NodeInfo to provide a summary list for i, spec := range p.specs { sspec := strings.Split(spec, ":") kind := sspec[len(sspec)-1] for _, attr := range p.ni.SummaryAttributes(kind) { // Make sure the attribute is in attrsNodes p.attrsNodes[i][attr] = "" // Fill col attributes (we only show nodes) p.colLabels = append(p.colLabels, p.ni.AttributeDisplayString(kind, attr)) p.colFormat = append(p.colFormat, "auto") p.colData = append(p.colData, fmt.Sprintf("%v:n:%s", i+1, attr)) p.colFunc = append(p.colFunc, nil) } } } else { var err error var attr, label, colData string var pos int var isNode bool var colFunc FuncShow // Go through the elements of the provided show clause for _, col := range p.show.Children { if col.Name != parser.NodeSHOWTERM { return nil, nil, p.newRuntimeError(ErrInvalidConstruct, col.Name, col) } // Reset label value label = "" colFunc = nil // Create the correct colData value if col.Token.ID == parser.TokenAT { // We have a function get the attribute which it operates on funcName := col.Children[0].Children[0].Token.Val funcInst, ok := showFunc[funcName] if !ok { return nil, nil, p.newRuntimeError(ErrInvalidConstruct, "Unknown function: "+funcName, col) } colFunc, colData, label, err = funcInst(col.Children[0], p) if err != nil { return nil, nil, p.newRuntimeError(ErrInvalidConstruct, err.Error(), col) } } else { colData = col.Token.Val } colDataSplit := strings.SplitN(colData, ":", 3) switch len(colDataSplit) { case 1: // Show attribute from root node kind attr = colDataSplit[0] pos = 0 isNode = true colData = "1:n:" + attr if label == "" { label = p.ni.AttributeDisplayString(p.specs[0], attr) } case 2: // First matching kind in a row provides the attribute kind := colDataSplit[0] if poslist, ok := nodeKindPos[kind]; ok { attr = colDataSplit[1] pos = poslist[0] isNode = true colData = fmt.Sprint(pos+1) + ":n:" + attr } else if poslist, ok := edgeKindPos[kind]; ok { attr = colDataSplit[1] pos = poslist[0] isNode = false colData = fmt.Sprint(pos+1) + ":e:" + attr } else { return nil, nil, p.newRuntimeError(ErrInvalidConstruct, "Cannot determine data position for kind: "+kind, col) } if label == "" { label = p.ni.AttributeDisplayString(kind, attr) } case 3: // Attribute from whatever is at the given traversal step attr = colDataSplit[2] pos, err = strconv.Atoi(colDataSplit[0]) if err != nil { return nil, nil, p.newRuntimeError(ErrInvalidConstruct, "Invalid data index: "+colData+" ("+err.Error()+")", col) } else if pos < 1 { return nil, nil, p.newRuntimeError(ErrInvalidConstruct, "Invalid data index: "+colData+" (index must be greater than 0)", col) } pos-- if colDataSplit[1] == "n" { isNode = true } else if colDataSplit[1] == "e" { isNode = false } else { return nil, nil, p.newRuntimeError(ErrInvalidConstruct, "Invalid data source '"+colDataSplit[1]+"' (either n - Node or e - Edge)", col) } if label == "" { label = p.ni.AttributeDisplayString("", attr) } } if pos >= len(p.attrsNodes) { return nil, nil, p.newRuntimeError(ErrInvalidColData, fmt.Sprintf("Data index out of range: %v", pos+1), col) } // Determine label and format colLabel := label colFormat := "auto" for _, t := range col.Children { if t.Name == parser.NodeAS { colLabel = t.Children[0].Token.Val } else if t.Name == parser.NodeFORMAT { colFormat = t.Children[0].Token.Val } else if t.Name != parser.NodeFUNC { return nil, nil, p.newRuntimeError(ErrInvalidConstruct, t.Name, t) } } // Fill col attributes p.colLabels = append(p.colLabels, colLabel) p.colFormat = append(p.colFormat, colFormat) p.colData = append(p.colData, colData) p.colFunc = append(p.colFunc, colFunc) // Populate attrsNodes and attrsEdges if isNode { p.attrsNodes[pos][attr] = "" } else { p.attrsEdges[pos][attr] = "" } } } return nodeKindPos, edgeKindPos, nil } /* next advances to the next query row. Returns false if no more rows are available. It is assumed that all traversal specs and query attrs have been filled. */ func (p *eqlRuntimeProvider) next() (bool, error) { // Create fetch lists if it is the first next() call if p._attrsNodesFetch == nil { makeFetchList := func(attrs []map[string]string, isEdge bool) [][]string { var fetchlist [][]string for _, attrs := range attrs { var attrsFetch []string for attr := range attrs { // Condition needs to be different for nodes and edges if !isEdge && attr != "" && attr != data.NodeKey && attr != data.NodeKind { attrsFetch = append(attrsFetch, attr) } else if attr != "" && attr != data.NodeKey && attr != data.NodeKind && attr != data.EdgeEnd1Key && attr != data.EdgeEnd1Kind && attr != data.EdgeEnd1Role && attr != data.EdgeEnd1Cascading && attr != data.EdgeEnd2Key && attr != data.EdgeEnd2Kind && attr != data.EdgeEnd2Role && attr != data.EdgeEnd2Cascading { attrsFetch = append(attrsFetch, attr) } } fetchlist = append(fetchlist, attrsFetch) } return fetchlist } p._attrsNodesFetch = makeFetchList(p.attrsNodes, false) p._attrsEdgesFetch = makeFetchList(p.attrsEdges, true) } // Make sure we have the row and rowEdge arrays if p.rowNode == nil { p.rowNode = make([]data.Node, 0) p.rowEdge = make([]data.Edge, 0) } // Check if a traversal can handle the call for _, child := range p.traversals { childRuntime := child.Runtime.(*traversalRuntime) if childRuntime.hasMoreNodes() { _, err := childRuntime.Eval() return err == nil, err } } // Get next root node startKey, err := p.nextStartKey() if err != nil || startKey == "" { return false, err } // Fetch node - always require the key attribute // to make sure we get a node back if it exists node, err := p.gm.FetchNodePart(p.part, startKey, p.specs[0], append(p._attrsNodesFetch[0], "key")) if err != nil || node == nil { return false, err } // Decide if this node should be added addNode := true if p.where != nil { res, err := p.where.Runtime.(CondRuntime).CondEval(node, nil) if err != nil { return false, err } addNode = res.(bool) } if addNode { // Add node and the first traversal if len(p.rowNode) == 0 { p.rowNode = append(p.rowNode, node) p.rowEdge = append(p.rowEdge, nil) } else { // Clear out the row for i := range p.rowNode { p.rowNode[i] = nil p.rowEdge[i] = nil } // Fill in the first node p.rowNode[0] = node p.rowEdge[0] = nil } // Give the new source to the children and let them evaluate for _, child := range p.traversals { childRuntime := child.Runtime.(*traversalRuntime) if err := childRuntime.newSource(node); err == ErrEmptyTraversal { // If an empty traversal error comes back advance until // there is an element or the end p.rowNode[0] = nil p.rowEdge[0] = nil return p.next() } else if err != nil { return false, err } } } else { // Recursively call next until there is a condition-matching node or // there are no more start keys available return p.next() } return true, nil } /* Instance function for general components */ type generalInst func(*eqlRuntimeProvider, *parser.ASTNode) parser.Runtime /* Runtime map for general components */ var generalProviderMap = map[string]generalInst{ parser.NodeEOF: invalidRuntimeInst, parser.NodeVALUE: valueRuntimeInst, parser.NodeTRUE: valueRuntimeInst, parser.NodeFALSE: valueRuntimeInst, parser.NodeNULL: valueRuntimeInst, parser.NodeLIST: valueRuntimeInst, parser.NodeFUNC: valueRuntimeInst, parser.NodeTRAVERSE: traversalRuntimeInst, parser.NodeWHERE: whereRuntimeInst, // Condition components // ==================== parser.NodeEQ: equalRuntimeInst, parser.NodeNEQ: notEqualRuntimeInst, parser.NodeLT: lessThanRuntimeInst, parser.NodeLEQ: lessThanEqualsRuntimeInst, parser.NodeGT: greaterThanRuntimeInst, parser.NodeGEQ: greaterThanEqualsRuntimeInst, parser.NodeNOT: notRuntimeInst, parser.NodeAND: andRuntimeInst, parser.NodeOR: orRuntimeInst, // Simple arithmetic expressions parser.NodePLUS: plusRuntimeInst, parser.NodeMINUS: minusRuntimeInst, parser.NodeTIMES: timesRuntimeInst, parser.NodeDIV: divRuntimeInst, parser.NodeMODINT: modIntRuntimeInst, parser.NodeDIVINT: divIntRuntimeInst, // List operations parser.NodeIN: inRuntimeInst, parser.NodeNOTIN: notInRuntimeInst, // String operations parser.NodeLIKE: likeRuntimeInst, parser.NodeCONTAINS: containsRuntimeInst, parser.NodeCONTAINSNOT: containsNotRuntimeInst, parser.NodeBEGINSWITH: beginsWithRuntimeInst, parser.NodeENDSWITH: endsWithRuntimeInst, }