123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- /*
- * 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 contains the EQL interpreter.
- */
- package interpreter
- import (
- "errors"
- "fmt"
- "strconv"
- "strings"
- "time"
- "devt.de/krotik/common/datautil"
- "devt.de/krotik/common/errorutil"
- "devt.de/krotik/eliasdb/eql/parser"
- "devt.de/krotik/eliasdb/graph/data"
- )
- // Where related functions
- // =======================
- /*
- FuncWhere represents a where related function.
- */
- type FuncWhere func(astNode *parser.ASTNode, rtp *eqlRuntimeProvider,
- node data.Node, edge data.Edge) (interface{}, error)
- /*
- Runtime map for where related functions
- */
- var whereFunc = map[string]FuncWhere{
- "count": whereCount,
- "parseDate": whereParseDate,
- }
- /*
- whereCount counts reachable nodes via a given traversal.
- */
- func whereCount(astNode *parser.ASTNode, rtp *eqlRuntimeProvider,
- node data.Node, edge data.Edge) (interface{}, error) {
- // Check parameters
- np := len(astNode.Children)
- if np != 2 && np != 3 {
- return nil, rtp.newRuntimeError(ErrInvalidConstruct,
- "Count function requires 1 or 2 parameters: traversal spec, condition clause", astNode)
- }
- spec := astNode.Children[1].Token.Val
- // Only need to retrieve full node values if there is a where clause
- nodes, _, err := rtp.gm.TraverseMulti(rtp.part, node.Key(), node.Kind(), spec, np == 3)
- if np == 3 {
- var filteredNodes []data.Node
- // If a where clause was given parse it and evaluate it
- conditionString := astNode.Children[2].Token.Val
- ast, err := parser.ParseWithRuntime("count condition", "get _ where "+conditionString, &GetRuntimeProvider{rtp})
- if err != nil {
- return nil, rtp.newRuntimeError(ErrInvalidConstruct,
- fmt.Sprintf("Invalid condition clause in count function: %s", err), astNode)
- }
- cond := ast.Children[1] // This should always pick out just the where clause
- errorutil.AssertOk(cond.Runtime.Validate()) // Validation should alwasys succeed
- for _, n := range nodes {
- res, err := cond.Children[0].Runtime.(CondRuntime).CondEval(n, nil)
- if err != nil {
- return nil, rtp.newRuntimeError(ErrInvalidConstruct,
- fmt.Sprintf("Invalid condition clause in count function: %s", err), astNode)
- } else if b, ok := res.(bool); ok {
- if b {
- filteredNodes = append(filteredNodes, n)
- }
- } else {
- return nil, rtp.newRuntimeError(ErrInvalidConstruct,
- "Could not evaluate condition clause in count function", astNode)
- }
- }
- nodes = filteredNodes
- }
- return len(nodes), err
- }
- /*
- whereParseDate converts a date string into a unix time value.
- */
- func whereParseDate(astNode *parser.ASTNode, rtp *eqlRuntimeProvider,
- node data.Node, edge data.Edge) (interface{}, error) {
- var datestr interface{}
- var t time.Time
- var ret int64
- var err error
- // Define default layout
- layout := time.RFC3339
- // Check parameters
- if len(astNode.Children) < 2 {
- return nil, rtp.newRuntimeError(ErrInvalidConstruct,
- "parseDate function requires 1 parameter: date string", astNode)
- }
- if len(astNode.Children) > 2 {
- datestr, err = astNode.Children[2].Runtime.(CondRuntime).CondEval(node, edge)
- layout = fmt.Sprint(datestr)
- }
- // Convert the date string
- datestr, err = astNode.Children[1].Runtime.(CondRuntime).CondEval(node, edge)
- if err == nil {
- t, err = time.Parse(layout, fmt.Sprint(datestr))
- if err == nil {
- ret = t.Unix()
- }
- }
- return ret, err
- }
- // Show related functions
- // ======================
- /*
- Runtime map for show related functions
- */
- var showFunc = map[string]FuncShowInst{
- "count": showCountInst,
- "objget": showObjgetInst,
- }
- /*
- FuncShow is the interface definition for show related functions
- */
- type FuncShow interface {
- /*
- name returns the name of the function.
- */
- name() string
- /*
- eval runs the function. Returns the result and a source for the result.
- The source should be a concrete node/edge key and kind or a query and
- should be returned in either of the following formats:
- n:<key>:<kind> for a node
- e:<key>:<kind> for an edge
- q:<query> for a query
- */
- eval(node data.Node, edge data.Edge) (interface{}, string, error)
- }
- /*
- FuncShowInst creates a function object. Returns which column data should be queried and
- how the colummn should be named.
- */
- type FuncShowInst func(astNode *parser.ASTNode, rtp *eqlRuntimeProvider) (FuncShow, string, string, error)
- // Show Count
- // ----------
- /*
- showCountInst creates a new showCount object.
- */
- func showCountInst(astNode *parser.ASTNode, rtp *eqlRuntimeProvider) (FuncShow, string, string, error) {
- var cond *parser.ASTNode
- // Check parameters
- np := len(astNode.Children)
- if np != 3 && np != 4 {
- return nil, "", "", errors.New("Count function requires 2 or 3 parameters: traversal step, traversal spec, condition clause")
- }
- pos := astNode.Children[1].Token.Val
- spec := astNode.Children[2].Token.Val
- if np == 4 {
- // If a condition clause was given parse it
- condString := astNode.Children[3].Token.Val
- ast, err := parser.ParseWithRuntime("count condition", "get _ where "+condString, &GetRuntimeProvider{rtp})
- if err != nil {
- return nil, "", "", fmt.Errorf("Invalid condition clause in count function: %s", err)
- }
- cond = ast.Children[1] // This should always pick out just the condition clause
- errorutil.AssertOk(cond.Runtime.Validate()) // Validation should alwasys succeed
- }
- return &showCount{rtp, astNode, spec, cond}, pos + ":n:key", "Count", nil
- }
- /*
- showCount is the number of reachable nodes via a given traversal spec.
- */
- type showCount struct {
- rtp *eqlRuntimeProvider
- astNode *parser.ASTNode
- spec string
- condition *parser.ASTNode
- }
- /*
- name returns the name of the function.
- */
- func (sc *showCount) name() string {
- return "count"
- }
- /*
- eval counts reachable nodes via a given traversal.
- */
- func (sc *showCount) eval(node data.Node, edge data.Edge) (interface{}, string, error) {
- condString := ""
- // Only need to retrieve full node values if there is a where clause
- nodes, _, err := sc.rtp.gm.TraverseMulti(sc.rtp.part, node.Key(), node.Kind(), sc.spec, sc.condition != nil)
- if err != nil {
- return nil, "", err
- }
- if sc.condition != nil {
- var filteredNodes []data.Node
- // If there is a condition clause filter the result
- condString, _ = parser.PrettyPrint(sc.condition)
- for _, n := range nodes {
- res, err := sc.condition.Children[0].Runtime.(CondRuntime).CondEval(n, nil)
- if err != nil {
- return nil, "", err
- } else if b, ok := res.(bool); ok {
- if b {
- filteredNodes = append(filteredNodes, n)
- }
- } else {
- return nil, "", sc.rtp.newRuntimeError(ErrInvalidConstruct,
- "Could not evaluate condition clause in count function", sc.astNode)
- }
- }
- nodes = filteredNodes
- }
- srcQuery := fmt.Sprintf("q:lookup %s %s traverse %s %s end show 2:n:%s, 2:n:%s, 2:n:%s",
- node.Kind(), strconv.Quote(node.Key()), sc.spec, condString, data.NodeKey, data.NodeKind, data.NodeName)
- return len(nodes), srcQuery, nil
- }
- // Show Objget
- // -----------
- /*
- showObjgetInst creates a new showObjget object.
- */
- func showObjgetInst(astNode *parser.ASTNode, rtp *eqlRuntimeProvider) (FuncShow, string, string, error) {
- // Check parameters
- if len(astNode.Children) != 4 {
- return nil, "", "",
- fmt.Errorf("Objget function requires 3 parameters: traversal step, attribute name, path to value")
- }
- pos := astNode.Children[1].Token.Val
- attr := astNode.Children[2].Token.Val
- path := astNode.Children[3].Token.Val
- return &showObjget{rtp, attr, strings.Split(path, ".")}, pos + ":n:" + attr,
- rtp.ni.AttributeDisplayString("", attr) + "." + path, nil
- }
- /*
- showObjget reaches into an object and extracts a value.
- */
- type showObjget struct {
- rtp *eqlRuntimeProvider
- attr string
- path []string
- }
- /*
- name returns the name of the function.
- */
- func (so *showObjget) name() string {
- return "objget"
- }
- /*
- eval reaches into an object and extracts a value.
- */
- func (so *showObjget) eval(node data.Node, edge data.Edge) (interface{}, string, error) {
- val := node.Attr(so.attr)
- if valMap, ok := val.(map[string]interface{}); ok {
- val, _ = datautil.GetNestedValue(valMap, so.path)
- }
- return val, "n:" + node.Kind() + ":" + node.Key(), nil
- }
|