From 107aa662d6c9f31e7b5b100fde16c22a400ccb46 Mon Sep 17 00:00:00 2001 From: Yosi Attias Date: Thu, 14 Apr 2016 18:30:08 +0300 Subject: [PATCH] Speicalized Stateful Expression MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creating specialized stateful expression in order to improve performance and code clarity. Speicalized stateful expression eliminates all heap allocations and most of interface{} conversions. The idea behind "specialized stateful expression" is to implement (very very simple) "JIT Compiler" for stateful expression. For example: given this expression ``"value" > 8.0``, at runtime we will try to find the type of "value", once we find the type (for this example, let's say it's float64) we will comparison functions which accepts float64 both on the right and left side. Contiuing with our example, let's say that on runtime after evaluating multiple times - "value" changed is type to int64, if that happens - we will have "type guard" that will raise an error and say this is not "float64 > float64" it's "int64 > float64" and we will adjust the evaluation function. There are more changes, those are the bottom line: * Tests - lots of tests added, at the time of writing this commit we have 77.8% coverage for "stateful" package. * Compile time errors - simple compile time errors that will stop the tasks starting if there is some simple mistakes. Finally, benchmarks: * Compared against the interepeted one - "NewStatefulExpr" * Ran with count=5 on Macbook Pro 13" Late 2011 (i5, 8GB RAM, 120GB SSD) name me old time/op new time/op delta _EvalBool_OneOperator_UnaryNode_BoolNode-4 252ns ± 2% 16ns ± 3% -93.53% (p=0.008 n=5+5) _EvalBool_OneOperator_NumberFloat64_NumberFloat64-4 540ns ± 2% 40ns ± 2% -92.58% (p=0.008 n=5+5) _EvalBool_OneOperator_NumberFloat64_NumberInt64-4 550ns ± 3% 40ns ± 3% -92.68% (p=0.008 n=5+5) _EvalBool_OneOperator_NumberInt64_NumberInt64-4 539ns ± 2% 39ns ± 4% -92.77% (p=0.008 n=5+5) _EvalBool_OneOperator_ReferenceNodeFloat64_NumberFloat64-4 524ns ± 3% 74ns ± 5% -85.93% (p=0.008 n=5+5) _EvalBool_OneOperator_ReferenceNodeFloat64_NumberInt64-4 526ns ± 1% 76ns ± 3% -85.51% (p=0.008 n=5+5) _EvalBool_OneOperator_ReferenceNodeFloat64_ReferenceNodeFloat64-4 495ns ± 3% 116ns ± 4% -76.48% (p=0.008 n=5+5) _EvalBool_OneOperatorWith11ScopeItem_ReferenceNodeFloat64_NumberFloat64-4 534ns ± 3% 90ns ± 4% -83.11% (p=0.008 n=5+5) _EvalBool_OneOperatorValueChanges_ReferenceNodeFloat64_NumberFloat64-4 2.98µs ± 1% 1.24µs ± 3% -58.44% (p=0.008 n=5+5) _EvalBool_OneOperator_ReferenceNodeInt64_ReferenceNodeInt64-4 503ns ± 3% 119ns ± 7% -76.25% (p=0.008 n=5+5) _EvalBool_OneOperatorWith11ScopeItem_ReferenceNodeInt64_NumberInt64-4 533ns ± 1% 87ns ± 3% -83.69% (p=0.008 n=5+5) _EvalBool_OneOperatorValueChanges_ReferenceNodeInt64_NumberInt64-4 3.08µs ± 4% 1.26µs ± 3% -59.22% (p=0.008 n=5+5) name old alloc/op new alloc/op delta _EvalBool_OneOperator_UnaryNode_BoolNode-4 18.0B ± 0% 0.0B ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperator_NumberFloat64_NumberFloat64-4 72.0B ± 0% 0.0B ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperator_NumberFloat64_NumberInt64-4 72.0B ± 0% 0.0B ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperator_NumberInt64_NumberInt64-4 72.0B ± 0% 0.0B ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperator_ReferenceNodeFloat64_NumberFloat64-4 64.0B ± 0% 0.0B ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperator_ReferenceNodeFloat64_NumberInt64-4 64.0B ± 0% 0.0B ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperator_ReferenceNodeFloat64_ReferenceNodeFloat64-4 49.0B ± 0% 0.0B ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperatorWith11ScopeItem_ReferenceNodeFloat64_NumberFloat64-4 64.0B ± 0% 0.0B ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperatorValueChanges_ReferenceNodeFloat64_NumberFloat64-4 64.0B ± 0% 0.0B ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperator_ReferenceNodeInt64_ReferenceNodeInt64-4 49.0B ± 0% 0.0B ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperatorWith11ScopeItem_ReferenceNodeInt64_NumberInt64-4 64.0B ± 0% 0.0B ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperatorValueChanges_ReferenceNodeInt64_NumberInt64-4 64.0B ± 0% 0.0B ±NaN% -100.00% (p=0.008 n=5+5) name old allocs/op new allocs/op delta _EvalBool_OneOperator_UnaryNode_BoolNode-4 3.00 ± 0% 0.00 ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperator_NumberFloat64_NumberFloat64-4 5.00 ± 0% 0.00 ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperator_NumberFloat64_NumberInt64-4 5.00 ± 0% 0.00 ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperator_NumberInt64_NumberInt64-4 5.00 ± 0% 0.00 ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperator_ReferenceNodeFloat64_NumberFloat64-4 4.00 ± 0% 0.00 ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperator_ReferenceNodeFloat64_NumberInt64-4 4.00 ± 0% 0.00 ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperator_ReferenceNodeFloat64_ReferenceNodeFloat64-4 3.00 ± 0% 0.00 ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperatorWith11ScopeItem_ReferenceNodeFloat64_NumberFloat64-4 4.00 ± 0% 0.00 ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperatorValueChanges_ReferenceNodeFloat64_NumberFloat64-4 4.00 ± 0% 0.00 ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperator_ReferenceNodeInt64_ReferenceNodeInt64-4 3.00 ± 0% 0.00 ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperatorWith11ScopeItem_ReferenceNodeInt64_NumberInt64-4 4.00 ± 0% 0.00 ±NaN% -100.00% (p=0.008 n=5+5) _EvalBool_OneOperatorValueChanges_ReferenceNodeInt64_NumberInt64-4 4.00 ± 0% 0.00 ±NaN% -100.00% (p=0.008 n=5+5) --- CHANGELOG.md | 1 + alert.go | 28 +- eval.go | 14 +- expr.go | 5 +- stream.go | 12 +- tick/lex.go | 8 +- tick/parser.go | 6 +- tick/stateful/eval_binary_node.go | 332 ++++++++ tick/stateful/eval_bool_node.go | 35 + tick/stateful/eval_float_node.go | 34 + tick/stateful/eval_function_node.go | 160 ++++ tick/stateful/eval_function_node_test.go | 328 ++++++++ tick/stateful/eval_int_node.go | 34 + tick/stateful/eval_reference_node.go | 96 +++ tick/stateful/eval_regex_node.go | 35 + tick/stateful/eval_string_node.go | 35 + tick/stateful/eval_unary_node.go | 84 ++ tick/stateful/eval_unary_node_test.go | 192 +++++ tick/stateful/evaluation_funcs.go | 789 ++++++++++++++++++ tick/stateful/execution_state.go | 22 + tick/stateful/expr.go | 93 +++ .../expr_benchmarks_test.go} | 96 ++- tick/stateful/expr_dynamic_test.go | 204 +++++ .../expr_test.go} | 578 +++++++++---- tick/stateful/node_evaluator.go | 78 ++ tick/stateful/types.go | 190 +++++ tick/stateful/types_test.go | 110 +++ tick/stateful_expr.go | 365 -------- where.go | 21 +- 29 files changed, 3438 insertions(+), 547 deletions(-) create mode 100644 tick/stateful/eval_binary_node.go create mode 100644 tick/stateful/eval_bool_node.go create mode 100644 tick/stateful/eval_float_node.go create mode 100644 tick/stateful/eval_function_node.go create mode 100644 tick/stateful/eval_function_node_test.go create mode 100644 tick/stateful/eval_int_node.go create mode 100644 tick/stateful/eval_reference_node.go create mode 100644 tick/stateful/eval_regex_node.go create mode 100644 tick/stateful/eval_string_node.go create mode 100644 tick/stateful/eval_unary_node.go create mode 100644 tick/stateful/eval_unary_node_test.go create mode 100644 tick/stateful/evaluation_funcs.go create mode 100644 tick/stateful/execution_state.go create mode 100644 tick/stateful/expr.go rename tick/{stateful_expr_benchmarks_test.go => stateful/expr_benchmarks_test.go} (76%) create mode 100644 tick/stateful/expr_dynamic_test.go rename tick/{stateful_expr_test.go => stateful/expr_test.go} (70%) create mode 100644 tick/stateful/node_evaluator.go create mode 100644 tick/stateful/types.go create mode 100644 tick/stateful/types_test.go delete mode 100644 tick/stateful_expr.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 0547ac6407..979cc7bf22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ For example, let's say we want to store all data that triggered an alert in Infl - [#388](https://github.com/influxdata/kapacitor/issues/388): The duration of an alert is now tracked and exposed as part of the alert data as well as can be set as a field via `.durationField('duration')`. - [#486](https://github.com/influxdata/kapacitor/pull/486): Default config file location. - [#461](https://github.com/influxdata/kapacitor/pull/461): Make Alerta `event` property configurable. +- [#491](https://github.com/influxdata/kapacitor/pull/491): BREAKING: Rewriting stateful expression in order to improve performance, the only breaking change is: short circuit evaluation for booleans - for example: ``lambda: "bool_value" && (count() > 100)`` if "bool_value" is false, we won't evaluate "count". ### Bugfixes diff --git a/alert.go b/alert.go index 4650e748c0..1dc8c65a80 100644 --- a/alert.go +++ b/alert.go @@ -19,7 +19,7 @@ import ( imodels "github.com/influxdata/influxdb/models" "github.com/influxdata/kapacitor/models" "github.com/influxdata/kapacitor/pipeline" - "github.com/influxdata/kapacitor/tick" + "github.com/influxdata/kapacitor/tick/stateful" ) const ( @@ -97,7 +97,7 @@ type AlertNode struct { a *pipeline.AlertNode endpoint string handlers []AlertHandler - levels []*tick.StatefulExpr + levels []stateful.Expression states map[models.GroupID]*alertState idTmpl *text.Template messageTmpl *text.Template @@ -273,16 +273,32 @@ func newAlertNode(et *ExecutingTask, n *pipeline.AlertNode, l *log.Logger) (an * } // Parse level expressions - an.levels = make([]*tick.StatefulExpr, CritAlert+1) + an.levels = make([]stateful.Expression, CritAlert+1) + if n.Info != nil { - an.levels[InfoAlert] = tick.NewStatefulExpr(n.Info) + statefulExpression, expressionCompileError := stateful.NewExpression(n.Info) + if expressionCompileError != nil { + return nil, fmt.Errorf("Failed to compile stateful expression for info: %s", expressionCompileError) + } + an.levels[InfoAlert] = statefulExpression } + if n.Warn != nil { - an.levels[WarnAlert] = tick.NewStatefulExpr(n.Warn) + statefulExpression, expressionCompileError := stateful.NewExpression(n.Warn) + if expressionCompileError != nil { + return nil, fmt.Errorf("Failed to compile stateful expression for warn: %s", expressionCompileError) + } + an.levels[WarnAlert] = statefulExpression } + if n.Crit != nil { - an.levels[CritAlert] = tick.NewStatefulExpr(n.Crit) + statefulExpression, expressionCompileError := stateful.NewExpression(n.Crit) + if expressionCompileError != nil { + return nil, fmt.Errorf("Failed to compile stateful expression for crit: %s", expressionCompileError) + } + an.levels[CritAlert] = statefulExpression } + // Setup states if n.History < 2 { n.History = 2 diff --git a/eval.go b/eval.go index e81b0c3e12..0e89a342f7 100644 --- a/eval.go +++ b/eval.go @@ -2,13 +2,14 @@ package kapacitor import ( "errors" + "fmt" "log" "time" "github.com/influxdata/kapacitor/expvar" "github.com/influxdata/kapacitor/models" "github.com/influxdata/kapacitor/pipeline" - "github.com/influxdata/kapacitor/tick" + "github.com/influxdata/kapacitor/tick/stateful" ) const ( @@ -18,7 +19,7 @@ const ( type EvalNode struct { node e *pipeline.EvalNode - expressions []*tick.StatefulExpr + expressions []stateful.Expression evalErrors *expvar.Int } @@ -32,9 +33,14 @@ func newEvalNode(et *ExecutingTask, n *pipeline.EvalNode, l *log.Logger) (*EvalN e: n, } // Create stateful expressions - en.expressions = make([]*tick.StatefulExpr, len(n.Expressions)) + en.expressions = make([]stateful.Expression, len(n.Expressions)) for i, expr := range n.Expressions { - en.expressions[i] = tick.NewStatefulExpr(expr) + statefulExpr, err := stateful.NewExpression(expr) + if err != nil { + return nil, fmt.Errorf("Failed to compile %v expression: %v", i, err) + } + + en.expressions[i] = statefulExpr } en.node.runF = en.runEval diff --git a/expr.go b/expr.go index 56846e8383..ff72f2e00e 100644 --- a/expr.go +++ b/expr.go @@ -6,10 +6,11 @@ import ( "github.com/influxdata/kapacitor/models" "github.com/influxdata/kapacitor/tick" + "github.com/influxdata/kapacitor/tick/stateful" ) -// Evaluate a given expression as a boolean predicate against a set of fields and tags -func EvalPredicate(se *tick.StatefulExpr, now time.Time, fields models.Fields, tags models.Tags) (bool, error) { +// EvalPredicate - Evaluate a given expression as a boolean predicate against a set of fields and tags +func EvalPredicate(se stateful.Expression, now time.Time, fields models.Fields, tags models.Tags) (bool, error) { vars, err := mergeFieldsAndTags(now, fields, tags) if err != nil { return false, err diff --git a/stream.go b/stream.go index 6a42ed8565..89c540a856 100644 --- a/stream.go +++ b/stream.go @@ -1,11 +1,12 @@ package kapacitor import ( + "fmt" "log" "github.com/influxdata/kapacitor/models" "github.com/influxdata/kapacitor/pipeline" - "github.com/influxdata/kapacitor/tick" + "github.com/influxdata/kapacitor/tick/stateful" ) type StreamNode struct { @@ -38,7 +39,7 @@ func (s *StreamNode) runSourceStream([]byte) error { type FromNode struct { node s *pipeline.FromNode - expression *tick.StatefulExpr + expression stateful.Expression dimensions []string allDimensions bool db string @@ -59,7 +60,12 @@ func newFromNode(et *ExecutingTask, n *pipeline.FromNode, l *log.Logger) (*FromN sn.allDimensions, sn.dimensions = determineDimensions(n.Dimensions) if n.Expression != nil { - sn.expression = tick.NewStatefulExpr(n.Expression) + expr, err := stateful.NewExpression(n.Expression) + if err != nil { + return nil, fmt.Errorf("Failed to compile from expression: %v", err) + } + + sn.expression = expr } return sn, nil diff --git a/tick/lex.go b/tick/lex.go index cb66724272..5be42f42b1 100644 --- a/tick/lex.go +++ b/tick/lex.go @@ -161,24 +161,24 @@ func (t TokenType) String() string { return "TRUE" case t == TokenFalse: return "FALSE" - case isExprOperator(t): + case IsExprOperator(t): return operatorStr[t] } return fmt.Sprintf("%d", t) } // True if token type is an operator used in mathematical or boolean expressions. -func isExprOperator(typ TokenType) bool { +func IsExprOperator(typ TokenType) bool { return typ > begin_tok_operator && typ < end_tok_operator } // True if token type is an operator used in mathematical expressions. -func isMathOperator(typ TokenType) bool { +func IsMathOperator(typ TokenType) bool { return typ > begin_tok_operator_math && typ < end_tok_operator_math } // True if token type is an operator used in comparisons. -func isCompOperator(typ TokenType) bool { +func IsCompOperator(typ TokenType) bool { return typ > begin_tok_operator_comp && typ < end_tok_operator_comp } diff --git a/tick/parser.go b/tick/parser.go index 5848431927..0f4a6178ff 100644 --- a/tick/parser.go +++ b/tick/parser.go @@ -342,12 +342,12 @@ var precedence = [...]int{ // https://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code func (p *parser) precedence(lhs Node, minP int) Node { look := p.peek() - for isExprOperator(look.typ) && precedence[look.typ] >= minP { + for IsExprOperator(look.typ) && precedence[look.typ] >= minP { op := p.next() rhs := p.primary(nil) look = p.peek() // left-associative - for isExprOperator(look.typ) && precedence[look.typ] > precedence[op.typ] { + for IsExprOperator(look.typ) && precedence[look.typ] > precedence[op.typ] { rhs = p.precedence(rhs, precedence[look.typ]) look = p.peek() } @@ -389,7 +389,7 @@ func (p *parser) lparameters() (args []Node) { func (p *parser) lparameter() (n Node) { n = p.primary(nil) - if isExprOperator(p.peek().typ) { + if IsExprOperator(p.peek().typ) { n = p.precedence(n, 0) } return diff --git a/tick/stateful/eval_binary_node.go b/tick/stateful/eval_binary_node.go new file mode 100644 index 0000000000..2fcdb3dd8a --- /dev/null +++ b/tick/stateful/eval_binary_node.go @@ -0,0 +1,332 @@ +package stateful + +import ( + "errors" + "fmt" + "regexp" + + "github.com/influxdata/kapacitor/tick" +) + +type resultContainer struct { + // IsBoolValue is kinda reundandant, but we want to be consistent with + // the numeric fields + IsBoolValue bool + BoolValue bool + + IsInt64Value bool + Int64Value int64 + + IsFloat64Value bool + Float64Value float64 +} + +// this function shouldn't be used! only for throwing details error messages! +func (rc resultContainer) value() interface{} { + switch { + case rc.IsBoolValue: + return rc.BoolValue + + case rc.IsInt64Value: + return rc.Int64Value + + case rc.IsFloat64Value: + return rc.Float64Value + } + + return nil +} + +// ErrSide wraps the error in the evaluation, we use this error to indicate the origin of the error +// left side or right side +type ErrSide struct { + error + IsLeftSide bool + IsRightSide bool +} + +// Evaluation functions +type evaluationFn func(scope *tick.Scope, executionState ExecutionState, left, right NodeEvaluator) (resultContainer, *ErrSide) + +// EvalBinaryNode is stateful expression which +// is evaluated using "expression trees" instead of stack based interpreter +type EvalBinaryNode struct { + operator tick.TokenType + + // This the evaluation function for this comparison node + // this function can be: + // *) specialized function + // *) dynamic function - which will delegate to specialized function + evaluationFn evaluationFn + + // Saving a cache version NodeEvaluator so we don't need to cast + // in every EvalBool call + leftSideEvaluator NodeEvaluator + leftSideType ValueType + + rightSideEvaluator NodeEvaluator + rightSideType ValueType + + // Return type + returnType ValueType +} + +func NewEvalBinaryNode(node *tick.BinaryNode) (*EvalBinaryNode, error) { + expression := &EvalBinaryNode{ + operator: node.Operator, + returnType: getConstantNodeType(node), + } + + leftSideEvaluator, err := createNodeEvaluator(node.Left) + if err != nil { + return nil, fmt.Errorf("Failed to handle left node: %v", err) + } + + rightSideEvaluator, err := createNodeEvaluator(node.Right) + if err != nil { + return nil, fmt.Errorf("Failed to handle right node: %v", err) + } + + expression.leftSideEvaluator = leftSideEvaluator + expression.rightSideEvaluator = rightSideEvaluator + + if isDynamicNode(node.Left) || isDynamicNode(node.Right) { + expression.evaluationFn = expression.evaluateDynamicNode + } else { + expression.leftSideType = getConstantNodeType(node.Left) + expression.rightSideType = getConstantNodeType(node.Right) + + expression.evaluationFn = expression.findEvaluationFn(expression.leftSideType, expression.rightSideType) + } + + return expression, nil +} + +func (n *EvalBinaryNode) Type(scope ReadOnlyScope, executionState ExecutionState) (ValueType, error) { + return findNodeTypes(n.returnType, []NodeEvaluator{n.leftSideEvaluator, n.rightSideEvaluator}, scope, executionState) +} + +func (n *EvalBinaryNode) EvalRegex(scope *tick.Scope, executionState ExecutionState) (*regexp.Regexp, error) { + return nil, ErrTypeGuardFailed{RequestedType: TRegex, ActualType: n.returnType} +} + +func (n *EvalBinaryNode) EvalString(scope *tick.Scope, executionState ExecutionState) (string, error) { + return "", ErrTypeGuardFailed{RequestedType: TString, ActualType: n.returnType} +} + +// EvalBool executes the expression based on eval bool +func (e *EvalBinaryNode) EvalBool(scope *tick.Scope, executionState ExecutionState) (bool, error) { + result, err := e.eval(scope, executionState) + if err != nil { + return false, err.error + } + + if result.IsBoolValue { + return result.BoolValue, nil + } + + return false, fmt.Errorf("expression returned unexpected type %T", result.value()) +} + +// EvalNum executes the expression based on eval numeric +func (e *EvalBinaryNode) EvalFloat(scope *tick.Scope, executionState ExecutionState) (float64, error) { + result, err := e.eval(scope, executionState) + if err != nil { + return float64(0), err.error + } + + if result.IsFloat64Value { + return result.Float64Value, nil + } + + if result.IsInt64Value { + return float64(0), ErrTypeGuardFailed{RequestedType: TFloat64, ActualType: TInt64} + } + + return float64(0), ErrTypeGuardFailed{RequestedType: TFloat64, ActualType: e.returnType} +} + +func (e *EvalBinaryNode) EvalInt(scope *tick.Scope, executionState ExecutionState) (int64, error) { + result, err := e.eval(scope, executionState) + if err != nil { + return int64(0), err.error + } + + if result.IsInt64Value { + return result.Int64Value, nil + } + + if result.IsFloat64Value { + return int64(0), ErrTypeGuardFailed{RequestedType: TInt64, ActualType: TFloat64} + } + + return int64(0), ErrTypeGuardFailed{RequestedType: TInt64, ActualType: e.returnType} + +} + +func (e *EvalBinaryNode) eval(scope *tick.Scope, executionState ExecutionState) (resultContainer, *ErrSide) { + if e.evaluationFn == nil { + err := e.determineError(scope, executionState, e.operator, e.leftSideEvaluator, e.rightSideEvaluator) + return boolFalseResultContainer, &ErrSide{error: err} + } + + evaluationResult, err := e.evaluationFn(scope, executionState, e.leftSideEvaluator, e.rightSideEvaluator) + + // This case can in dynamic nodes, + // for example: RefNode("value") > NumberNode("float64") + // in the first evaluation "value" is float64 so we will have float64 > float64 comparison fn + // after the first evaluation, let's assume that "value" is changed to int64 - we need to change + // the comparison fn + if err != nil { + if typeGuardErr, isTypeGuardError := err.error.(ErrTypeGuardFailed); isTypeGuardError { + // Fix the type info, thanks to the type guard info + if err.IsLeftSide { + e.leftSideType = typeGuardErr.ActualType + } + + if err.IsRightSide { + e.rightSideType = typeGuardErr.ActualType + } + + // re-find the evaluation fn + e.evaluationFn = e.findEvaluationFn(e.leftSideType, e.rightSideType) + + // recurse (so we handle nil evaluationFn, and etc) + return e.eval(scope, executionState) + } + } + + return evaluationResult, err +} + +// evaluateDynamicNode fetches the value of the right and left node at evaluation time (aka "runtime") +// and find the matching evaluation function for the givne types - this is where the "specialisation" happens. +func (e *EvalBinaryNode) evaluateDynamicNode(scope *tick.Scope, executionState ExecutionState, left, right NodeEvaluator) (resultContainer, *ErrSide) { + var leftSideType ValueType + var rightSideType ValueType + var err error + + // For getting the type we must pass new execution state, since the node can be stateful (like function call) + // and on the second in the specialiszation we might loose the correct state + // For example: "count() == 1" + // 1. we evaluate the left side and counter is 1 (upper ^ in this function) + // 2. we evaluate the second time in "EvalBool" + typeExecutionState := CreateExecutionState() + + if leftSideType, err = left.Type(scope, typeExecutionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if rightSideType, err = right.Type(scope, typeExecutionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + e.leftSideType = leftSideType + e.rightSideType = rightSideType + + e.evaluationFn = e.findEvaluationFn(e.leftSideType, e.rightSideType) + + return e.eval(scope, executionState) +} + +func (e *EvalBinaryNode) determineError(scope *tick.Scope, executionState ExecutionState, operator tick.TokenType, left, right NodeEvaluator) error { + // Validate the evaluation parameters: + // *) not support types like arrays + // *) not comparison operator + // *) invalid operator for the given type + + typeExecutionState := CreateExecutionState() + + leftValueType, err := left.Type(scope, typeExecutionState) + if err != nil { + return fmt.Errorf("Can't get the type of the left node: %s", err) + } + leftTypeName := leftValueType.String() + + if leftValueType == InvalidType { + return errors.New("left value is invalid value type") + } + + rightValueType, err := right.Type(scope, typeExecutionState) + if err != nil { + return fmt.Errorf("Can't get the type of the right node: %s", err) + } + rightTypeName := rightValueType.String() + + if rightValueType == InvalidType { + return errors.New("right value is invalid value type") + } + + err = isValidOperator(e.operator, leftValueType, rightValueType) + if err != nil { + return err + } + + return fmt.Errorf("mismatched type to binary operator. got %s %v %s. see bool(), int(), float()", leftTypeName, e.operator, rightTypeName) +} + +func (e *EvalBinaryNode) findEvaluationFn(leftType, rightType ValueType) evaluationFn { + return evaluationFuncs[operationKey{operator: e.operator, leftType: leftType, rightType: rightType}] +} + +// Type to comparison operator - for comparison operator validation (see: isValidBinaryOperator) +// The key is value type where the value is set of TokenType +var typeToBinaryOperators = (func() map[ValueType]map[tick.TokenType]bool { + // This map is built at "runtime" because we don't to have tight coupling + // every time we had new "comparison operator" / "math operator" to update this map + // and the performance cost is neglibile for doing so. + + result := make(map[ValueType]map[tick.TokenType]bool, 0) + + for opKey := range evaluationFuncs { + typeSet, exists := result[opKey.leftType] + + if !exists { + result[opKey.leftType] = make(map[tick.TokenType]bool, 0) + typeSet = result[opKey.leftType] + } + + typeSet[opKey.operator] = true + result[opKey.leftType] = typeSet + } + + return result +})() + +// isValidOperator returns whether the operator and left/right nodes are valid for comparison, if not +// false will be returned with correct error message +func isValidOperator(operator tick.TokenType, leftNodeType, rightNodeType ValueType) error { + if !tick.IsExprOperator(operator) { + return fmt.Errorf("return: unknown operator %v", operator) + } + + var nodeType ValueType + + // Only for TRegex we determine the validity of the operator by the right node + if rightNodeType == TRegex { + nodeType = rightNodeType + } else { + nodeType = leftNodeType + } + + isValid := typeToBinaryOperators[nodeType][operator] + if !isValid { + return fmt.Errorf("invalid %s %s operator %v", nodeType.String(), operatorType(operator), operator) + } + + return nil +} + +func operatorType(operator tick.TokenType) string { + if tick.IsMathOperator(operator) { + return "math" + } + + if tick.IsCompOperator(operator) { + return "comparison" + } + + // Actually, we shouldn't get here.. because this function is called only + // after the operator validation! + return "INVALID" +} diff --git a/tick/stateful/eval_bool_node.go b/tick/stateful/eval_bool_node.go new file mode 100644 index 0000000000..d5ec442e78 --- /dev/null +++ b/tick/stateful/eval_bool_node.go @@ -0,0 +1,35 @@ +package stateful + +import ( + "regexp" + + "github.com/influxdata/kapacitor/tick" +) + +type EvalBoolNode struct { + Node *tick.BoolNode +} + +func (n *EvalBoolNode) Type(scope ReadOnlyScope, executionState ExecutionState) (ValueType, error) { + return TBool, nil +} + +func (n *EvalBoolNode) EvalBool(scope *tick.Scope, executionState ExecutionState) (bool, error) { + return n.Node.Bool, nil +} + +func (n *EvalBoolNode) EvalFloat(scope *tick.Scope, executionState ExecutionState) (float64, error) { + return float64(0), ErrTypeGuardFailed{RequestedType: TFloat64, ActualType: TBool} +} + +func (n *EvalBoolNode) EvalInt(scope *tick.Scope, executionState ExecutionState) (int64, error) { + return int64(0), ErrTypeGuardFailed{RequestedType: TInt64, ActualType: TBool} +} + +func (n *EvalBoolNode) EvalString(scope *tick.Scope, executionState ExecutionState) (string, error) { + return "", ErrTypeGuardFailed{RequestedType: TString, ActualType: TBool} +} + +func (n *EvalBoolNode) EvalRegex(scope *tick.Scope, executionState ExecutionState) (*regexp.Regexp, error) { + return nil, ErrTypeGuardFailed{RequestedType: TRegex, ActualType: TBool} +} diff --git a/tick/stateful/eval_float_node.go b/tick/stateful/eval_float_node.go new file mode 100644 index 0000000000..3b4e9687e8 --- /dev/null +++ b/tick/stateful/eval_float_node.go @@ -0,0 +1,34 @@ +package stateful + +import ( + "regexp" + + "github.com/influxdata/kapacitor/tick" +) + +type EvalFloatNode struct { + Float64 float64 +} + +func (n *EvalFloatNode) Type(scope ReadOnlyScope, executionState ExecutionState) (ValueType, error) { + return TFloat64, nil +} + +func (n *EvalFloatNode) EvalFloat(scope *tick.Scope, executionState ExecutionState) (float64, error) { + return n.Float64, nil +} + +func (n *EvalFloatNode) EvalInt(scope *tick.Scope, executionState ExecutionState) (int64, error) { + return int64(0), ErrTypeGuardFailed{RequestedType: TFloat64, ActualType: TFloat64} +} + +func (n *EvalFloatNode) EvalString(scope *tick.Scope, executionState ExecutionState) (string, error) { + return "", ErrTypeGuardFailed{RequestedType: TString, ActualType: TFloat64} +} + +func (n *EvalFloatNode) EvalBool(scope *tick.Scope, executionState ExecutionState) (bool, error) { + return false, ErrTypeGuardFailed{RequestedType: TBool, ActualType: TFloat64} +} +func (n *EvalFloatNode) EvalRegex(scope *tick.Scope, executionState ExecutionState) (*regexp.Regexp, error) { + return nil, ErrTypeGuardFailed{RequestedType: TRegex, ActualType: TFloat64} +} diff --git a/tick/stateful/eval_function_node.go b/tick/stateful/eval_function_node.go new file mode 100644 index 0000000000..29149287a8 --- /dev/null +++ b/tick/stateful/eval_function_node.go @@ -0,0 +1,160 @@ +package stateful + +import ( + "fmt" + "reflect" + "regexp" + + "github.com/influxdata/kapacitor/tick" +) + +type EvalFunctionNode struct { + funcName string + argsEvaluators []NodeEvaluator +} + +func NewEvalFunctionNode(funcNode *tick.FunctionNode) (*EvalFunctionNode, error) { + evalFuncNode := &EvalFunctionNode{ + funcName: funcNode.Func, + } + + evalFuncNode.argsEvaluators = make([]NodeEvaluator, 0, len(funcNode.Args)) + for i, argNode := range funcNode.Args { + argEvaluator, err := createNodeEvaluator(argNode) + if err != nil { + return nil, fmt.Errorf("Failed to handle %v argument: %v", i+1, err) + } + + evalFuncNode.argsEvaluators = append(evalFuncNode.argsEvaluators, argEvaluator) + } + + return evalFuncNode, nil +} + +func (n *EvalFunctionNode) Type(scope ReadOnlyScope, executionState ExecutionState) (ValueType, error) { + // PERF: today we are evaluating the function, it will be much faster if will type info the function it self + result, err := n.callFunction(scope.(*tick.Scope), executionState) + if err != nil { + return InvalidType, err + } + + // We can't cache here the result (although it's very tempting ;)) + // because can't trust function to return always the same consistent type + return valueTypeOf(reflect.TypeOf(result)), nil +} + +// callFunction - core method for evaluating function where all NodeEvaluator methods should use +func (n *EvalFunctionNode) callFunction(scope *tick.Scope, executionState ExecutionState) (interface{}, error) { + args := make([]interface{}, 0, len(n.argsEvaluators)) + for i, argEvaluator := range n.argsEvaluators { + value, err := eval(argEvaluator, scope, executionState) + if err != nil { + return nil, fmt.Errorf("Failed to handle %v argument: %v", i+1, err) + } + + args = append(args, value) + } + + f := executionState.Funcs[n.funcName] + + if f == nil { + return nil, fmt.Errorf("undefined function: %q", n.funcName) + } + + ret, err := f.Call(args...) + if err != nil { + return nil, fmt.Errorf("error calling %q: %s", n.funcName, err) + } + + return ret, nil +} + +func (n *EvalFunctionNode) EvalRegex(scope *tick.Scope, executionState ExecutionState) (*regexp.Regexp, error) { + refValue, err := n.callFunction(scope, executionState) + if err != nil { + return nil, err + } + + if regexValue, isRegex := refValue.(*regexp.Regexp); isRegex { + return regexValue, nil + } + + return nil, ErrTypeGuardFailed{RequestedType: TRegex, ActualType: valueTypeOf(reflect.TypeOf(refValue))} +} + +func (n *EvalFunctionNode) EvalString(scope *tick.Scope, executionState ExecutionState) (string, error) { + refValue, err := n.callFunction(scope, executionState) + if err != nil { + return "", err + } + + if stringValue, isString := refValue.(string); isString { + return stringValue, nil + } + + return "", ErrTypeGuardFailed{RequestedType: TString, ActualType: valueTypeOf(reflect.TypeOf(refValue))} +} + +func (n *EvalFunctionNode) EvalFloat(scope *tick.Scope, executionState ExecutionState) (float64, error) { + refValue, err := n.callFunction(scope, executionState) + if err != nil { + return float64(0), err + } + + if float64Value, isFloat64 := refValue.(float64); isFloat64 { + return float64Value, nil + } + + return float64(0), ErrTypeGuardFailed{RequestedType: TFloat64, ActualType: valueTypeOf(reflect.TypeOf(refValue))} +} + +func (n *EvalFunctionNode) EvalInt(scope *tick.Scope, executionState ExecutionState) (int64, error) { + refValue, err := n.callFunction(scope, executionState) + if err != nil { + return int64(0), err + } + + if int64Value, isInt64 := refValue.(int64); isInt64 { + return int64Value, nil + } + + return int64(0), ErrTypeGuardFailed{RequestedType: TInt64, ActualType: valueTypeOf(reflect.TypeOf(refValue))} +} + +func (n *EvalFunctionNode) EvalBool(scope *tick.Scope, executionState ExecutionState) (bool, error) { + refValue, err := n.callFunction(scope, executionState) + if err != nil { + return false, err + } + + if boolValue, isBool := refValue.(bool); isBool { + return boolValue, nil + } + + return false, ErrTypeGuardFailed{RequestedType: TBool, ActualType: valueTypeOf(reflect.TypeOf(refValue))} +} + +// eval - generic evaluation until we have reflection/introspection capabillities so we can know the type of args +// and return type, we can remove this entirely +func eval(n NodeEvaluator, scope *tick.Scope, executionState ExecutionState) (interface{}, error) { + retType, err := n.Type(scope, CreateExecutionState()) + if err != nil { + return nil, err + } + + switch retType { + case TBool: + return n.EvalBool(scope, executionState) + case TInt64: + return n.EvalInt(scope, executionState) + case TFloat64: + return n.EvalFloat(scope, executionState) + case TString: + return n.EvalString(scope, executionState) + case TRegex: + return n.EvalRegex(scope, executionState) + default: + return nil, fmt.Errorf("expression returned unexpected type %s", retType) + } + +} diff --git a/tick/stateful/eval_function_node_test.go b/tick/stateful/eval_function_node_test.go new file mode 100644 index 0000000000..38af084271 --- /dev/null +++ b/tick/stateful/eval_function_node_test.go @@ -0,0 +1,328 @@ +package stateful_test + +import ( + "errors" + "strings" + "testing" + + "github.com/influxdata/kapacitor/tick" + "github.com/influxdata/kapacitor/tick/stateful" +) + +func TestEvalFunctionNode_InvalidNodeAsArgument(t *testing.T) { + evaluator, err := stateful.NewEvalFunctionNode(&tick.FunctionNode{ + Args: []tick.Node{&tick.CommentNode{}}, + }) + + expectedError := errors.New("Failed to handle 1 argument: Given node type is not valid evaluation node: *tick.CommentNode") + + if err == nil && evaluator != nil { + t.Error("Expected an error, but go nil error and evaluator") + return + } + + if err.Error() != expectedError.Error() { + t.Errorf("Got unexpected error:\ngot: %v\nexpected: %v\n", err, expectedError) + } +} + +func TestEvalFunctionNode_FailedToEvaluateArgumentNodes(t *testing.T) { + // bool("value"), where in our case "value" won't exist + evaluator, err := stateful.NewEvalFunctionNode(&tick.FunctionNode{ + Func: "bool", + Args: []tick.Node{ + &tick.ReferenceNode{Reference: "value"}, + }, + }) + + if err != nil { + t.Fatalf("Failed to create node evaluator: %v", err) + } + + result, err := evaluator.EvalBool(tick.NewScope(), stateful.CreateExecutionState()) + + expectedError := errors.New("Failed to handle 1 argument: name \"value\" is undefined. Names in scope:") + if err == nil { + t.Errorf("Expected an error, but got nil error and result (%t)", result) + return + } + + if strings.TrimSpace(err.Error()) != expectedError.Error() { + t.Errorf("Got unexpected error:\ngot: %v\nexpected: %v\n", err, expectedError) + } +} + +func TestEvalFunctionNode_UndefinedFunction(t *testing.T) { + evaluator, err := stateful.NewEvalFunctionNode(&tick.FunctionNode{ + Func: "yosi_the_king", + Args: []tick.Node{}, + }) + + if err != nil { + t.Fatalf("Failed to create node evaluator: %v", err) + } + + result, err := evaluator.EvalBool(tick.NewScope(), stateful.CreateExecutionState()) + + expectedError := errors.New("undefined function: \"yosi_the_king\"") + if err == nil { + t.Errorf("Expected an error, but got nil error and result (%t)", result) + return + } + + if strings.TrimSpace(err.Error()) != expectedError.Error() { + t.Errorf("Got unexpected error:\ngot: %v\nexpected: %v\n", err, expectedError) + } +} + +func TestEvalFunctionNode_AryMismatch(t *testing.T) { + evaluator, err := stateful.NewEvalFunctionNode(&tick.FunctionNode{ + Func: "sigma", + Args: []tick.Node{&tick.BoolNode{Bool: true}}, + }) + + if err != nil { + t.Fatalf("Failed to create node evaluator: %v", err) + } + + result, err := evaluator.EvalFloat(tick.NewScope(), stateful.CreateExecutionState()) + + expectedError := errors.New("error calling \"sigma\": value is not a float") + if err == nil { + t.Errorf("Expected an error, but got nil error and result (%v)", result) + return + } + + if strings.TrimSpace(err.Error()) != expectedError.Error() { + t.Errorf("Got unexpected error:\ngot: %v\nexpected: %v\n", err, expectedError) + } +} + +func TestEvalFunctionNode_EvalBool_Sanity(t *testing.T) { + evaluator, err := stateful.NewEvalFunctionNode(&tick.FunctionNode{ + Func: "bool", + Args: []tick.Node{&tick.StringNode{Literal: "true"}}, + }) + + if err != nil { + t.Fatalf("Failed to create node evaluator: %v", err) + } + + result, err := evaluator.EvalBool(tick.NewScope(), stateful.CreateExecutionState()) + if err != nil { + t.Errorf("Expected a result, but got error - %v", err) + return + } + + if !result { + t.Errorf("unexpected result: got: %v, expected: true", result) + } +} + +func TestEvalFunctionNode_EvalInt64_Sanity(t *testing.T) { + evaluator, err := stateful.NewEvalFunctionNode(&tick.FunctionNode{ + Func: "count", + Args: []tick.Node{}, + }) + + if err != nil { + t.Fatalf("Failed to create node evaluator: %v", err) + } + + result, err := evaluator.EvalInt(tick.NewScope(), stateful.CreateExecutionState()) + if err != nil { + t.Errorf("Expected a result, but got error - %v", err) + return + } + + if result != int64(1) { + t.Errorf("unexpected result: got: %T(%v), expected: int64(1)", result, result) + } +} + +func TestEvalFunctionNode_EvalInt64_KeepConsistentState(t *testing.T) { + evaluator, err := stateful.NewEvalFunctionNode(&tick.FunctionNode{ + Func: "count", + Args: []tick.Node{}, + }) + + if err != nil { + t.Fatalf("Failed to create node evaluator: %v", err) + } + + executionState := stateful.CreateExecutionState() + + // first evaluation + result, err := evaluator.EvalInt(tick.NewScope(), executionState) + if err != nil { + t.Errorf("first evaluation: Expected a result, but got error - %v", err) + return + } + + if result != int64(1) { + t.Errorf("first evaluation: unexpected result: got: %T(%v), expected: int64(1)", result, result) + } + + // second evaluation + result, err = evaluator.EvalInt(tick.NewScope(), executionState) + if err != nil { + t.Errorf("second evaluation: Expected a result, but got error - %v", err) + return + } + + if result != int64(2) { + t.Errorf("second evaluation: unexpected result: got: %T(%v), expected: int64(2)", result, result) + } +} + +func TestEvalFunctionNode_EvalInt64_Reset(t *testing.T) { + evaluator, err := stateful.NewEvalFunctionNode(&tick.FunctionNode{ + Func: "count", + Args: []tick.Node{}, + }) + + if err != nil { + t.Fatalf("Failed to create node evaluator: %v", err) + } + + executionState := stateful.CreateExecutionState() + + // first evaluation + result, err := evaluator.EvalInt(tick.NewScope(), executionState) + if err != nil { + t.Errorf("first evaluation: Expected a result, but got error - %v", err) + return + } + + if result != int64(1) { + t.Errorf("first evaluation: unexpected result: got: %T(%v), expected: int64(1)", result, result) + } + + // reset (we don't call ResetAll on ExecutionState in order to isolate the scope of this test) + for _, fnc := range executionState.Funcs { + fnc.Reset() + } + + // second evaluation + result, err = evaluator.EvalInt(tick.NewScope(), executionState) + if err != nil { + t.Errorf("second evaluation (after reset): Expected a result, but got error - %v", err) + return + } + + if result != int64(1) { + t.Errorf("second evaluation (after reset): unexpected result: got: %T(%v), expected: int64(1)", result, result) + } +} + +func TestEvalFunctionNode_EvalFloat64_Sanity(t *testing.T) { + evaluator, err := stateful.NewEvalFunctionNode(&tick.FunctionNode{ + Func: "abs", + Args: []tick.Node{ + &tick.NumberNode{ + IsFloat: true, + Float64: float64(-1), + }, + }, + }) + + if err != nil { + t.Fatalf("Failed to create node evaluator: %v", err) + } + + result, err := evaluator.EvalFloat(tick.NewScope(), stateful.CreateExecutionState()) + if err != nil { + t.Errorf("Expected a result, but got error - %v", err) + return + } + + if result != float64(1) { + t.Errorf("unexpected result: got: %T(%v), expected: float64(1)", result, result) + } +} + +func TestEvalFunctionNode_ComplexNodes(t *testing.T) { + // pow("x" * 2, -"y") => pow(4, -1) + evaluator, err := stateful.NewEvalFunctionNode(&tick.FunctionNode{ + Func: "pow", + Args: []tick.Node{ + // "x" * 2 + &tick.BinaryNode{ + Operator: tick.TokenMult, + Left: &tick.ReferenceNode{Reference: "x"}, + Right: &tick.NumberNode{IsFloat: true, Float64: float64(2)}, + }, + + // -"y" + &tick.UnaryNode{ + Operator: tick.TokenMinus, + Node: &tick.ReferenceNode{Reference: "y"}, + }, + }, + }) + + if err != nil { + t.Fatalf("Failed to create node evaluator: %v", err) + } + + scope := tick.NewScope() + scope.Set("x", float64(2)) + scope.Set("y", float64(1)) + + result, err := evaluator.EvalFloat(scope, stateful.CreateExecutionState()) + if err != nil { + t.Errorf("Expected a result, but got error - %v", err) + return + } + + if result != float64(0.25) { + t.Errorf("unexpected result: got: %T(%v), expected: float64(0.25)", result, result) + } +} + +func TestStatefulExpression_Integration_EvalBool_SanityCallingFunction(t *testing.T) { + scope := tick.NewScope() + + se := mustCompileExpression(t, &tick.BinaryNode{ + Operator: tick.TokenEqual, + Left: &tick.FunctionNode{ + Func: "count", + }, + Right: &tick.NumberNode{ + IsInt: true, + Int64: 1, + }, + }) + + result, err := se.EvalBool(scope) + if err != nil { + t.Error(err) + } + + if !result { + t.Errorf("first evaluation: unexpected result: got: %v, expected: true", result) + } + + // Second time, to make sure that count() increases the value + result, err = se.EvalBool(scope) + if err != nil { + t.Error(err) + } + + if result { + t.Errorf("second evaluation: unexpected result: got: %v, expected: false", result) + } + + // reset the expression + se.Reset() + + result, err = se.EvalBool(scope) + if err != nil { + t.Error(err) + } + + if !result { + t.Errorf("last evaluation after reset: unexpected result: got: %v, expected: true", result) + } + +} diff --git a/tick/stateful/eval_int_node.go b/tick/stateful/eval_int_node.go new file mode 100644 index 0000000000..3ed152a368 --- /dev/null +++ b/tick/stateful/eval_int_node.go @@ -0,0 +1,34 @@ +package stateful + +import ( + "regexp" + + "github.com/influxdata/kapacitor/tick" +) + +type EvalIntNode struct { + Int64 int64 +} + +func (n *EvalIntNode) Type(scope ReadOnlyScope, executionState ExecutionState) (ValueType, error) { + return TInt64, nil +} + +func (n *EvalIntNode) EvalFloat(scope *tick.Scope, executionState ExecutionState) (float64, error) { + return float64(0), ErrTypeGuardFailed{RequestedType: TFloat64, ActualType: TInt64} +} + +func (n *EvalIntNode) EvalInt(scope *tick.Scope, executionState ExecutionState) (int64, error) { + return n.Int64, nil +} + +func (n *EvalIntNode) EvalString(scope *tick.Scope, executionState ExecutionState) (string, error) { + return "", ErrTypeGuardFailed{RequestedType: TString, ActualType: TInt64} +} + +func (n *EvalIntNode) EvalBool(scope *tick.Scope, executionState ExecutionState) (bool, error) { + return false, ErrTypeGuardFailed{RequestedType: TBool, ActualType: TInt64} +} +func (n *EvalIntNode) EvalRegex(scope *tick.Scope, executionState ExecutionState) (*regexp.Regexp, error) { + return nil, ErrTypeGuardFailed{RequestedType: TRegex, ActualType: TInt64} +} diff --git a/tick/stateful/eval_reference_node.go b/tick/stateful/eval_reference_node.go new file mode 100644 index 0000000000..f659f2899e --- /dev/null +++ b/tick/stateful/eval_reference_node.go @@ -0,0 +1,96 @@ +package stateful + +import ( + "reflect" + "regexp" + + "github.com/influxdata/kapacitor/tick" +) + +type EvalReferenceNode struct { + Node *tick.ReferenceNode +} + +// getReferenceValue - core method for evaluating function where all NodeEvaluator methods should use +func (n *EvalReferenceNode) getReferenceValue(scope *tick.Scope, executionState ExecutionState) (interface{}, error) { + value, err := scope.Get(n.Node.Reference) + if err != nil { + return nil, err + } + + return value, nil +} + +func (n *EvalReferenceNode) Type(scope ReadOnlyScope, executionState ExecutionState) (ValueType, error) { + value, err := n.getReferenceValue(scope.(*tick.Scope), executionState) + if err != nil { + return InvalidType, err + } + + return valueTypeOf(reflect.TypeOf(value)), nil +} + +func (n *EvalReferenceNode) EvalRegex(scope *tick.Scope, executionState ExecutionState) (*regexp.Regexp, error) { + refValue, err := n.getReferenceValue(scope, executionState) + if err != nil { + return nil, err + } + + if regexValue, isRegex := refValue.(*regexp.Regexp); isRegex { + return regexValue, nil + } + + return nil, ErrTypeGuardFailed{RequestedType: TRegex, ActualType: valueTypeOf(reflect.TypeOf(refValue))} +} + +func (n *EvalReferenceNode) EvalString(scope *tick.Scope, executionState ExecutionState) (string, error) { + refValue, err := n.getReferenceValue(scope, executionState) + if err != nil { + return "", err + } + + if stringValue, isString := refValue.(string); isString { + return stringValue, nil + } + + return "", ErrTypeGuardFailed{RequestedType: TString, ActualType: valueTypeOf(reflect.TypeOf(refValue))} +} + +func (n *EvalReferenceNode) EvalFloat(scope *tick.Scope, executionState ExecutionState) (float64, error) { + refValue, err := n.getReferenceValue(scope, executionState) + if err != nil { + return float64(0), err + } + + if float64Value, isFloat64 := refValue.(float64); isFloat64 { + return float64Value, nil + } + + return float64(0), ErrTypeGuardFailed{RequestedType: TFloat64, ActualType: valueTypeOf(reflect.TypeOf(refValue))} +} + +func (n *EvalReferenceNode) EvalInt(scope *tick.Scope, executionState ExecutionState) (int64, error) { + refValue, err := n.getReferenceValue(scope, executionState) + if err != nil { + return int64(0), err + } + + if int64Value, isInt64 := refValue.(int64); isInt64 { + return int64Value, nil + } + + return int64(0), ErrTypeGuardFailed{RequestedType: TInt64, ActualType: valueTypeOf(reflect.TypeOf(refValue))} +} + +func (n *EvalReferenceNode) EvalBool(scope *tick.Scope, executionState ExecutionState) (bool, error) { + refValue, err := n.getReferenceValue(scope, executionState) + if err != nil { + return false, err + } + + if boolValue, isBool := refValue.(bool); isBool { + return boolValue, nil + } + + return false, ErrTypeGuardFailed{RequestedType: TBool, ActualType: valueTypeOf(reflect.TypeOf(refValue))} +} diff --git a/tick/stateful/eval_regex_node.go b/tick/stateful/eval_regex_node.go new file mode 100644 index 0000000000..eca94cd3d9 --- /dev/null +++ b/tick/stateful/eval_regex_node.go @@ -0,0 +1,35 @@ +package stateful + +import ( + "regexp" + + "github.com/influxdata/kapacitor/tick" +) + +type EvalRegexNode struct { + Node *tick.RegexNode +} + +func (n *EvalRegexNode) Type(scope ReadOnlyScope, executionState ExecutionState) (ValueType, error) { + return TRegex, nil +} + +func (n *EvalRegexNode) EvalRegex(scope *tick.Scope, executionState ExecutionState) (*regexp.Regexp, error) { + return n.Node.Regex, nil +} + +func (n *EvalRegexNode) EvalString(scope *tick.Scope, executionState ExecutionState) (string, error) { + return "", ErrTypeGuardFailed{RequestedType: TString, ActualType: TRegex} +} + +func (n *EvalRegexNode) EvalFloat(scope *tick.Scope, executionState ExecutionState) (float64, error) { + return float64(0), ErrTypeGuardFailed{RequestedType: TFloat64, ActualType: TRegex} +} + +func (n *EvalRegexNode) EvalInt(scope *tick.Scope, executionState ExecutionState) (int64, error) { + return int64(0), ErrTypeGuardFailed{RequestedType: TInt64, ActualType: TRegex} +} + +func (n *EvalRegexNode) EvalBool(scope *tick.Scope, executionState ExecutionState) (bool, error) { + return false, ErrTypeGuardFailed{RequestedType: TBool, ActualType: TRegex} +} diff --git a/tick/stateful/eval_string_node.go b/tick/stateful/eval_string_node.go new file mode 100644 index 0000000000..85206b5b74 --- /dev/null +++ b/tick/stateful/eval_string_node.go @@ -0,0 +1,35 @@ +package stateful + +import ( + "regexp" + + "github.com/influxdata/kapacitor/tick" +) + +type EvalStringNode struct { + Node *tick.StringNode +} + +func (n *EvalStringNode) Type(scope ReadOnlyScope, executionState ExecutionState) (ValueType, error) { + return TString, nil +} + +func (n *EvalStringNode) EvalString(scope *tick.Scope, executionState ExecutionState) (string, error) { + return n.Node.Literal, nil +} + +func (n *EvalStringNode) EvalFloat(scope *tick.Scope, executionState ExecutionState) (float64, error) { + return float64(0), ErrTypeGuardFailed{RequestedType: TFloat64, ActualType: TString} +} + +func (n *EvalStringNode) EvalInt(scope *tick.Scope, executionState ExecutionState) (int64, error) { + return int64(0), ErrTypeGuardFailed{RequestedType: TInt64, ActualType: TString} +} + +func (n *EvalStringNode) EvalBool(scope *tick.Scope, executionState ExecutionState) (bool, error) { + return false, ErrTypeGuardFailed{RequestedType: TBool, ActualType: TString} +} + +func (n *EvalStringNode) EvalRegex(scope *tick.Scope, executionState ExecutionState) (*regexp.Regexp, error) { + return nil, ErrTypeGuardFailed{RequestedType: TRegex, ActualType: TString} +} diff --git a/tick/stateful/eval_unary_node.go b/tick/stateful/eval_unary_node.go new file mode 100644 index 0000000000..eb31b05b92 --- /dev/null +++ b/tick/stateful/eval_unary_node.go @@ -0,0 +1,84 @@ +package stateful + +import ( + "fmt" + "regexp" + + "github.com/influxdata/kapacitor/tick" +) + +type EvalUnaryNode struct { + nodeEvaluator NodeEvaluator + returnType ValueType +} + +func NewEvalUnaryNode(unaryNode *tick.UnaryNode) (*EvalUnaryNode, error) { + if !isValidUnaryOperator(unaryNode.Operator) { + return nil, fmt.Errorf("Invalid unary operator: %q", unaryNode.Operator) + } + + nodeEvaluator, err := createNodeEvaluator(unaryNode.Node) + if err != nil { + return nil, fmt.Errorf("Failed to handle node: %v", err) + } + + return &EvalUnaryNode{ + nodeEvaluator: nodeEvaluator, + returnType: getConstantNodeType(unaryNode), + }, nil +} + +func isValidUnaryOperator(operator tick.TokenType) bool { + return operator == tick.TokenNot || operator == tick.TokenMinus +} + +func (n *EvalUnaryNode) Type(scope ReadOnlyScope, executionState ExecutionState) (ValueType, error) { + return findNodeTypes(n.returnType, []NodeEvaluator{n.nodeEvaluator}, scope, executionState) +} + +func (n *EvalUnaryNode) EvalRegex(scope *tick.Scope, executionState ExecutionState) (*regexp.Regexp, error) { + return nil, ErrTypeGuardFailed{RequestedType: TRegex, ActualType: n.returnType} +} + +func (n *EvalUnaryNode) EvalString(scope *tick.Scope, executionState ExecutionState) (string, error) { + return "", ErrTypeGuardFailed{RequestedType: TString, ActualType: n.returnType} +} + +func (n *EvalUnaryNode) EvalFloat(scope *tick.Scope, executionState ExecutionState) (float64, error) { + if n.returnType.IsNumeric() { + result, err := n.nodeEvaluator.EvalFloat(scope, executionState) + if err != nil { + return float64(0), err + } + + return -1 * result, nil + } + + return float64(0), ErrTypeGuardFailed{RequestedType: TFloat64, ActualType: n.returnType} +} + +func (n *EvalUnaryNode) EvalInt(scope *tick.Scope, executionState ExecutionState) (int64, error) { + if n.returnType.IsNumeric() { + result, err := n.nodeEvaluator.EvalInt(scope, executionState) + if err != nil { + return int64(0), err + } + + return -1 * result, nil + } + + return int64(0), ErrTypeGuardFailed{RequestedType: TInt64, ActualType: n.returnType} +} + +func (n *EvalUnaryNode) EvalBool(scope *tick.Scope, executionState ExecutionState) (bool, error) { + if n.returnType == TBool { + result, err := n.nodeEvaluator.EvalBool(scope, executionState) + if err != nil { + return false, err + } + + return !result, nil + } + + return false, ErrTypeGuardFailed{RequestedType: TBool, ActualType: n.returnType} +} diff --git a/tick/stateful/eval_unary_node_test.go b/tick/stateful/eval_unary_node_test.go new file mode 100644 index 0000000000..326624ab1d --- /dev/null +++ b/tick/stateful/eval_unary_node_test.go @@ -0,0 +1,192 @@ +package stateful_test + +import ( + "errors" + "strings" + "testing" + + "github.com/influxdata/kapacitor/tick" + "github.com/influxdata/kapacitor/tick/stateful" +) + +func TestEvalUnaryNode_InvalidOperator(t *testing.T) { + evaluator, err := stateful.NewEvalUnaryNode(&tick.UnaryNode{ + Operator: tick.TokenAnd, + Node: &tick.BoolNode{Bool: true}, + }) + + expectedError := errors.New("Invalid unary operator: \"AND\"") + + if err == nil && evaluator != nil { + t.Error("Expected an error, but got nil error and evaluator") + return + } + + if err.Error() != expectedError.Error() { + t.Errorf("Got unexpected error:\ngot: %v\nexpected: %v\n", err, expectedError) + } +} + +func TestEvalUnaryNode_InvalidNode(t *testing.T) { + evaluator, err := stateful.NewEvalUnaryNode(&tick.UnaryNode{ + Operator: tick.TokenMinus, + Node: &tick.CommentNode{}, + }) + + expectedError := errors.New("Failed to handle node: Given node type is not valid evaluation node: *tick.CommentNode") + + if err == nil && evaluator != nil { + t.Error("Expected an error, but got nil error and evaluator") + return + } + + if err.Error() != expectedError.Error() { + t.Errorf("Got unexpected error:\ngot: %v\nexpected: %v\n", err, expectedError) + } +} + +func TestEvalUnaryNode_EvalBool_Sanity(t *testing.T) { + evaluator, err := stateful.NewEvalUnaryNode(&tick.UnaryNode{ + Operator: tick.TokenNot, + Node: &tick.BoolNode{ + Bool: false, + }, + }) + + if err != nil { + t.Fatalf("Failed to compile unary node: %v", err) + } + + result, err := evaluator.EvalBool(tick.NewScope(), stateful.CreateExecutionState()) + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + if !result { + t.Errorf("unexpected result: got: %t, expected: true", result) + } +} + +func TestEvalUnaryNode_EvalFloat64_Sanity(t *testing.T) { + evaluator, err := stateful.NewEvalUnaryNode(&tick.UnaryNode{ + Operator: tick.TokenMinus, + Node: &tick.NumberNode{ + IsFloat: true, + Float64: float64(12), + }, + }) + + if err != nil { + t.Fatalf("Failed to compile unary node: %v", err) + } + + result, err := evaluator.EvalFloat(tick.NewScope(), stateful.CreateExecutionState()) + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + if result != float64(-12) { + t.Errorf("unexpected result: got: %T(%v), expected: float64(-12)", result, result) + } +} + +func TestEvalUnaryNode_EvalInt64_Sanity(t *testing.T) { + evaluator, err := stateful.NewEvalUnaryNode(&tick.UnaryNode{ + Operator: tick.TokenMinus, + Node: &tick.NumberNode{ + IsInt: true, + Int64: int64(12), + }, + }) + + if err != nil { + t.Fatalf("Failed to compile unary node: %v", err) + } + + result, err := evaluator.EvalInt(tick.NewScope(), stateful.CreateExecutionState()) + if err != nil { + t.Errorf("Got unexpected error: %v", err) + } + + if result != int64(-12) { + t.Errorf("unexpected result: got: %T(%v), expected: int64(-12)", result, result) + } +} + +func TestEvalUnaryNode_EvalString_UnexpectedReturnType(t *testing.T) { + evaluator, err := stateful.NewEvalUnaryNode(&tick.UnaryNode{ + Operator: tick.TokenNot, + Node: &tick.BoolNode{ + Bool: false, + }, + }) + + if err != nil { + t.Fatalf("Failed to compile unary node: %v", err) + } + + result, err := evaluator.EvalString(tick.NewScope(), stateful.CreateExecutionState()) + + expectedError := errors.New("expression returned unexpected type boolean") + + if err == nil && result != "" { + t.Errorf("Expected an error, but got nil error and result: %v", result) + return + } + + if err.Error() != expectedError.Error() { + t.Errorf("Got unexpected error:\ngot: %v\nexpected: %v\n", err, expectedError) + } +} + +func TestEvalUnaryNode_EvalInt64_FailedToEvaluateNode(t *testing.T) { + evaluator, err := stateful.NewEvalUnaryNode(&tick.UnaryNode{ + Operator: tick.TokenMinus, + Node: &tick.BoolNode{ + Bool: false, + }, + }) + + if err != nil { + t.Fatalf("Failed to compile unary node: %v", err) + } + + result, err := evaluator.EvalInt(tick.NewScope(), stateful.CreateExecutionState()) + + expectedError := errors.New("expression returned unexpected type boolean") + + if err == nil && result != int64(0) { + t.Errorf("Expected an error, but got nil error and result: %v", result) + return + } + + if err.Error() != expectedError.Error() { + t.Errorf("Got unexpected error:\ngot: %v\nexpected: %v\n", err, expectedError) + } +} + +func TestEvalUnaryNode_EvalFloat64_FailedToEvaluateNode(t *testing.T) { + evaluator, err := stateful.NewEvalUnaryNode(&tick.UnaryNode{ + Operator: tick.TokenMinus, + Node: &tick.ReferenceNode{ + Reference: "value", + }, + }) + + if err != nil { + t.Fatalf("Failed to compile unary node: %v", err) + } + + result, err := evaluator.EvalFloat(tick.NewScope(), stateful.CreateExecutionState()) + + expectedError := errors.New("name \"value\" is undefined. Names in scope:") + + if err == nil && result != float64(0) { + t.Errorf("Expected an error, but got nil error and result: %v", result) + return + } + + if strings.TrimSpace(err.Error()) != expectedError.Error() { + t.Errorf("Got unexpected error:\ngot: %v\nexpected: %v\n", err, expectedError) + } +} diff --git a/tick/stateful/evaluation_funcs.go b/tick/stateful/evaluation_funcs.go new file mode 100644 index 0000000000..f46b190d5c --- /dev/null +++ b/tick/stateful/evaluation_funcs.go @@ -0,0 +1,789 @@ +package stateful + +import ( + "regexp" + + "github.com/influxdata/kapacitor/tick" +) + +type operationKey struct { + operator tick.TokenType + leftType ValueType + rightType ValueType +} + +var boolTrueResultContainer = resultContainer{BoolValue: true, IsBoolValue: true} +var boolFalseResultContainer = resultContainer{BoolValue: false, IsBoolValue: true} +var emptyResultContainer = resultContainer{} + +var evaluationFuncs = map[operationKey]evaluationFn{ + // ----------------------------------------- + // Comparison evaluation funcs + + operationKey{operator: tick.TokenAnd, leftType: TBool, rightType: TBool}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left bool + var right bool + var err error + + if left, err = leftNode.EvalBool(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + // Short circuit evaluation + if !left { + return boolFalseResultContainer, nil + } + + if right, err = rightNode.EvalBool(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left && right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenOr, leftType: TBool, rightType: TBool}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left bool + var right bool + var err error + + if left, err = leftNode.EvalBool(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + // Short circuit evaluation + if left { + return boolTrueResultContainer, nil + } + + if right, err = rightNode.EvalBool(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left || right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenEqual, leftType: TBool, rightType: TBool}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left bool + var right bool + var err error + + if left, err = leftNode.EvalBool(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalBool(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left == right, IsBoolValue: true}, nil + }, + + operationKey{operator: tick.TokenNotEqual, leftType: TBool, rightType: TBool}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left bool + var right bool + var err error + + if left, err = leftNode.EvalBool(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalBool(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left != right, IsBoolValue: true}, nil + }, + + operationKey{operator: tick.TokenLess, leftType: TFloat64, rightType: TFloat64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left float64 + var right float64 + var err error + + if left, err = leftNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left < right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenLessEqual, leftType: TInt64, rightType: TInt64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left int64 + var right int64 + var err error + + if left, err = leftNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left <= right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenNotEqual, leftType: TInt64, rightType: TFloat64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left int64 + var right float64 + var err error + + if left, err = leftNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: float64(left) != right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenGreaterEqual, leftType: TInt64, rightType: TInt64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left int64 + var right int64 + var err error + + if left, err = leftNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left >= right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenEqual, leftType: TFloat64, rightType: TFloat64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left float64 + var right float64 + var err error + + if left, err = leftNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left == right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenNotEqual, leftType: TInt64, rightType: TInt64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left int64 + var right int64 + var err error + + if left, err = leftNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left != right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenNotEqual, leftType: TFloat64, rightType: TFloat64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left float64 + var right float64 + var err error + + if left, err = leftNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left != right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenLessEqual, leftType: TFloat64, rightType: TFloat64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left float64 + var right float64 + var err error + + if left, err = leftNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left <= right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenEqual, leftType: TInt64, rightType: TInt64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left int64 + var right int64 + var err error + + if left, err = leftNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left == right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenGreater, leftType: TInt64, rightType: TInt64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left int64 + var right int64 + var err error + + if left, err = leftNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left > right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenGreater, leftType: TFloat64, rightType: TInt64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left float64 + var right int64 + var err error + + if left, err = leftNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left > float64(right), IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenGreaterEqual, leftType: TFloat64, rightType: TInt64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left float64 + var right int64 + var err error + + if left, err = leftNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left >= float64(right), IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenEqual, leftType: TFloat64, rightType: TInt64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left float64 + var right int64 + var err error + + if left, err = leftNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left == float64(right), IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenLessEqual, leftType: TInt64, rightType: TFloat64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left int64 + var right float64 + var err error + + if left, err = leftNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: float64(left) <= right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenEqual, leftType: TInt64, rightType: TFloat64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left int64 + var right float64 + var err error + + if left, err = leftNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: float64(left) == right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenNotEqual, leftType: TFloat64, rightType: TInt64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left float64 + var right int64 + var err error + + if left, err = leftNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left != float64(right), IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenLess, leftType: TFloat64, rightType: TInt64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left float64 + var right int64 + var err error + + if left, err = leftNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left < float64(right), IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenLess, leftType: TInt64, rightType: TInt64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left int64 + var right int64 + var err error + + if left, err = leftNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left < right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenGreaterEqual, leftType: TFloat64, rightType: TFloat64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left float64 + var right float64 + var err error + + if left, err = leftNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left >= right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenGreater, leftType: TFloat64, rightType: TFloat64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left float64 + var right float64 + var err error + + if left, err = leftNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left > right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenLessEqual, leftType: TFloat64, rightType: TInt64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left float64 + var right int64 + var err error + + if left, err = leftNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left <= float64(right), IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenGreaterEqual, leftType: TInt64, rightType: TFloat64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left int64 + var right float64 + var err error + + if left, err = leftNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: float64(left) >= right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenGreater, leftType: TInt64, rightType: TFloat64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left int64 + var right float64 + var err error + + if left, err = leftNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: float64(left) > right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenLess, leftType: TInt64, rightType: TFloat64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left int64 + var right float64 + var err error + + if left, err = leftNode.EvalInt(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalFloat(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: float64(left) < right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenGreater, leftType: TString, rightType: TString}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left string + var right string + var err error + + if left, err = leftNode.EvalString(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalString(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left > right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenGreaterEqual, leftType: TString, rightType: TString}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left string + var right string + var err error + + if left, err = leftNode.EvalString(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalString(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left >= right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenLess, leftType: TString, rightType: TString}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left string + var right string + var err error + + if left, err = leftNode.EvalString(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalString(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left < right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenLessEqual, leftType: TString, rightType: TString}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left string + var right string + var err error + + if left, err = leftNode.EvalString(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalString(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left <= right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenEqual, leftType: TString, rightType: TString}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left string + var right string + var err error + + if left, err = leftNode.EvalString(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalString(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left == right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenNotEqual, leftType: TString, rightType: TString}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left string + var right string + var err error + + if left, err = leftNode.EvalString(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalString(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: left != right, IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenRegexNotEqual, leftType: TString, rightType: TRegex}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left string + var right *regexp.Regexp + var err error + + if left, err = leftNode.EvalString(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalRegex(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: !right.MatchString(left), IsBoolValue: true}, nil + + }, + + operationKey{operator: tick.TokenRegexEqual, leftType: TString, rightType: TRegex}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left string + var right *regexp.Regexp + var err error + + if left, err = leftNode.EvalString(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalRegex(scope, executionState); err != nil { + return boolFalseResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{BoolValue: right.MatchString(left), IsBoolValue: true}, nil + + }, + + // ----------------------------------------- + // Math evaluation funcs + + operationKey{operator: tick.TokenPlus, leftType: TFloat64, rightType: TFloat64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left float64 + var right float64 + var err error + + if left, err = leftNode.EvalFloat(scope, executionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalFloat(scope, executionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{Float64Value: left + right, IsFloat64Value: true}, nil + }, + + operationKey{operator: tick.TokenMinus, leftType: TFloat64, rightType: TFloat64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left float64 + var right float64 + var err error + + if left, err = leftNode.EvalFloat(scope, executionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalFloat(scope, executionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{Float64Value: left - right, IsFloat64Value: true}, nil + }, + + operationKey{operator: tick.TokenMult, leftType: TFloat64, rightType: TFloat64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left float64 + var right float64 + var err error + + if left, err = leftNode.EvalFloat(scope, executionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalFloat(scope, executionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{Float64Value: left * right, IsFloat64Value: true}, nil + }, + + operationKey{operator: tick.TokenDiv, leftType: TFloat64, rightType: TFloat64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left float64 + var right float64 + var err error + + if left, err = leftNode.EvalFloat(scope, executionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalFloat(scope, executionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{Float64Value: left / right, IsFloat64Value: true}, nil + }, + + operationKey{operator: tick.TokenPlus, leftType: TInt64, rightType: TInt64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left int64 + var right int64 + var err error + + if left, err = leftNode.EvalInt(scope, executionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalInt(scope, executionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{Int64Value: left + right, IsInt64Value: true}, nil + }, + + operationKey{operator: tick.TokenMinus, leftType: TInt64, rightType: TInt64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left int64 + var right int64 + var err error + + if left, err = leftNode.EvalInt(scope, executionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalInt(scope, executionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{Int64Value: left - right, IsInt64Value: true}, nil + }, + + operationKey{operator: tick.TokenMult, leftType: TInt64, rightType: TInt64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left int64 + var right int64 + var err error + + if left, err = leftNode.EvalInt(scope, executionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalInt(scope, executionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{Int64Value: left * right, IsInt64Value: true}, nil + }, + + operationKey{operator: tick.TokenDiv, leftType: TInt64, rightType: TInt64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left int64 + var right int64 + var err error + + if left, err = leftNode.EvalInt(scope, executionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalInt(scope, executionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{Int64Value: left / right, IsInt64Value: true}, nil + }, + + operationKey{operator: tick.TokenMod, leftType: TInt64, rightType: TInt64}: func(scope *tick.Scope, executionState ExecutionState, leftNode, rightNode NodeEvaluator) (resultContainer, *ErrSide) { + var left int64 + var right int64 + var err error + + if left, err = leftNode.EvalInt(scope, executionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsLeftSide: true} + } + + if right, err = rightNode.EvalInt(scope, executionState); err != nil { + return emptyResultContainer, &ErrSide{error: err, IsRightSide: true} + } + + return resultContainer{Int64Value: left % right, IsInt64Value: true}, nil + }, +} diff --git a/tick/stateful/execution_state.go b/tick/stateful/execution_state.go new file mode 100644 index 0000000000..ae0ee2f9a1 --- /dev/null +++ b/tick/stateful/execution_state.go @@ -0,0 +1,22 @@ +package stateful + +import "github.com/influxdata/kapacitor/tick" + +// ExecutionState is auxiliary struct for data/context that needs to be passed +// to evaluation functions +type ExecutionState struct { + Funcs tick.Funcs +} + +func CreateExecutionState() ExecutionState { + return ExecutionState{ + Funcs: tick.NewFunctions(), + } +} + +func (ea ExecutionState) ResetAll() { + // Reset the functions + for _, f := range ea.Funcs { + f.Reset() + } +} diff --git a/tick/stateful/expr.go b/tick/stateful/expr.go new file mode 100644 index 0000000000..11b92792d6 --- /dev/null +++ b/tick/stateful/expr.go @@ -0,0 +1,93 @@ +package stateful + +import ( + "fmt" + + "github.com/influxdata/kapacitor/tick" +) + +// Expression is interface that describe expression with state and +// it's evaluation. +type Expression interface { + Reset() + + EvalFloat(scope *tick.Scope) (float64, error) + EvalInt(scope *tick.Scope) (int64, error) + EvalString(scope *tick.Scope) (string, error) + EvalBool(scope *tick.Scope) (bool, error) + + EvalNum(scope *tick.Scope) (interface{}, error) +} + +type expression struct { + nodeEvaluator NodeEvaluator + executionState ExecutionState +} + +// NewExpression accept a node and try to "compile"/ "specialise" it +// in order to achieve better runtime performance. +// +// For example: +// Given a BinaryNode{ReferNode("value"), NumberNode{Float64:10}} during runtime +// we can find the type of "value" and find the most matching comparison function - (float64,float64) or (int64,float64) +func NewExpression(node tick.Node) (Expression, error) { + nodeEvaluator, err := createNodeEvaluator(node) + if err != nil { + return nil, err + } + + return &expression{ + nodeEvaluator: nodeEvaluator, + executionState: CreateExecutionState(), + }, nil +} + +func (se *expression) Reset() { + se.executionState.ResetAll() +} + +func (se *expression) EvalBool(scope *tick.Scope) (bool, error) { + return se.nodeEvaluator.EvalBool(scope, se.executionState) +} + +func (se *expression) EvalInt(scope *tick.Scope) (int64, error) { + return se.nodeEvaluator.EvalInt(scope, se.executionState) +} + +func (se *expression) EvalFloat(scope *tick.Scope) (float64, error) { + return se.nodeEvaluator.EvalFloat(scope, se.executionState) +} + +func (se *expression) EvalString(scope *tick.Scope) (string, error) { + return se.nodeEvaluator.EvalString(scope, se.executionState) +} + +func (se *expression) EvalNum(scope *tick.Scope) (interface{}, error) { + returnType, err := se.nodeEvaluator.Type(scope, CreateExecutionState()) + if err != nil { + return nil, err + } + + switch returnType { + case TInt64: + result, err := se.EvalInt(scope) + if err != nil { + // Making sure we return consistently nil on errors, and not zero values. + return nil, err + } + + return result, err + + case TFloat64: + result, err := se.EvalFloat(scope) + if err != nil { + // Making sure we return consistently nil on errors, and not zero values. + return nil, err + } + + return result, err + + default: + return nil, fmt.Errorf("expression returned unexpected type %s", returnType) + } +} diff --git a/tick/stateful_expr_benchmarks_test.go b/tick/stateful/expr_benchmarks_test.go similarity index 76% rename from tick/stateful_expr_benchmarks_test.go rename to tick/stateful/expr_benchmarks_test.go index efab15b908..8c075e0b6a 100644 --- a/tick/stateful_expr_benchmarks_test.go +++ b/tick/stateful/expr_benchmarks_test.go @@ -1,10 +1,11 @@ -package tick_test +package stateful_test import ( "fmt" "testing" "github.com/influxdata/kapacitor/tick" + "github.com/influxdata/kapacitor/tick/stateful" ) /* @@ -21,6 +22,30 @@ import ( Benchmark_{Evaluation type: EvalBool or EvalNum}_{Type: OneOperator, TwoOperator}_{LeftNode}_{RightNode} */ +func Benchmark_EvalBool_OneOperator_UnaryNode_BoolNode(b *testing.B) { + + emptyScope := tick.NewScope() + benchmarkEvalBool(b, emptyScope, &tick.UnaryNode{ + Operator: tick.TokenNot, + Node: &tick.BoolNode{ + Bool: false, + }, + }) +} + +func Benchmark_EvalBool_OneOperator_UnaryNode_ReferenceNode(b *testing.B) { + + scope := tick.NewScope() + scope.Set("value", bool(false)) + + benchmarkEvalBool(b, scope, &tick.UnaryNode{ + Operator: tick.TokenNot, + Node: &tick.ReferenceNode{ + Reference: "value", + }, + }) +} + func Benchmark_EvalBool_OneOperator_NumberFloat64_NumberFloat64(b *testing.B) { emptyScope := tick.NewScope() @@ -68,6 +93,25 @@ func Benchmark_EvalBool_OneOperator_NumberInt64_NumberInt64(b *testing.B) { }) } +func Benchmark_EvalBool_OneOperator_UnaryNode(b *testing.B) { + + scope := tick.NewScope() + scope.Set("value", bool(true)) + + benchmarkEvalBool(b, scope, &tick.BinaryNode{ + Operator: tick.TokenEqual, + Left: &tick.UnaryNode{ + Operator: tick.TokenNot, + Node: &tick.BoolNode{ + Bool: false, + }, + }, + Right: &tick.ReferenceNode{ + Reference: "value", + }, + }) +} + func Benchmark_EvalBool_OneOperator_ReferenceNodeFloat64_NumberFloat64(b *testing.B) { scope := tick.NewScope() @@ -148,7 +192,7 @@ func Benchmark_EvalBool_OneOperatorValueChanges_ReferenceNodeFloat64_NumberFloat b.ReportAllocs() b.ResetTimer() - se := tick.NewStatefulExpr(&tick.BinaryNode{ + se, err := stateful.NewExpression(&tick.BinaryNode{ Operator: tick.TokenGreater, Left: &tick.ReferenceNode{ Reference: "value", @@ -158,6 +202,9 @@ func Benchmark_EvalBool_OneOperatorValueChanges_ReferenceNodeFloat64_NumberFloat Float64: float64(10), }, }) + if err != nil { + b.Fatalf("Failed to compile the expression: %v", err) + } // We have maximum value because we want to limit the maximum number in // the reference node so we don't get too much big numbers and the benchmark suite will increase our iterations number (b.N) @@ -224,6 +271,38 @@ func Benchmark_EvalBool_OneOperatorWith11ScopeItem_ReferenceNodeInt64_NumberInt6 }) } +func Benchmark_EvalBool_TwoLevelDeep(b *testing.B) { + scope := tick.NewScope() + scope.Set("a", float64(11)) + scope.Set("b", float64(8)) + + benchmarkEvalBool(b, scope, &tick.BinaryNode{ + Operator: tick.TokenAnd, + + Left: &tick.BinaryNode{ + Operator: tick.TokenGreater, + Left: &tick.ReferenceNode{ + Reference: "a", + }, + Right: &tick.NumberNode{ + IsFloat: true, + Float64: 10, + }, + }, + + Right: &tick.BinaryNode{ + Operator: tick.TokenLess, + Left: &tick.ReferenceNode{ + Reference: "b", + }, + Right: &tick.NumberNode{ + IsFloat: true, + Float64: 10, + }, + }, + }) +} + func Benchmark_EvalBool_OneOperatorValueChanges_ReferenceNodeInt64_NumberInt64(b *testing.B) { scope := tick.NewScope() @@ -234,7 +313,7 @@ func Benchmark_EvalBool_OneOperatorValueChanges_ReferenceNodeInt64_NumberInt64(b b.ReportAllocs() b.ResetTimer() - se := tick.NewStatefulExpr(&tick.BinaryNode{ + se, err := stateful.NewExpression(&tick.BinaryNode{ Operator: tick.TokenGreater, Left: &tick.ReferenceNode{ Reference: "value", @@ -244,6 +323,9 @@ func Benchmark_EvalBool_OneOperatorValueChanges_ReferenceNodeInt64_NumberInt64(b Int64: int64(10), }, }) + if err != nil { + b.Fatalf("Failed to compile the expression: %v", err) + } // We have maximum value because we want to limit the maximum number in // the reference node so we don't get too much big numbers and the benchmark suite will increase our iterations number (b.N) @@ -257,8 +339,8 @@ func Benchmark_EvalBool_OneOperatorValueChanges_ReferenceNodeInt64_NumberInt64(b currentValue += int64(1) if currentValue > maximumValue { currentValue = initialValue - } + } scope.Set("value", currentValue) b.StartTimer() @@ -279,8 +361,11 @@ func benchmarkEvalBool(b *testing.B, scope *tick.Scope, node tick.Node) { b.ReportAllocs() b.ResetTimer() - se := tick.NewStatefulExpr(node) var err error + se, err := stateful.NewExpression(node) + if err != nil { + b.Fatalf("Failed to compile the expression: %v", err) + } for i := 0; i < b.N; i++ { evalBoolResult, err = se.EvalBool(scope) @@ -288,5 +373,4 @@ func benchmarkEvalBool(b *testing.B, scope *tick.Scope, node tick.Node) { b.Fatal(err) } } - } diff --git a/tick/stateful/expr_dynamic_test.go b/tick/stateful/expr_dynamic_test.go new file mode 100644 index 0000000000..75c0b679dc --- /dev/null +++ b/tick/stateful/expr_dynamic_test.go @@ -0,0 +1,204 @@ +package stateful_test + +import ( + "errors" + "testing" + + "github.com/influxdata/kapacitor/tick" +) + +type valueExpectation struct { + Value interface{} + ExpectedResult interface{} + ExpectedError error + + // Should we EvalBool or EvalNum + IsEvalBool bool + IsEvalNum bool +} + +type testCase struct { + Title string + + // Node which the test is evaluated upon, must contain ReferenceNode with ref to "value" + Node tick.Node + + Expectations []valueExpectation +} + +// runDyanmicTestCase is when we want to change the "dyanmism" of +// a node - type change or value change +func runDynamicTestCase(t *testing.T, tc testCase) { + se := mustCompileExpression(t, tc.Node) + + for i, expectation := range tc.Expectations { + scope := tick.NewScope() + scope.Set("value", expectation.Value) + + var result interface{} + var err error + evaluationType := "" + + if expectation.IsEvalBool { + evaluationType = "EvalBool" + result, err = se.EvalBool(scope) + } + + if expectation.IsEvalNum { + evaluationType = "EvalNum" + result, err = se.EvalNum(scope) + } + + if err != nil { + if expectation.ExpectedError == nil { + t.Errorf("%s: %s: Iteration %v: Got unexpected error while expecting for result:\n %v\n", tc.Title, evaluationType, (i + 1), err) + continue + } else if err.Error() != expectation.ExpectedError.Error() { + t.Errorf("%s: %s: Iteration %v: Unexpected error:\ngot: %v\nexpected: %v\n", tc.Title, evaluationType, (i + 1), err, expectation.ExpectedError) + } + } + + if result != expectation.ExpectedResult { + t.Errorf("%s: %s: Iteration %v: Unexpected result:\ngot: %t\nexpected: %t\n", tc.Title, evaluationType, (i + 1), result, expectation.ExpectedResult) + } + } + +} + +func TestExpression_BinaryNode_DynamicTestCases(t *testing.T) { + + runDynamicTestCase(t, testCase{ + Title: "BinaryNode - EvalBool supports numeric type changes", + + Node: &tick.BinaryNode{ + Operator: tick.TokenGreater, + Left: &tick.ReferenceNode{ + Reference: "value", + }, + Right: &tick.NumberNode{ + IsFloat: true, + Float64: float64(10), + }, + }, + + Expectations: []valueExpectation{ + {IsEvalBool: true, Value: float64(20), ExpectedError: nil, ExpectedResult: true}, + {IsEvalBool: true, Value: int64(5), ExpectedError: nil, ExpectedResult: false}, + }, + }) + + runDynamicTestCase(t, testCase{ + Title: "BinaryNode - EvalNum supports numeric type changes", + + Node: &tick.BinaryNode{ + Operator: tick.TokenPlus, + Left: &tick.ReferenceNode{ + Reference: "value", + }, + Right: &tick.NumberNode{ + IsFloat: true, + Float64: float64(10), + }, + }, + + Expectations: []valueExpectation{ + { + IsEvalNum: true, + Value: float64(20), + ExpectedError: nil, + ExpectedResult: float64(30), + }, + { + IsEvalNum: true, + Value: int64(5), + ExpectedError: errors.New("mismatched type to binary operator. got int64 + float64. see bool(), int(), float()"), + ExpectedResult: nil, + }, + }, + }) + + runDynamicTestCase(t, testCase{ + Title: "BinaryNode - EvalNum supports numeric value changes", + + Node: &tick.BinaryNode{ + Operator: tick.TokenGreater, + Left: &tick.ReferenceNode{ + Reference: "value", + }, + Right: &tick.NumberNode{ + IsFloat: true, + Float64: float64(10), + }, + }, + + Expectations: []valueExpectation{ + { + IsEvalBool: true, + Value: float64(20), + ExpectedError: nil, + ExpectedResult: true, + }, + { + IsEvalBool: true, + Value: int64(5), + ExpectedError: nil, + ExpectedResult: false, + }, + }, + }) + +} + +func TestExpression_UnaryNode_DyanmicTestCases(t *testing.T) { + runDynamicTestCase(t, testCase{ + Title: "UnaryNode - EvalNum supports numeric type changes", + + Node: &tick.UnaryNode{ + Operator: tick.TokenMinus, + Node: &tick.ReferenceNode{ + Reference: "value", + }, + }, + + Expectations: []valueExpectation{ + { + IsEvalNum: true, + Value: float64(20), + ExpectedError: nil, + ExpectedResult: float64(-20), + }, + { + IsEvalNum: true, + Value: int64(20), + ExpectedError: nil, + ExpectedResult: int64(-20), + }, + }, + }) + + runDynamicTestCase(t, testCase{ + Title: "UnaryNode - EvalBool supports boolean value changes", + + Node: &tick.UnaryNode{ + Operator: tick.TokenNot, + Node: &tick.ReferenceNode{ + Reference: "value", + }, + }, + + Expectations: []valueExpectation{ + { + IsEvalBool: true, + Value: bool(true), + ExpectedError: nil, + ExpectedResult: bool(false), + }, + { + IsEvalBool: true, + Value: bool(false), + ExpectedError: nil, + ExpectedResult: bool(true), + }, + }, + }) +} diff --git a/tick/stateful_expr_test.go b/tick/stateful/expr_test.go similarity index 70% rename from tick/stateful_expr_test.go rename to tick/stateful/expr_test.go index 1cbc3c9892..0a08061840 100644 --- a/tick/stateful_expr_test.go +++ b/tick/stateful/expr_test.go @@ -1,12 +1,14 @@ -package tick_test +package stateful_test import ( "errors" "regexp" "strings" "testing" + "time" "github.com/influxdata/kapacitor/tick" + "github.com/influxdata/kapacitor/tick/stateful" ) type keyStruct struct { @@ -15,7 +17,153 @@ type keyStruct struct { op tick.TokenType } -func TestStatefulExpression_EvalBool_BoolNode(t *testing.T) { +func TestExpression_EvalNum_KeepsFunctionsState(t *testing.T) { + se := mustCompileExpression(t, &tick.FunctionNode{ + Func: "sigma", + Args: []tick.Node{&tick.ReferenceNode{Reference: "value"}}, + }) + + // first + scope := tick.NewScope() + scope.Set("value", float64(97.1)) + result, err := se.EvalNum(scope) + if err != nil { + t.Errorf("First: Got unexpected error: %v", err) + } + + if result != float64(0) { + t.Errorf("First: expected count to be math.NaN() but got %v", result) + } + + // second + scope.Set("value", float64(92.6)) + result, err = se.EvalNum(scope) + if err != nil { + t.Errorf("Second: Got unexpected error: %v", err) + } + + if result != float64(0.7071067811865476) { + t.Errorf("Second: expected count to be float64(0.7071067811865476) but got %v", result) + } + +} + +func TestExpression_EvalBool_BinaryNodeWithDurationNode(t *testing.T) { + se, err := stateful.NewExpression(&tick.BinaryNode{ + Operator: tick.TokenGreater, + Left: &tick.DurationNode{ + Dur: time.Minute, + }, + Right: &tick.DurationNode{ + Dur: time.Second, + }, + }) + + expectedError := errors.New("Failed to handle left node: Given node type is not valid evaluation node: *tick.DurationNode") + + if err == nil { + t.Errorf("Expected error, but got expression: %v", se) + } + + if err != nil && err.Error() != expectedError.Error() { + t.Errorf("Got unexpected error:\nexpected: %v\ngot: %v", expectedError, err) + } + +} + +func TestExpression_Eval_NotSupportedNode(t *testing.T) { + // Passing IdentifierNode, yeah.. this crazy test, but we want to make sure + // we don't have panics or crashes + se, err := stateful.NewExpression(&tick.IdentifierNode{}) + expectedError := errors.New("Given node type is not valid evaluation node: *tick.IdentifierNode") + if err == nil { + t.Errorf("EvalBool: Expected error, but got expression: %v", se) + } + + if err != nil && err.Error() != expectedError.Error() { + t.Errorf("EvalBool: Got unexpected error:\nexpected: %v\ngot: %v", expectedError, err) + } + + // BinaryNode - Left is identifier + se, err = stateful.NewExpression(&tick.BinaryNode{ + Operator: tick.TokenEqual, + Left: &tick.IdentifierNode{}, + Right: &tick.BoolNode{Bool: true}, + }) + + expectedError = errors.New("Failed to handle left node: Given node type is not valid evaluation node: *tick.IdentifierNode") + if err == nil { + t.Errorf("EvalBool BinaryNode(Left=>Identifier): Expected error, but got expression: %v", se) + } + + if err != nil && err.Error() != expectedError.Error() { + t.Errorf("EvalBool BinaryNode(Left=>Identifier): Got unexpected error:\nexpected: %v\ngot: %v", expectedError, err) + } + + // BinaryNode - Right is identifier + se, err = stateful.NewExpression(&tick.BinaryNode{ + Operator: tick.TokenEqual, + Left: &tick.BoolNode{Bool: true}, + Right: &tick.IdentifierNode{}, + }) + + expectedError = errors.New("Failed to handle right node: Given node type is not valid evaluation node: *tick.IdentifierNode") + if err == nil { + t.Errorf("EvalBool BinaryNode(Right=>Identifier): Expected error, but got expression: %v", se) + } + + if err != nil && err.Error() != expectedError.Error() { + t.Errorf("EvalBool BinaryNode(Right=>Identifier): Got unexpected error:\nexpected: %v\ngot: %v", expectedError, err) + } +} + +func TestExpression_Eval_NodeAndEvalTypeNotMatching(t *testing.T) { + // Test EvalBool against BinaryNode that returns math result + se := mustCompileExpression(t, &tick.BinaryNode{ + Operator: tick.TokenPlus, + Left: &tick.NumberNode{ + IsFloat: true, + Float64: float64(5), + }, + Right: &tick.NumberNode{ + IsFloat: true, + Float64: float64(10), + }, + }) + + result, err := se.EvalBool(tick.NewScope()) + expectedError := errors.New("expression returned unexpected type float64") + if err == nil { + t.Errorf("EvalBool: Expected error result, but got result: %v", result) + } + + if err != nil && err.Error() != expectedError.Error() { + t.Errorf("EvalBool: Got unexpected error:\nexpected: %v\ngot: %v", expectedError, err) + } + + // Test EvalNum against BinaryNode that returns bool result + se = mustCompileExpression(t, &tick.BinaryNode{ + Operator: tick.TokenOr, + Left: &tick.BoolNode{ + Bool: true, + }, + Right: &tick.BoolNode{ + Bool: false, + }, + }) + + numResult, err := se.EvalNum(tick.NewScope()) + expectedError = errors.New("expression returned unexpected type boolean") + if err == nil { + t.Errorf("EvalNum: Expected error result, but got result: %v", numResult) + } + + if err != nil && err.Error() != expectedError.Error() { + t.Errorf("EvalNum: Got unexpected error:\nexpected: %v\ngot: %v", expectedError, err) + } +} + +func TestExpression_EvalBool_BoolNode(t *testing.T) { leftValues := []interface{}{true, false} // Right values are the same as left, just add a mismatch case @@ -34,7 +182,7 @@ func TestStatefulExpression_EvalBool_BoolNode(t *testing.T) { } } - runEvalBoolTests(t, createBoolNode, leftValues, rightValues, operators, map[keyStruct]interface{}{ + runCompiledEvalBoolTests(t, createBoolNode, leftValues, rightValues, operators, map[keyStruct]interface{}{ // Left: True, Right: True keyStruct{true, true, tick.TokenEqual}: true, keyStruct{true, true, tick.TokenNotEqual}: false, @@ -66,22 +214,22 @@ func TestStatefulExpression_EvalBool_BoolNode(t *testing.T) { keyStruct{false, false, tick.TokenLess}: errors.New("invalid boolean comparison operator <"), // (Redundant test case) - keyStruct{true, "NON_BOOL_VALUE", tick.TokenLess}: errors.New("mismatched type to binary operator. got bool < string. see bool(), int(), float()"), - keyStruct{true, "NON_BOOL_VALUE", tick.TokenLess}: errors.New("mismatched type to binary operator. got bool < string. see bool(), int(), float()"), - keyStruct{false, "NON_BOOL_VALUE", tick.TokenLess}: errors.New("mismatched type to binary operator. got bool < string. see bool(), int(), float()"), - keyStruct{false, "NON_BOOL_VALUE", tick.TokenLess}: errors.New("mismatched type to binary operator. got bool < string. see bool(), int(), float()"), + keyStruct{true, "NON_BOOL_VALUE", tick.TokenLess}: errors.New("invalid boolean comparison operator <"), + keyStruct{true, "NON_BOOL_VALUE", tick.TokenLess}: errors.New("invalid boolean comparison operator <"), + keyStruct{false, "NON_BOOL_VALUE", tick.TokenLess}: errors.New("invalid boolean comparison operator <"), + keyStruct{false, "NON_BOOL_VALUE", tick.TokenLess}: errors.New("invalid boolean comparison operator <"), // Left: True, Right: "NON_BOOL_VALUE" - keyStruct{true, "NON_BOOL_VALUE", tick.TokenEqual}: errors.New("mismatched type to binary operator. got bool == string. see bool(), int(), float()"), - keyStruct{true, "NON_BOOL_VALUE", tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got bool != string. see bool(), int(), float()"), - keyStruct{true, "NON_BOOL_VALUE", tick.TokenAnd}: errors.New("mismatched type to binary operator. got bool AND string. see bool(), int(), float()"), - keyStruct{true, "NON_BOOL_VALUE", tick.TokenOr}: errors.New("mismatched type to binary operator. got bool OR string. see bool(), int(), float()"), + keyStruct{true, "NON_BOOL_VALUE", tick.TokenEqual}: errors.New("mismatched type to binary operator. got boolean == string. see bool(), int(), float()"), + keyStruct{true, "NON_BOOL_VALUE", tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got boolean != string. see bool(), int(), float()"), + keyStruct{true, "NON_BOOL_VALUE", tick.TokenAnd}: errors.New("mismatched type to binary operator. got boolean AND string. see bool(), int(), float()"), + keyStruct{true, "NON_BOOL_VALUE", tick.TokenOr}: errors.New("mismatched type to binary operator. got boolean OR string. see bool(), int(), float()"), // Left: False, Right: "NON_BOOL_VALUE" - keyStruct{false, "NON_BOOL_VALUE", tick.TokenEqual}: errors.New("mismatched type to binary operator. got bool == string. see bool(), int(), float()"), - keyStruct{false, "NON_BOOL_VALUE", tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got bool != string. see bool(), int(), float()"), - keyStruct{false, "NON_BOOL_VALUE", tick.TokenAnd}: errors.New("mismatched type to binary operator. got bool AND string. see bool(), int(), float()"), - keyStruct{false, "NON_BOOL_VALUE", tick.TokenOr}: errors.New("mismatched type to binary operator. got bool OR string. see bool(), int(), float()"), + keyStruct{false, "NON_BOOL_VALUE", tick.TokenEqual}: errors.New("mismatched type to binary operator. got boolean == string. see bool(), int(), float()"), + keyStruct{false, "NON_BOOL_VALUE", tick.TokenNotEqual}: errors.New("mismatched type to binary operator. got boolean != string. see bool(), int(), float()"), + keyStruct{false, "NON_BOOL_VALUE", tick.TokenAnd}: errors.New("mismatched type to binary operator. got boolean AND string. see bool(), int(), float()"), + keyStruct{false, "NON_BOOL_VALUE", tick.TokenOr}: errors.New("mismatched type to binary operator. got boolean OR string. see bool(), int(), float()"), // Left: "NON_BOOL_VALUE", Right: True keyStruct{"NON_BOOL_VALUE", true, tick.TokenEqual}: errors.New("mismatched type to binary operator. got string == bool. see bool(), int(), float()"), @@ -98,7 +246,7 @@ func TestStatefulExpression_EvalBool_BoolNode(t *testing.T) { } -func TestStatefulExpression_EvalBool_NumberNode(t *testing.T) { +func TestExpression_EvalBool_NumberNode(t *testing.T) { leftValues := []interface{}{float64(5), float64(10), int64(5)} rightValues := []interface{}{float64(5), float64(10), int64(5), "NON_INT_VALUE"} @@ -127,7 +275,7 @@ func TestStatefulExpression_EvalBool_NumberNode(t *testing.T) { } } - runEvalBoolTests(t, createNumberNode, leftValues, rightValues, operators, map[keyStruct]interface{}{ + runCompiledEvalBoolTests(t, createNumberNode, leftValues, rightValues, operators, map[keyStruct]interface{}{ // Left is float64(5), Right is float64(5) keyStruct{float64(5), float64(5), tick.TokenEqual}: true, keyStruct{float64(5), float64(5), tick.TokenNotEqual}: false, @@ -209,20 +357,20 @@ func TestStatefulExpression_EvalBool_NumberNode(t *testing.T) { keyStruct{int64(5), int64(5), tick.TokenLessEqual}: true, }, map[keyStruct]error{ // Invalid operator - keyStruct{float64(5), float64(5), tick.TokenOr}: errors.New("invalid float comparison operator OR"), - keyStruct{float64(5), float64(10), tick.TokenOr}: errors.New("invalid float comparison operator OR"), - keyStruct{float64(5), int64(5), tick.TokenOr}: errors.New("invalid float comparison operator OR"), - keyStruct{float64(10), float64(5), tick.TokenOr}: errors.New("invalid float comparison operator OR"), - keyStruct{float64(10), float64(10), tick.TokenOr}: errors.New("invalid float comparison operator OR"), - keyStruct{float64(10), int64(5), tick.TokenOr}: errors.New("invalid float comparison operator OR"), - keyStruct{int64(5), float64(5), tick.TokenOr}: errors.New("invalid float comparison operator OR"), - keyStruct{int64(5), float64(10), tick.TokenOr}: errors.New("invalid float comparison operator OR"), - keyStruct{int64(5), int64(5), tick.TokenOr}: errors.New("invalid float comparison operator OR"), + keyStruct{float64(5), float64(5), tick.TokenOr}: errors.New("invalid float64 comparison operator OR"), + keyStruct{float64(5), float64(10), tick.TokenOr}: errors.New("invalid float64 comparison operator OR"), + keyStruct{float64(5), int64(5), tick.TokenOr}: errors.New("invalid float64 comparison operator OR"), + keyStruct{float64(10), float64(5), tick.TokenOr}: errors.New("invalid float64 comparison operator OR"), + keyStruct{float64(10), float64(10), tick.TokenOr}: errors.New("invalid float64 comparison operator OR"), + keyStruct{float64(10), int64(5), tick.TokenOr}: errors.New("invalid float64 comparison operator OR"), + keyStruct{int64(5), float64(5), tick.TokenOr}: errors.New("invalid int64 comparison operator OR"), + keyStruct{int64(5), float64(10), tick.TokenOr}: errors.New("invalid int64 comparison operator OR"), + keyStruct{int64(5), int64(5), tick.TokenOr}: errors.New("invalid int64 comparison operator OR"), // (Redundant case) - keyStruct{float64(5), "NON_INT_VALUE", tick.TokenOr}: errors.New("mismatched type to binary operator. got float64 OR string. see bool(), int(), float()"), - keyStruct{float64(10), "NON_INT_VALUE", tick.TokenOr}: errors.New("mismatched type to binary operator. got float64 OR string. see bool(), int(), float()"), - keyStruct{int64(5), "NON_INT_VALUE", tick.TokenOr}: errors.New("mismatched type to binary operator. got int64 OR string. see bool(), int(), float()"), + keyStruct{float64(5), "NON_INT_VALUE", tick.TokenOr}: errors.New("invalid float64 comparison operator OR"), + keyStruct{float64(10), "NON_INT_VALUE", tick.TokenOr}: errors.New("invalid float64 comparison operator OR"), + keyStruct{int64(5), "NON_INT_VALUE", tick.TokenOr}: errors.New("invalid int64 comparison operator OR"), // Left is float64(5), Right is "NON_INT_VALUE" keyStruct{float64(5), "NON_INT_VALUE", tick.TokenEqual}: errors.New("mismatched type to binary operator. got float64 == string. see bool(), int(), float()"), @@ -250,7 +398,7 @@ func TestStatefulExpression_EvalBool_NumberNode(t *testing.T) { }) } -func TestStatefulExpression_EvalBool_StringNode(t *testing.T) { +func TestExpression_EvalBool_StringNode(t *testing.T) { leftValues := []interface{}{"a", "b"} rightValues := []interface{}{"a", "b", int64(123)} operators := []tick.TokenType{tick.TokenEqual, tick.TokenNotEqual, tick.TokenGreater, tick.TokenGreaterEqual, tick.TokenLessEqual, tick.TokenLess, tick.TokenOr} @@ -272,7 +420,7 @@ func TestStatefulExpression_EvalBool_StringNode(t *testing.T) { } } - runEvalBoolTests(t, createStringNode, leftValues, rightValues, operators, map[keyStruct]interface{}{ + runCompiledEvalBoolTests(t, createStringNode, leftValues, rightValues, operators, map[keyStruct]interface{}{ // Left is "a", Right is "a" keyStruct{"a", "a", tick.TokenEqual}: true, keyStruct{"a", "a", tick.TokenNotEqual}: false, @@ -311,8 +459,8 @@ func TestStatefulExpression_EvalBool_StringNode(t *testing.T) { keyStruct{"b", "a", tick.TokenOr}: errors.New("invalid string comparison operator OR"), keyStruct{"b", "b", tick.TokenOr}: errors.New("invalid string comparison operator OR"), - keyStruct{"a", int64(123), tick.TokenOr}: errors.New("mismatched type to binary operator. got string OR int64. see bool(), int(), float()"), - keyStruct{"b", int64(123), tick.TokenOr}: errors.New("mismatched type to binary operator. got string OR int64. see bool(), int(), float()"), + keyStruct{"a", int64(123), tick.TokenOr}: errors.New("invalid string comparison operator OR"), + keyStruct{"b", int64(123), tick.TokenOr}: errors.New("invalid string comparison operator OR"), // Left is "a", Right is int64(123) keyStruct{"a", int64(123), tick.TokenEqual}: errors.New("mismatched type to binary operator. got string == int64. see bool(), int(), float()"), @@ -332,7 +480,7 @@ func TestStatefulExpression_EvalBool_StringNode(t *testing.T) { }) } -func TestStatefulExpression_EvalBool_RegexNode(t *testing.T) { +func TestExpression_EvalBool_RegexNode(t *testing.T) { leftValues := []interface{}{"abc", "cba"} // Right values are regex, but we are supplying strings because the keyStruct and maps don't play nice together @@ -354,7 +502,7 @@ func TestStatefulExpression_EvalBool_RegexNode(t *testing.T) { } - runEvalBoolTests(t, createStringOrRegexNode, leftValues, rightValues, operators, map[keyStruct]interface{}{ + runCompiledEvalBoolTests(t, createStringOrRegexNode, leftValues, rightValues, operators, map[keyStruct]interface{}{ // Left is "abc", Right is regex "(.*)c" keyStruct{"abc", "R!^(.*)c$", tick.TokenRegexEqual}: true, keyStruct{"abc", "R!^(.*)c$", tick.TokenRegexNotEqual}: false, @@ -370,10 +518,10 @@ func TestStatefulExpression_EvalBool_RegexNode(t *testing.T) { }) } -func TestStatefulExpression_EvalBool_NotSupportedValueLeft(t *testing.T) { +func TestExpression_EvalBool_NotSupportedValueLeft(t *testing.T) { scope := tick.NewScope() scope.Set("value", []int{1, 2, 3}) - _, err := evalBoolWithScope(scope, &tick.BinaryNode{ + _, err := evalCompiledBoolWithScope(t, scope, &tick.BinaryNode{ Operator: tick.TokenEqual, Left: &tick.ReferenceNode{ Reference: "value", @@ -383,7 +531,28 @@ func TestStatefulExpression_EvalBool_NotSupportedValueLeft(t *testing.T) { }, }) - expectedError := "mismatched type to binary operator. got []int == string. see bool(), int(), float()" + expectedError := "left value is invalid value type" + + if err != nil && (err.Error() != expectedError) { + t.Errorf("Unexpected error result: \ngot: %v\nexpected: %v", err.Error(), expectedError) + } + + if err == nil { + t.Error("Unexpected error result: but didn't got any error") + } + + // Swap sides + _, err = evalCompiledBoolWithScope(t, scope, &tick.BinaryNode{ + Operator: tick.TokenEqual, + Left: &tick.StringNode{ + Literal: "yo", + }, + Right: &tick.ReferenceNode{ + Reference: "value", + }, + }) + + expectedError = "right value is invalid value type" if err != nil && (err.Error() != expectedError) { t.Errorf("Unexpected error result: \ngot: %v\nexpected: %v", err.Error(), expectedError) @@ -394,8 +563,8 @@ func TestStatefulExpression_EvalBool_NotSupportedValueLeft(t *testing.T) { } } -func TestStatefulExpression_EvalBool_UnknownOperator(t *testing.T) { - _, err := evalBoolWithScope(tick.NewScope(), &tick.BinaryNode{ +func TestExpression_EvalBool_UnknownOperator(t *testing.T) { + _, err := evalCompiledBoolWithScope(t, tick.NewScope(), &tick.BinaryNode{ Operator: tick.TokenType(666), Left: &tick.StringNode{ Literal: "value", @@ -416,12 +585,12 @@ func TestStatefulExpression_EvalBool_UnknownOperator(t *testing.T) { } } -func TestStatefulExpression_evalBinary_ReferenceNodeDosentExist(t *testing.T) { +func TestExpression_EvalBool_ReferenceNodeDosentExist(t *testing.T) { emptyScope := tick.NewScope() expectedError := `name "value" is undefined. Names in scope: ` // Check left side - _, err := evalBoolWithScope(emptyScope, &tick.BinaryNode{ + _, err := evalCompiledBoolWithScope(t, emptyScope, &tick.BinaryNode{ Operator: tick.TokenEqual, Left: &tick.ReferenceNode{ Reference: "value", @@ -440,7 +609,7 @@ func TestStatefulExpression_evalBinary_ReferenceNodeDosentExist(t *testing.T) { } // Check right side - _, err = evalBoolWithScope(emptyScope, &tick.BinaryNode{ + _, err = evalCompiledBoolWithScope(t, emptyScope, &tick.BinaryNode{ Operator: tick.TokenEqual, Left: &tick.StringNode{ Literal: "yo", @@ -459,14 +628,14 @@ func TestStatefulExpression_evalBinary_ReferenceNodeDosentExist(t *testing.T) { } } -func TestStatefulExpression_EvalBool_ReturnsReferenceNode(t *testing.T) { +func TestExpression_EvalBool_ReturnsReferenceNode(t *testing.T) { scope := tick.NewScope() // First Case - true as boolValue boolValue := true scope.Set("boolValue", boolValue) - result, err := evalBoolWithScope(scope, &tick.ReferenceNode{ + result, err := evalCompiledBoolWithScope(t, scope, &tick.ReferenceNode{ Reference: "boolValue", }) @@ -482,7 +651,7 @@ func TestStatefulExpression_EvalBool_ReturnsReferenceNode(t *testing.T) { boolValue = false scope.Set("boolValue", boolValue) - result, err = evalBoolWithScope(scope, &tick.ReferenceNode{ + result, err = evalCompiledBoolWithScope(t, scope, &tick.ReferenceNode{ Reference: "boolValue", }) @@ -495,32 +664,34 @@ func TestStatefulExpression_EvalBool_ReturnsReferenceNode(t *testing.T) { } } -func TestStatefulExpression_EvalBool_ReferenceNodeDosentExist(t *testing.T) { +func TestExpression_EvalNum_ReferenceNodeDosentExist(t *testing.T) { emptyScope := tick.NewScope() expectedError := `name "value" is undefined. Names in scope: ` // Check left side - _, err := evalBoolWithScope(emptyScope, &tick.ReferenceNode{ + se := mustCompileExpression(t, &tick.ReferenceNode{ Reference: "value", }) + result, err := se.EvalNum(emptyScope) + if err != nil && (err.Error() != expectedError) { t.Errorf("Unexpected error result: \ngot: %v\nexpected: %v", err.Error(), expectedError) } if err == nil { - t.Error("Unexpected error result: but didn't got any error") + t.Errorf("Expected error result: but didn't got any error, got result: %v", result) } } -func TestStatefulExpression_EvalBool_UnexpectedTypeResult(t *testing.T) { - expectedError := `expression returned unexpected type []int` +func TestExpression_EvalBool_UnexpectedTypeResult(t *testing.T) { + expectedError := `expression returned unexpected type invalid type` scope := tick.NewScope() scope.Set("value", []int{1, 2, 3}) // Check left side - _, err := evalBoolWithScope(scope, &tick.ReferenceNode{ + _, err := evalCompiledBoolWithScope(t, scope, &tick.ReferenceNode{ Reference: "value", }) @@ -533,12 +704,12 @@ func TestStatefulExpression_EvalBool_UnexpectedTypeResult(t *testing.T) { } } -func TestStatefulExpression_EvalBool_ReferenceNodeDosentExistInBinaryNode(t *testing.T) { +func TestExpression_EvalBool_ReferenceNodeDosentExistInBinaryNode(t *testing.T) { emptyScope := tick.NewScope() expectedError := `name "value" is undefined. Names in scope: ` // Check left side - _, err := evalBoolWithScope(emptyScope, &tick.BinaryNode{ + _, err := evalCompiledBoolWithScope(t, emptyScope, &tick.BinaryNode{ Operator: tick.TokenGreater, Left: &tick.ReferenceNode{ Reference: "value", @@ -558,114 +729,153 @@ func TestStatefulExpression_EvalBool_ReferenceNodeDosentExistInBinaryNode(t *tes } } -func TestStatefulExpression_EvalBool_ReferenceNodeValueChanges(t *testing.T) { - scope := tick.NewScope() +func TestExpression_EvalNum_BinaryNodeWithUnary(t *testing.T) { - scope.Set("value", float64(20)) - se := tick.NewStatefulExpr(&tick.BinaryNode{ - Operator: tick.TokenGreater, - Left: &tick.ReferenceNode{ - Reference: "value", + // -"value" < 0 , yes, of course, this is always true.. + se := mustCompileExpression(t, &tick.BinaryNode{ + Operator: tick.TokenLess, + Left: &tick.UnaryNode{ + Operator: tick.TokenMinus, + Node: &tick.ReferenceNode{ + Reference: "value", + }, }, Right: &tick.NumberNode{ - IsFloat: true, - Float64: float64(10), + IsInt: true, + Int64: int64(0), }, }) + scope := tick.NewScope() + scope.Set("value", int64(4)) result, err := se.EvalBool(scope) - if err != nil { - t.Error("Got an error while evaluating expression:", err) + t.Errorf("Ref node: Failed to evaluate:\n%v", err) } if !result { - t.Errorf("Unexpected result: got=%t, expected=true", result) - } - - // Now change to value to 5 - scope.Set("value", float64(5)) - result, err = se.EvalBool(scope) - - if err != nil { - t.Error("Got an error while evaluating expression:", err) + t.Errorf("int64 ref test case: unexpected result: got: %t, expected: true", result) } - if result { - t.Errorf("Unexpected result: got=%t, expected=false", result) - } } -func TestStatefulExpression_EvalBool_ReferenceNodeTypeChanges(t *testing.T) { - scope := tick.NewScope() +func TestExpression_EvalBool_BinaryNodeWithBoolUnaryNode(t *testing.T) { - scope.Set("value", float64(20)) - se := tick.NewStatefulExpr(&tick.BinaryNode{ - Operator: tick.TokenGreater, - Left: &tick.ReferenceNode{ - Reference: "value", + emptyScope := tick.NewScope() + + se := mustCompileExpression(t, &tick.BinaryNode{ + Operator: tick.TokenEqual, + Left: &tick.UnaryNode{ + Operator: tick.TokenNot, + Node: &tick.BoolNode{ + Bool: false, + }, }, - Right: &tick.NumberNode{ - IsFloat: true, - Float64: float64(10), + Right: &tick.BoolNode{ + Bool: true, }, }) - result, err := se.EvalBool(scope) - + result, err := se.EvalBool(emptyScope) if err != nil { - t.Error("Got an error while evaluating expression:", err) + t.Errorf("first case: %v", err) } if !result { - t.Errorf("Unexpected result: got=%t, expected=true", result) + t.Errorf("first case: unexpected result: got: %t, expected: true", result) } - // Now change to value to int64 from float64 - scope.Set("value", int64(5)) - result, err = se.EvalBool(scope) + // now with ref + se = mustCompileExpression(t, &tick.BinaryNode{ + Operator: tick.TokenEqual, + Left: &tick.UnaryNode{ + Operator: tick.TokenNot, + Node: &tick.ReferenceNode{ + Reference: "value", + }, + }, + Right: &tick.BoolNode{ + Bool: true, + }, + }) + + scope := tick.NewScope() + scope.Set("value", bool(false)) + result, err = se.EvalBool(scope) if err != nil { - t.Error("Got an error while evaluating expression:", err) + t.Errorf("ref case: %v", err) } - if result { - t.Errorf("Unexpected result: got=%t, expected=false", result) + if !result { + t.Errorf("ref case: unexpected result: got: %t, expected: true", result) } + } -func TestStatefulExpression_EvalNum_UnaryExpression(t *testing.T) { +func TestExpression_EvalBool_BinaryNodeWithNumericUnaryNode(t *testing.T) { scope := tick.NewScope() - se := tick.NewStatefulExpr(&tick.UnaryNode{ - Node: &tick.NumberNode{ + se := mustCompileExpression(t, &tick.BinaryNode{ + Operator: tick.TokenLess, + Left: &tick.UnaryNode{ + Operator: tick.TokenMinus, + Node: &tick.NumberNode{ + IsInt: true, + Int64: 4, + }, + }, + Right: &tick.NumberNode{ IsInt: true, - Int64: 4, + Int64: 0, }, - Operator: tick.TokenMinus, }) - result, err := se.EvalNum(scope) + result, err := se.EvalBool(scope) if err != nil { t.Error(err) } - if result != int64(-4) { - t.Errorf("unexpected result: got: %t, expected: -4", result) + if !result { + t.Errorf("unexpected result: got: %t, expected: true", result) } } -func TestStatefulExpression_EvalBool_UnaryExpression(t *testing.T) { +func TestExpression_EvalBool_TwoLevelsDeepBinary(t *testing.T) { scope := tick.NewScope() - se := tick.NewStatefulExpr(&tick.UnaryNode{ - Node: &tick.BoolNode{ - Bool: true, + // passing + scope.Set("a", int64(11)) + scope.Set("b", int64(5)) + + // a > 10 and b < 10 + se := mustCompileExpression(t, &tick.BinaryNode{ + Operator: tick.TokenAnd, + + Left: &tick.BinaryNode{ + Operator: tick.TokenGreater, + Left: &tick.ReferenceNode{ + Reference: "a", + }, + Right: &tick.NumberNode{ + IsInt: true, + Int64: 10, + }, + }, + + Right: &tick.BinaryNode{ + Operator: tick.TokenLess, + Left: &tick.ReferenceNode{ + Reference: "b", + }, + Right: &tick.NumberNode{ + IsInt: true, + Int64: 10, + }, }, - Operator: tick.TokenNot, }) result, err := se.EvalBool(scope) @@ -673,12 +883,24 @@ func TestStatefulExpression_EvalBool_UnaryExpression(t *testing.T) { t.Error(err) } + if !result { + t.Errorf("unexpected result: got: %t, expected: true", result) + } + + // fail + scope.Set("a", int64(6)) + + result, err = se.EvalBool(scope) + if err != nil { + t.Error(err) + } + if result { t.Errorf("unexpected result: got: %t, expected: false", result) } } -func TestStatefulExpression_EvalBool_TwoLevelsDeepBinary(t *testing.T) { +func TestExpression_EvalBool_TwoLevelsDeepBinaryWithEvalNum_Int64(t *testing.T) { scope := tick.NewScope() @@ -687,7 +909,7 @@ func TestStatefulExpression_EvalBool_TwoLevelsDeepBinary(t *testing.T) { scope.Set("b", int64(5)) // a > 10 and b < 10 - se := tick.NewStatefulExpr(&tick.BinaryNode{ + se := mustCompileExpression(t, &tick.BinaryNode{ Operator: tick.TokenAnd, Left: &tick.BinaryNode{ @@ -695,9 +917,17 @@ func TestStatefulExpression_EvalBool_TwoLevelsDeepBinary(t *testing.T) { Left: &tick.ReferenceNode{ Reference: "a", }, - Right: &tick.NumberNode{ - IsInt: true, - Int64: 10, + // right = 5 * 2 = 10 + Right: &tick.BinaryNode{ + Operator: tick.TokenMult, + Left: &tick.NumberNode{ + IsInt: true, + Int64: 5, + }, + Right: &tick.NumberNode{ + IsInt: true, + Int64: 2, + }, }, }, @@ -735,35 +965,72 @@ func TestStatefulExpression_EvalBool_TwoLevelsDeepBinary(t *testing.T) { } } -func TestStatefulExpression_EvalNum_SanityCallingFunction(t *testing.T) { +func TestExpression_EvalBool_TwoLevelsDeepBinaryWithEvalNum_Float64(t *testing.T) { scope := tick.NewScope() - se := tick.NewStatefulExpr(&tick.FunctionNode{ - Func: "count", + // passing + scope.Set("a", float64(11)) + scope.Set("b", float64(5)) + + // a > 10 and b < 10 + se := mustCompileExpression(t, &tick.BinaryNode{ + Operator: tick.TokenAnd, + + Left: &tick.BinaryNode{ + Operator: tick.TokenGreater, + Left: &tick.ReferenceNode{ + Reference: "a", + }, + // right = 5 * 2 = 10 + Right: &tick.BinaryNode{ + Operator: tick.TokenMult, + Left: &tick.NumberNode{ + IsFloat: true, + Float64: 5, + }, + Right: &tick.NumberNode{ + IsFloat: true, + Float64: 2, + }, + }, + }, + + Right: &tick.BinaryNode{ + Operator: tick.TokenLess, + Left: &tick.ReferenceNode{ + Reference: "b", + }, + Right: &tick.NumberNode{ + IsFloat: true, + Float64: 10, + }, + }, }) - result, err := se.EvalNum(scope) + result, err := se.EvalBool(scope) if err != nil { t.Error(err) } - if result != int64(1) { - t.Errorf("unexpected result: got: %t, expected: 1", result) + if !result { + t.Errorf("unexpected result: got: %t, expected: true", result) } - // Second time, to make sure that count() increases the value - result, err = se.EvalNum(scope) + // fail + scope.Set("a", float64(6)) + + result, err = se.EvalBool(scope) if err != nil { t.Error(err) } - if result != int64(2) { - t.Errorf("unexpected result: got: %t, expected: 2", result) + if result { + t.Errorf("unexpected result: got: %t, expected: false", result) } } -func TestStatefulExpression_EvalNum_NumberNode(t *testing.T) { +func TestExpression_EvalNum_NumberNode(t *testing.T) { leftValues := []interface{}{float64(5), float64(10), int64(5)} rightValues := []interface{}{float64(5), float64(10), int64(5), "NON_INT_VALUE"} @@ -798,7 +1065,7 @@ func TestStatefulExpression_EvalNum_NumberNode(t *testing.T) { } } - runEvalNumericTests(t, createNumberNode, leftValues, rightValues, operators, map[keyStruct]interface{}{ + runCompiledNumericTests(t, createNumberNode, leftValues, rightValues, operators, map[keyStruct]interface{}{ // Left is float64(5), Right is float64(5) keyStruct{float64(5), float64(5), tick.TokenPlus}: float64(10), keyStruct{float64(5), float64(5), tick.TokenMinus}: float64(0), @@ -831,10 +1098,14 @@ func TestStatefulExpression_EvalNum_NumberNode(t *testing.T) { keyStruct{float64(10), float64(10), tick.TokenDiv}: float64(1), }, map[keyStruct]error{ // Modulo token where left is float - keyStruct{float64(5), float64(5), tick.TokenMod}: errors.New("invalid float math operator %"), - keyStruct{float64(5), float64(10), tick.TokenMod}: errors.New("invalid float math operator %"), - keyStruct{float64(10), float64(5), tick.TokenMod}: errors.New("invalid float math operator %"), - keyStruct{float64(10), float64(10), tick.TokenMod}: errors.New("invalid float math operator %"), + keyStruct{float64(5), float64(5), tick.TokenMod}: errors.New("invalid float64 math operator %"), + keyStruct{float64(5), float64(10), tick.TokenMod}: errors.New("invalid float64 math operator %"), + keyStruct{float64(10), float64(5), tick.TokenMod}: errors.New("invalid float64 math operator %"), + keyStruct{float64(10), float64(10), tick.TokenMod}: errors.New("invalid float64 math operator %"), + keyStruct{float64(5), int64(5), tick.TokenMod}: errors.New("invalid float64 math operator %"), + keyStruct{float64(10), int64(5), tick.TokenMod}: errors.New("invalid float64 math operator %"), + keyStruct{float64(10), "NON_INT_VALUE", tick.TokenMod}: errors.New("invalid float64 math operator %"), + keyStruct{float64(5), "NON_INT_VALUE", tick.TokenMod}: errors.New("invalid float64 math operator %"), // Left is int, right is float keyStruct{int64(5), float64(5), tick.TokenPlus}: errors.New("mismatched type to binary operator. got int64 + float64. see bool(), int(), float()"), @@ -853,13 +1124,11 @@ func TestStatefulExpression_EvalNum_NumberNode(t *testing.T) { keyStruct{float64(5), int64(5), tick.TokenMinus}: errors.New("mismatched type to binary operator. got float64 - int64. see bool(), int(), float()"), keyStruct{float64(5), int64(5), tick.TokenMult}: errors.New("mismatched type to binary operator. got float64 * int64. see bool(), int(), float()"), keyStruct{float64(5), int64(5), tick.TokenDiv}: errors.New("mismatched type to binary operator. got float64 / int64. see bool(), int(), float()"), - keyStruct{float64(5), int64(5), tick.TokenMod}: errors.New("mismatched type to binary operator. got float64 % int64. see bool(), int(), float()"), keyStruct{float64(10), int64(5), tick.TokenPlus}: errors.New("mismatched type to binary operator. got float64 + int64. see bool(), int(), float()"), keyStruct{float64(10), int64(5), tick.TokenMinus}: errors.New("mismatched type to binary operator. got float64 - int64. see bool(), int(), float()"), keyStruct{float64(10), int64(5), tick.TokenMult}: errors.New("mismatched type to binary operator. got float64 * int64. see bool(), int(), float()"), keyStruct{float64(10), int64(5), tick.TokenDiv}: errors.New("mismatched type to binary operator. got float64 / int64. see bool(), int(), float()"), - keyStruct{float64(10), int64(5), tick.TokenMod}: errors.New("mismatched type to binary operator. got float64 % int64. see bool(), int(), float()"), // Left is int64, Right is "NON_INT_VALUE" keyStruct{int64(5), "NON_INT_VALUE", tick.TokenPlus}: errors.New("mismatched type to binary operator. got int64 + string. see bool(), int(), float()"), @@ -873,16 +1142,14 @@ func TestStatefulExpression_EvalNum_NumberNode(t *testing.T) { keyStruct{float64(5), "NON_INT_VALUE", tick.TokenMinus}: errors.New("mismatched type to binary operator. got float64 - string. see bool(), int(), float()"), keyStruct{float64(5), "NON_INT_VALUE", tick.TokenMult}: errors.New("mismatched type to binary operator. got float64 * string. see bool(), int(), float()"), keyStruct{float64(5), "NON_INT_VALUE", tick.TokenDiv}: errors.New("mismatched type to binary operator. got float64 / string. see bool(), int(), float()"), - keyStruct{float64(5), "NON_INT_VALUE", tick.TokenMod}: errors.New("mismatched type to binary operator. got float64 % string. see bool(), int(), float()"), keyStruct{float64(10), "NON_INT_VALUE", tick.TokenPlus}: errors.New("mismatched type to binary operator. got float64 + string. see bool(), int(), float()"), keyStruct{float64(10), "NON_INT_VALUE", tick.TokenMinus}: errors.New("mismatched type to binary operator. got float64 - string. see bool(), int(), float()"), keyStruct{float64(10), "NON_INT_VALUE", tick.TokenMult}: errors.New("mismatched type to binary operator. got float64 * string. see bool(), int(), float()"), keyStruct{float64(10), "NON_INT_VALUE", tick.TokenDiv}: errors.New("mismatched type to binary operator. got float64 / string. see bool(), int(), float()"), - keyStruct{float64(10), "NON_INT_VALUE", tick.TokenMod}: errors.New("mismatched type to binary operator. got float64 % string. see bool(), int(), float()"), }) } -func runEvalNumericTests( +func runCompiledNumericTests( t *testing.T, createNodeFn func(v interface{}) tick.Node, leftValues []interface{}, @@ -891,13 +1158,13 @@ func runEvalNumericTests( expected map[keyStruct]interface{}, errorExpectations map[keyStruct]error) { - runEvalTests(t, func(scope *tick.Scope, n tick.Node) (interface{}, error) { - se := tick.NewStatefulExpr(n) + runCompiledEvalTests(t, func(t *testing.T, scope *tick.Scope, n tick.Node) (interface{}, error) { + se := mustCompileExpression(t, n) return se.EvalNum(scope) }, createNodeFn, leftValues, rightValues, operators, expected, errorExpectations) } -func runEvalBoolTests( +func runCompiledEvalBoolTests( t *testing.T, createNodeFn func(v interface{}) tick.Node, leftValues []interface{}, @@ -906,17 +1173,17 @@ func runEvalBoolTests( expected map[keyStruct]interface{}, errorExpectations map[keyStruct]error) { - runEvalTests(t, evalBoolWithScope, createNodeFn, leftValues, rightValues, operators, expected, errorExpectations) + runCompiledEvalTests(t, evalCompiledBoolWithScope, createNodeFn, leftValues, rightValues, operators, expected, errorExpectations) } -func evalBoolWithScope(scope *tick.Scope, n tick.Node) (interface{}, error) { - se := tick.NewStatefulExpr(n) +func evalCompiledBoolWithScope(t *testing.T, scope *tick.Scope, n tick.Node) (interface{}, error) { + se := mustCompileExpression(t, n) return se.EvalBool(scope) } -func runEvalTests( +func runCompiledEvalTests( t *testing.T, - evalNodeFn func(scope *tick.Scope, n tick.Node) (interface{}, error), + evalNodeFn func(t *testing.T, scope *tick.Scope, n tick.Node) (interface{}, error), createNodeFn func(v interface{}) tick.Node, leftValues []interface{}, rightValues []interface{}, @@ -937,7 +1204,7 @@ func runEvalTests( // Test simple const values compares emptyScope := tick.NewScope() - result, err := evalNodeFn(emptyScope, &tick.BinaryNode{ + result, err := evalNodeFn(t, emptyScope, &tick.BinaryNode{ Operator: op, Left: createNodeFn(lhs), Right: createNodeFn(rhs), @@ -959,7 +1226,7 @@ func runEvalTests( // Test left is reference while the right is const scope := tick.NewScope() scope.Set("value", lhs) - result, err = evalNodeFn(scope, &tick.BinaryNode{ + result, err = evalNodeFn(t, scope, &tick.BinaryNode{ Operator: op, Left: &tick.ReferenceNode{ Reference: "value", @@ -967,10 +1234,14 @@ func runEvalTests( Right: createNodeFn(rhs), }) - if isErrorOk && errorExpected.Error() != err.Error() { - t.Errorf("unexpected error result: %t %v %t\ngot: %v\nexp: %v", lhs, op, rhs, err, errorExpected) + if isErrorOk { + if err == nil { + t.Errorf("reference test: expected an error but got result: %t %v %t\nresult: %t\nerr: %v", lhs, op, rhs, result, err) + } else if errorExpected.Error() != err.Error() { + t.Errorf("reference test: unexpected error result: %t %v %t\ngot: %v\nexp: %v", lhs, op, rhs, err, errorExpected) + } } else if isExpectedResultOk && exp != result { - t.Errorf("unexpected result: %t %v %t\ngot: %v\nexp: %v", lhs, op, rhs, result, exp) + t.Errorf("reference test: unexpected bool result: %t %v %t\ngot: %t\nexp: %t", lhs, op, rhs, result, exp) } } @@ -978,3 +1249,12 @@ func runEvalTests( } } } + +func mustCompileExpression(t *testing.T, node tick.Node) stateful.Expression { + se, err := stateful.NewExpression(node) + if err != nil { + t.Fatalf("Failed to compile expression: %v", err) + } + + return se +} diff --git a/tick/stateful/node_evaluator.go b/tick/stateful/node_evaluator.go new file mode 100644 index 0000000000..30e3721448 --- /dev/null +++ b/tick/stateful/node_evaluator.go @@ -0,0 +1,78 @@ +package stateful + +import ( + "errors" + "fmt" + "regexp" + + "github.com/influxdata/kapacitor/tick" +) + +// ErrTypeGuardFailed is returned when a speicifc value type is requested thorugh NodeEvaluator (for example: "Float64Value") +// when the node doesn't support the given type, for example "Float64Value" is called on BoolNode +type ErrTypeGuardFailed struct { + RequestedType ValueType + ActualType ValueType +} + +func (e ErrTypeGuardFailed) Error() string { + return fmt.Sprintf("expression returned unexpected type %s", e.ActualType) +} + +type ReadOnlyScope interface { + Get(name string) (interface{}, error) +} + +// NodeEvaluator provides a generic way for trying to fetch +// node value, if a speicifc type is requested (so Value isn't called, the *Value is called) ErrTypeGuardFailed must be returned +type NodeEvaluator interface { + EvalFloat(scope *tick.Scope, executionState ExecutionState) (float64, error) + EvalInt(scope *tick.Scope, executionState ExecutionState) (int64, error) + EvalString(scope *tick.Scope, executionState ExecutionState) (string, error) + EvalBool(scope *tick.Scope, executionState ExecutionState) (bool, error) + EvalRegex(scope *tick.Scope, executionState ExecutionState) (*regexp.Regexp, error) + + // Type returns the type of ValueType + Type(scope ReadOnlyScope, executionState ExecutionState) (ValueType, error) +} + +func createNodeEvaluator(n tick.Node) (NodeEvaluator, error) { + switch node := n.(type) { + + case *tick.BoolNode: + return &EvalBoolNode{Node: node}, nil + + case *tick.NumberNode: + switch { + case node.IsFloat: + return &EvalFloatNode{Float64: node.Float64}, nil + + case node.IsInt: + return &EvalIntNode{Int64: node.Int64}, nil + + default: + // We wouldn't reach ever, unless there is bug in tick parsing ;) + return nil, errors.New("Invalid NumberNode: Not float or int") + } + + case *tick.StringNode: + return &EvalStringNode{Node: node}, nil + + case *tick.RegexNode: + return &EvalRegexNode{Node: node}, nil + + case *tick.BinaryNode: + return NewEvalBinaryNode(node) + + case *tick.ReferenceNode: + return &EvalReferenceNode{Node: node}, nil + + case *tick.FunctionNode: + return NewEvalFunctionNode(node) + + case *tick.UnaryNode: + return NewEvalUnaryNode(node) + } + + return nil, fmt.Errorf("Given node type is not valid evaluation node: %T", n) +} diff --git a/tick/stateful/types.go b/tick/stateful/types.go new file mode 100644 index 0000000000..5761656fe1 --- /dev/null +++ b/tick/stateful/types.go @@ -0,0 +1,190 @@ +package stateful + +import ( + "reflect" + "regexp" + + "github.com/influxdata/kapacitor/tick" +) + +type ValueType uint8 + +const ( + InvalidType ValueType = iota << 1 + TFloat64 + TInt64 + TString + TBool + TRegex +) + +const TNumeric = TFloat64 & TInt64 + +func (v ValueType) IsNumeric() bool { + return (v|TInt64 == TInt64) || (v|TFloat64 == TFloat64) +} + +func (v ValueType) String() string { + switch v { + case TFloat64: + return "float64" + case TInt64: + return "int64" + case TString: + return "string" + case TBool: + return "boolean" + case TRegex: + return "regex" + } + + return "invalid type" +} + +func valueTypeOf(t reflect.Type) ValueType { + switch t.Kind() { + case reflect.Float64: + return TFloat64 + case reflect.Int64: + return TInt64 + case reflect.String: + return TString + case reflect.Bool: + return TBool + default: + // This is not primitive type.. so it must be regex + if t == reflect.TypeOf((*regexp.Regexp)(nil)) { + return TRegex + } + return InvalidType + } +} + +// getCostantNodeType - Given a tick.Node we want to know it's return type +// this method does exactly this, few examples: +// *) StringNode -> TString +// *) UnaryNode -> we base the type by the node type +func getConstantNodeType(n tick.Node) ValueType { + switch node := n.(type) { + case *tick.NumberNode: + if node.IsInt { + return TInt64 + } + + if node.IsFloat { + return TFloat64 + } + case *tick.StringNode: + return TString + case *tick.BoolNode: + return TBool + case *tick.RegexNode: + return TRegex + + case *tick.UnaryNode: + // If this is comparison operator we know for sure the output must be boolean + if node.Operator == tick.TokenNot { + return TBool + } + + // if this is math result it can be Int64/Float64, the type decision will be upon + // getConstantNodeType to choose or if it's dynamic node the EvalUnaryNode will choose. + if node.Operator == tick.TokenMinus { + nodeType := getConstantNodeType(node.Node) + if nodeType == InvalidType { + return TNumeric + } + + return nodeType + } + + case *tick.BinaryNode: + if tick.IsCompOperator(node.Operator) { + return TBool + } + + if tick.IsMathOperator(node.Operator) { + // Numeric result shall return, we must be more specific + leftType := getConstantNodeType(node.Left) + + // quick exit here.. + if leftType == TFloat64 { + return TFloat64 + } + + rightType := getConstantNodeType(node.Right) + if rightType == TFloat64 { + return TFloat64 + } + + if leftType == TInt64 && rightType == TInt64 { + return TInt64 + } + + // This is math operator but now float or int, + // This is invalid expression which will get an error while evaluating + // We can return an error right here if we want + return TNumeric + } + } + + return InvalidType +} + +func isDynamicNode(n tick.Node) bool { + switch node := n.(type) { + case *tick.ReferenceNode: + return true + + case *tick.FunctionNode: + return true + + case *tick.UnaryNode: + // unary if dynamic only if it's childs are dynamic + return isDynamicNode(node.Node) + + default: + return false + } + +} + +// findNodeTypes returns the aggregative type of the nodes (it handles corner case like TNumeric) +// should be used by EvalUnaryNode and EvalBinaryNode, won't work well for EvalFunctionNode on it's arg +func findNodeTypes(constantType ValueType, nodes []NodeEvaluator, scope ReadOnlyScope, executionState ExecutionState) (ValueType, error) { + // Generic eval for binary node is a bit tricky! (look at getConstantNodeType before reading this comment!) + // If this is comparison node we know for sure this is bool return + // but if this math operator we can't know it's type if it contain dynamic node (for example "value" - int64(5), the result can be int64 or float64) + // only in the specialization we will know + + // TODO: Fold getConstantType to here + switch constantType { + case TBool: + case TInt64: + case TFloat64: + return constantType, nil + + case TNumeric: + // !! DON'T CACHE THE RESULT in n.Type !! + // This section of code is very tempting to save the type in n.Type + // the reason we reached to TNumeric because one side is dynmic, if it's dynamic + // he can change types, so we. + + // TNumeric means this is TInt64 or TFloat64 + // Enough one is float64, exit , otherwise all are int64 + for _, nodeEvaluator := range nodes { + nodeType, err := nodeEvaluator.Type(scope, executionState) + if err != nil { + return InvalidType, err + } + + if nodeType == TFloat64 { + return TFloat64, nil + } + } + + return TInt64, nil + } + + return constantType, nil +} diff --git a/tick/stateful/types_test.go b/tick/stateful/types_test.go new file mode 100644 index 0000000000..0f36ddb706 --- /dev/null +++ b/tick/stateful/types_test.go @@ -0,0 +1,110 @@ +package stateful + +import ( + "errors" + "reflect" + "regexp" + "strings" + "testing" + + "github.com/influxdata/kapacitor/tick" +) + +func Test_valueTypeOf(t *testing.T) { + type expectation struct { + value interface{} + valueType ValueType + } + + expectations := []expectation{ + {value: float64(0), valueType: TFloat64}, + {value: int64(0), valueType: TInt64}, + {value: "Kapacitor Rulz", valueType: TString}, + {value: true, valueType: TBool}, + {value: regexp.MustCompile("\\d"), valueType: TRegex}, + {value: t, valueType: InvalidType}, + } + + for _, expect := range expectations { + result := valueTypeOf(reflect.TypeOf(expect.value)) + + if result != expect.valueType { + t.Errorf("Got unexpected result for valueTypeOf(%T):\ngot: %s\nexpected: %s", expect.value, result, expect.valueType) + } + + } +} + +func Test_findNodeTypes_constantType(t *testing.T) { + constantTypes := []ValueType{TBool, TInt64, TFloat64} + + for _, valueType := range constantTypes { + result, err := findNodeTypes(valueType, []NodeEvaluator{}, tick.NewScope(), CreateExecutionState()) + if err != nil { + t.Errorf("Got error while trying to findNodeTypes for value type %q: %v", valueType, err) + continue + } + + if result != valueType { + t.Errorf("Got unexpected result:\ngot: %s\nexpected: %s\n", result, valueType) + } + } +} + +func Test_findNodeTypes_TNumeric(t *testing.T) { + type expectation struct { + evaluators []NodeEvaluator + valueType ValueType + } + + expectations := []expectation{ + { + evaluators: []NodeEvaluator{&EvalFloatNode{}}, + valueType: TFloat64, + }, + + { + evaluators: []NodeEvaluator{&EvalIntNode{}}, + valueType: TInt64, + }, + + { + evaluators: []NodeEvaluator{ + &EvalFloatNode{}, + &EvalIntNode{}, + }, + valueType: TFloat64, + }, + } + + for _, expect := range expectations { + result, err := findNodeTypes(TNumeric, expect.evaluators, tick.NewScope(), CreateExecutionState()) + if err != nil { + t.Errorf("Got error while trying to findNodeTypes for value type %q: %v", expect.valueType, err) + continue + } + + if result != expect.valueType { + t.Errorf("Got unexpected result:\ngot: %s\nexpected: %s\n", result, expect.valueType) + } + } +} + +func Test_findNodeTypes_TNumericWithEvalError(t *testing.T) { + refNodeEvaluator, err := createNodeEvaluator(&tick.ReferenceNode{Reference: "value"}) + if err != nil { + t.Fatalf("Failed to create reference node evaluator for test: %v", err) + } + + result, err := findNodeTypes(TNumeric, []NodeEvaluator{refNodeEvaluator}, tick.NewScope(), CreateExecutionState()) + expectedError := errors.New("name \"value\" is undefined. Names in scope:") + + if err == nil { + t.Errorf("Expected an error, but got nil error and result: %q", result) + return + } + + if strings.TrimSpace(err.Error()) != expectedError.Error() { + t.Errorf("Got unexpected error:\ngot: %v\nexpected: %v\n", err, expectedError) + } +} diff --git a/tick/stateful_expr.go b/tick/stateful_expr.go deleted file mode 100644 index 95c6d1cc52..0000000000 --- a/tick/stateful_expr.go +++ /dev/null @@ -1,365 +0,0 @@ -package tick - -import ( - "errors" - "fmt" - "math" - "regexp" -) - -var ErrInvalidExpr = errors.New("expression is invalid, could not evaluate") - -// Expression functions are stateful. Their state is updated with -// each call to the function. A StatefulExpr is a Node -// and its associated function state. -type StatefulExpr struct { - Node Node - Funcs Funcs -} - -func NewStatefulExpr(n Node) *StatefulExpr { - return &StatefulExpr{ - Node: n, - Funcs: NewFunctions(), - } -} - -// Reset the state -func (s *StatefulExpr) Reset() { - for _, f := range s.Funcs { - f.Reset() - } -} - -func (s *StatefulExpr) EvalBool(scope *Scope) (bool, error) { - stck := &stack{} - err := s.eval(s.Node, scope, stck) - if err != nil { - return false, err - } - if stck.Len() == 1 { - value := stck.Pop() - // Resolve reference - if ref, ok := value.(*ReferenceNode); ok { - value, err = scope.Get(ref.Reference) - if err != nil { - return false, err - } - } - b, ok := value.(bool) - if ok { - return b, nil - } else { - return false, fmt.Errorf("expression returned unexpected type %T", value) - } - } - return false, ErrInvalidExpr -} - -func (s *StatefulExpr) EvalNum(scope *Scope) (interface{}, error) { - stck := &stack{} - err := s.eval(s.Node, scope, stck) - if err != nil { - return math.NaN(), err - } - if stck.Len() == 1 { - value := stck.Pop() - // Resolve reference - if ref, ok := value.(*ReferenceNode); ok { - value, err = scope.Get(ref.Reference) - if err != nil { - return math.NaN(), err - } - } - switch value.(type) { - case float64, int64: - return value, nil - default: - return math.NaN(), fmt.Errorf("expression returned unexpected type %T", value) - } - } - return math.NaN(), ErrInvalidExpr -} - -func (s *StatefulExpr) eval(n Node, scope *Scope, stck *stack) (err error) { - switch node := n.(type) { - case *BoolNode: - stck.Push(node.Bool) - case *NumberNode: - if node.IsInt { - stck.Push(node.Int64) - } else { - stck.Push(node.Float64) - } - case *DurationNode: - stck.Push(node.Dur) - case *StringNode: - stck.Push(node.Literal) - case *RegexNode: - stck.Push(node.Regex) - case *UnaryNode: - err = s.eval(node.Node, scope, stck) - if err != nil { - return - } - s.evalUnary(node.Operator, scope, stck) - case *BinaryNode: - err = s.eval(node.Left, scope, stck) - if err != nil { - return - } - err = s.eval(node.Right, scope, stck) - if err != nil { - return - } - err = s.evalBinary(node.Operator, scope, stck) - if err != nil { - return - } - case *FunctionNode: - args := make([]interface{}, len(node.Args)) - for i, arg := range node.Args { - err = s.eval(arg, scope, stck) - if err != nil { - return - } - a := stck.Pop() - if r, ok := a.(*ReferenceNode); ok { - a, err = scope.Get(r.Reference) - if err != nil { - return err - } - } - args[i] = a - } - // Call function - f := s.Funcs[node.Func] - if f == nil { - return fmt.Errorf("undefined function %s", node.Func) - } - ret, err := f.Call(args...) - if err != nil { - return fmt.Errorf("error calling %s: %s", node.Func, err) - } - stck.Push(ret) - default: - stck.Push(node) - } - return nil -} - -func (s *StatefulExpr) evalUnary(op TokenType, scope *Scope, stck *stack) error { - v := stck.Pop() - switch op { - case TokenMinus: - switch n := v.(type) { - case float64: - stck.Push(-1 * n) - case int64: - stck.Push(-1 * n) - default: - return fmt.Errorf("invalid arugument to '-' %v", v) - } - case TokenNot: - if b, ok := v.(bool); ok { - stck.Push(!b) - } else { - return fmt.Errorf("invalid arugument to '!' %v", v) - } - } - return nil -} - -func errMismatched(op TokenType, l, r interface{}) error { - return fmt.Errorf("mismatched type to binary operator. got %T %v %T. see bool(), int(), float()", l, op, r) -} - -func (s *StatefulExpr) evalBinary(op TokenType, scope *Scope, stck *stack) (err error) { - r := stck.Pop() - l := stck.Pop() - // Resolve any references - if ref, ok := l.(*ReferenceNode); ok { - l, err = scope.Get(ref.Reference) - if err != nil { - return err - } - } - if ref, ok := r.(*ReferenceNode); ok { - r, err = scope.Get(ref.Reference) - if err != nil { - return err - } - } - var v interface{} - switch { - case isMathOperator(op): - switch ln := l.(type) { - case int64: - rn, ok := r.(int64) - if !ok { - return errMismatched(op, l, r) - } - v, err = doIntMath(op, ln, rn) - case float64: - rn, ok := r.(float64) - if !ok { - return errMismatched(op, l, r) - } - v, err = doFloatMath(op, ln, rn) - default: - return errMismatched(op, l, r) - } - case isCompOperator(op): - switch ln := l.(type) { - case bool: - rn, ok := r.(bool) - if !ok { - return errMismatched(op, l, r) - } - v, err = doBoolComp(op, ln, rn) - case int64: - lf := float64(ln) - var rf float64 - switch rn := r.(type) { - case int64: - rf = float64(rn) - case float64: - rf = rn - default: - return errMismatched(op, l, r) - } - v, err = doFloatComp(op, lf, rf) - case float64: - var rf float64 - switch rn := r.(type) { - case int64: - rf = float64(rn) - case float64: - rf = rn - default: - return errMismatched(op, l, r) - } - v, err = doFloatComp(op, ln, rf) - case string: - rn, ok := r.(string) - if ok { - v, err = doStringComp(op, ln, rn) - } else if rx, ok := r.(*regexp.Regexp); ok { - v, err = doRegexComp(op, ln, rx) - } else { - return errMismatched(op, l, r) - } - default: - return errMismatched(op, l, r) - } - default: - return fmt.Errorf("return: unknown operator %v", op) - } - if err != nil { - return - } - stck.Push(v) - return -} - -func doIntMath(op TokenType, l, r int64) (v int64, err error) { - switch op { - case TokenPlus: - v = l + r - case TokenMinus: - v = l - r - case TokenMult: - v = l * r - case TokenDiv: - v = l / r - case TokenMod: - v = l % r - default: - return 0, fmt.Errorf("invalid integer math operator %v", op) - } - return -} - -func doFloatMath(op TokenType, l, r float64) (v float64, err error) { - switch op { - case TokenPlus: - v = l + r - case TokenMinus: - v = l - r - case TokenMult: - v = l * r - case TokenDiv: - v = l / r - default: - return math.NaN(), fmt.Errorf("invalid float math operator %v", op) - } - return -} - -func doBoolComp(op TokenType, l, r bool) (v bool, err error) { - switch op { - case TokenEqual: - v = l == r - case TokenNotEqual: - v = l != r - case TokenAnd: - v = l && r - case TokenOr: - v = l || r - default: - err = fmt.Errorf("invalid boolean comparison operator %v", op) - } - return -} - -func doFloatComp(op TokenType, l, r float64) (v bool, err error) { - switch op { - case TokenEqual: - v = l == r - case TokenNotEqual: - v = l != r - case TokenLess: - v = l < r - case TokenGreater: - v = l > r - case TokenLessEqual: - v = l <= r - case TokenGreaterEqual: - v = l >= r - default: - err = fmt.Errorf("invalid float comparison operator %v", op) - } - return -} - -func doStringComp(op TokenType, l, r string) (v bool, err error) { - switch op { - case TokenEqual: - v = l == r - case TokenNotEqual: - v = l != r - case TokenLess: - v = l < r - case TokenGreater: - v = l > r - case TokenLessEqual: - v = l <= r - case TokenGreaterEqual: - v = l >= r - default: - err = fmt.Errorf("invalid string comparison operator %v", op) - } - return -} - -func doRegexComp(op TokenType, l string, r *regexp.Regexp) (v bool, err error) { - switch op { - case TokenRegexEqual: - v = r.MatchString(l) - case TokenRegexNotEqual: - v = !r.MatchString(l) - default: - err = fmt.Errorf("invalid regex comparison operator %v", op) - } - return -} diff --git a/where.go b/where.go index ed71274e06..8a2d05b4b6 100644 --- a/where.go +++ b/where.go @@ -2,18 +2,19 @@ package kapacitor import ( "errors" + "fmt" "log" "github.com/influxdata/kapacitor/models" "github.com/influxdata/kapacitor/pipeline" - "github.com/influxdata/kapacitor/tick" + "github.com/influxdata/kapacitor/tick/stateful" ) type WhereNode struct { node w *pipeline.WhereNode endpoint string - expressions map[models.GroupID]*tick.StatefulExpr + expressions map[models.GroupID]stateful.Expression } // Create a new WhereNode which filters down the batch or stream by a condition @@ -21,7 +22,7 @@ func newWhereNode(et *ExecutingTask, n *pipeline.WhereNode, l *log.Logger) (wn * wn = &WhereNode{ node: node{Node: n, et: et, logger: l}, w: n, - expressions: make(map[models.GroupID]*tick.StatefulExpr), + expressions: make(map[models.GroupID]stateful.Expression), } wn.runF = wn.runWhere if n.Expression == nil { @@ -37,7 +38,12 @@ func (w *WhereNode) runWhere(snapshot []byte) error { w.timer.Start() expr := w.expressions[p.Group] if expr == nil { - expr = tick.NewStatefulExpr(w.w.Expression) + compiledExpr, err := stateful.NewExpression(w.w.Expression) + if err != nil { + return fmt.Errorf("Failed to compile expression in where clause: %v", err) + } + + expr = compiledExpr w.expressions[p.Group] = expr } if pass, err := EvalPredicate(expr, p.Time, p.Fields, p.Tags); pass { @@ -59,7 +65,12 @@ func (w *WhereNode) runWhere(snapshot []byte) error { w.timer.Start() expr := w.expressions[b.Group] if expr == nil { - expr = tick.NewStatefulExpr(w.w.Expression) + compiledExpr, err := stateful.NewExpression(w.w.Expression) + if err != nil { + return fmt.Errorf("Failed to compile expression in where clause: %v", err) + } + + expr = compiledExpr w.expressions[b.Group] = expr } for i := 0; i < len(b.Points); {