diff --git a/fixtures/multi_vql_queries.golden b/fixtures/multi_vql_queries.golden index 7aaff23..e8e8fc1 100644 --- a/fixtures/multi_vql_queries.golden +++ b/fixtures/multi_vql_queries.golden @@ -1431,5 +1431,39 @@ { "Total": 3 } + ], + "092/000 Negative: LET X \u003c= 1 + -2": null, + "092/001 Negative: SELECT X FROM scope()": [ + { + "X": -1 + } + ], + "093/000 Negative index: LET X \u003c= 2": null, + "093/001 Negative index: LET Bar(X) = X + 3": null, + "093/002 Negative index: LET Foo \u003c= (1, 2 + X, 3, 4 + -5)": null, + "093/003 Negative index: LET Float \u003c= 12.232": null, + "093/004 Negative index: LET StoredQuery = SELECT * FROM info()": null, + "093/005 Negative index: SELECT Foo[-2], Foo[-X], -X, -`X`, -Bar(X=3), -Float, -StoredQuery, Foo[-2:], Foo[-X:], Foo[-Bar(X=-1):] FROM scope()": [ + { + "Foo[-2]": 3, + "Foo[-X]": 3, + "-X": -2, + "-`X`": -2, + "-Bar(X=3)": -6, + "-Float": -12.232, + "-StoredQuery": null, + "Foo[-2:]": [ + 3, + -1 + ], + "Foo[-X:]": [ + 3, + -1 + ], + "Foo[-Bar(X=-1):]": [ + 3, + -1 + ] + } ] } \ No newline at end of file diff --git a/vfilter.go b/vfilter.go index 1ae5448..c92c356 100644 --- a/vfilter.go +++ b/vfilter.go @@ -168,11 +168,14 @@ var ( `|(?ims)(?P\bORDER\s+BY\b)` + `|(?ims)(?P\bTRUE\b|\bFALSE\b)` + `|(?ims)(?P\bLET\b)` + + `|(?P-?(0x[0-9a-f]+|\d*\.?\d+([eE][-+]?\d+)?))` + "|(?P[a-zA-Z_][a-zA-Z0-9_]*|`[^`]+`)" + + "|(?P-[a-zA-Z_][a-zA-Z0-9_]*|-`[^`]+`)" + + `|(?P<>|!=|<=|>=|=>|=~|[-]|[+]|[:*/%,.()=<>{}\[\]])` + `|''(?P'.*?')''` + `|(?P'([^'\\]*(\\.[^'\\]*)*)'|"([^"\\]*(\\.[^"\\]*)*)")` + - `|(?P[-+]?(0x[0-9a-f]+|\d*\.?\d+([eE][-+]?\d+)?))` + - `|(?P<>|!=|<=|>=|=>|=~|[-:+*/%,.()=<>{}\[\]])`, + + "", )) vqlParser = participle.MustBuild( @@ -404,15 +407,24 @@ func (self *VQL) Eval(ctx context.Context, scope types.Scope) <-chan Row { // LET is for stored query: LET X = SELECT ... switch self.LetOperator { case "=": - stored_query := NewStoredQuery(self.StoredQuery, name) - stored_query.parameters, stored_query.defaults = self.getParameters() + if self.StoredQuery == nil { + scope.AppendVars(ordereddict.NewDict().Set(name, Null{})) + } else { + stored_query := NewStoredQuery(self.StoredQuery, name) + stored_query.parameters, stored_query.defaults = self.getParameters() - scope.AppendVars(ordereddict.NewDict().Set(name, stored_query)) + scope.AppendVars(ordereddict.NewDict().Set(name, stored_query)) + } case "<=": - // Delegate to the scope's materializer to actually - // materialize this query. - scope.AppendVars(ordereddict.NewDict().Set( - name, scope.Materialize(ctx, name, self.StoredQuery))) + if self.StoredQuery == nil { + scope.AppendVars(ordereddict.NewDict().Set(name, Null{})) + } else { + + // Delegate to the scope's materializer to actually + // materialize this query. + scope.AppendVars(ordereddict.NewDict().Set( + name, scope.Materialize(ctx, name, self.StoredQuery))) + } } close(output_chan) @@ -427,6 +439,10 @@ func (self *VQL) Eval(ctx context.Context, scope types.Scope) <-chan Row { defer close(output_chan) defer subscope.Close() + if self.Query == nil { + return + } + row_chan := self.Query.Eval(ctx, subscope) for { select { @@ -584,9 +600,14 @@ func (self *_Select) Eval(ctx context.Context, scope types.Scope) <-chan Row { // be relayed. NOTE: We need to transform the row first in // order to assign aliases. go func() { + defer close(output_chan) + + if self.From == nil { + return + } + from_chan := self.From.Eval(ctx, scope) - defer close(output_chan) for { select { // Are we cancelled? @@ -815,11 +836,6 @@ type _OpMembershipTerm struct { Term *string ` "." @Ident )` } -type _SliceRange struct { - X *string `( { @Number} ":" ` - RangeRightStr *string ` { @Number } )` -} - // --------------------------------------- // The Top level precedence expression. Precedence table: @@ -892,9 +908,10 @@ type _Term struct { type _SymbolRef struct { Comments []*_Comment ` [ @@ ] ` - Symbol string `@Ident { @"." @Ident }` - Called bool `{ @"(" ` - Parameters []*_Args ` [ @@ { "," @@ } ] ")" } ` + Negated bool + Symbol string `(@SymbolIdent | @Ident) { @"." @Ident }` + Called bool `{ @"(" ` + Parameters []*_Args ` [ @@ { "," @@ } ] ")" } ` mu sync.Mutex function FunctionInterface @@ -903,7 +920,6 @@ type _SymbolRef struct { type _Value struct { Comments []*_Comment ` [ @@ ] ` - Negated bool `[ "-" | "+" ]` SymbolRef *_SymbolRef `( @@ ` Subexpression *_CommaExpression `| "(" @@ ")"` @@ -1608,21 +1624,25 @@ func (self *_SymbolRef) IsAggregate(scope types.Scope) bool { return value.Info(scope, types.NewTypeMap()).IsAggregate } -func (self *_SymbolRef) getFunction(scope types.Scope) (types.Any, bool) { +func (self *_SymbolRef) getFunction(scope types.Scope) (res types.Any, negated, pres bool) { self.mu.Lock() components := self.split_symbol if components == nil { components = utils.SplitIdent(self.Symbol) + if len(components) > 0 && strings.HasPrefix(components[0], "-") { + negated = true + components[0] = strings.TrimPrefix(components[0], "-") + } self.split_symbol = components } self.mu.Unlock() // Single item reference and called - call built in function. if len(components) == 1 && self.Called { - res, pres := scope.GetFunction(self.Symbol) + res, pres := scope.GetFunction(components[0]) if pres { - return res, pres + return res, negated, pres } } @@ -1645,13 +1665,46 @@ func (self *_SymbolRef) getFunction(scope types.Scope) (types.Any, bool) { } } - return nil, false + return nil, negated, false } result = subcomponent } - return result, true + return result, negated, true +} + +func (self *_SymbolRef) maybeNegate( + ctx context.Context, scope types.Scope, + negate bool, value Any) Any { + if !negate { + return value + } + + switch t := value.(type) { + case StoredExpression: + return self.maybeNegate(ctx, scope, negate, t.Reduce(ctx, scope)) + + case LazyExpr: + return self.maybeNegate(ctx, scope, negate, t.ReduceWithScope(ctx, scope)) + + case bool: + return !t + + case float64: + return -t + + case float32: + return -t + + default: + i, ok := utils.ToInt64(value) + if ok { + return -i + } + } + + return Null{} } func (self *_SymbolRef) Reduce(ctx context.Context, scope types.Scope) Any { @@ -1659,7 +1712,7 @@ func (self *_SymbolRef) Reduce(ctx context.Context, scope types.Scope) Any { // The symbol is just a constant in the scope. It may be a // stored expression, a function or a stored query or just a // plain value. - value, pres := self.getFunction(scope) + value, negated, pres := self.getFunction(scope) if value != nil && pres { switch t := value.(type) { case FunctionInterface: @@ -1670,7 +1723,8 @@ func (self *_SymbolRef) Reduce(ctx context.Context, scope types.Scope) Any { } // The symbol is a function and this is a call site, e.g. Symbol(...) - return self.callFunction(ctx, scope, t) + return self.maybeNegate(ctx, scope, negated, + self.callFunction(ctx, scope, t)) // If the symbol is a stored expression we evaluated // it. @@ -1694,7 +1748,7 @@ func (self *_SymbolRef) Reduce(ctx context.Context, scope types.Scope) Any { scope.GetStats().IncFunctionsCalled() - return t.Reduce(ctx, subscope) + return self.maybeNegate(ctx, scope, negated, t.Reduce(ctx, subscope)) case StoredQuery: // If the call site specifies parameters then @@ -1724,10 +1778,11 @@ func (self *_SymbolRef) Reduce(ctx context.Context, scope types.Scope) Any { scope.GetStats().IncFunctionsCalled() // Wrap the query with the captured scope. - return &StoredQueryCallSite{ - query: t, - scope: subscope, - } + return self.maybeNegate(ctx, scope, negated, + &StoredQueryCallSite{ + query: t, + scope: subscope, + }) } } @@ -1738,7 +1793,7 @@ func (self *_SymbolRef) Reduce(ctx context.Context, scope types.Scope) Any { } // Every thing else is taken literally. - return value + return self.maybeNegate(ctx, scope, negated, value) } return Null{} diff --git a/vfilter_test.go b/vfilter_test.go index 4ab7ab7..8fef46e 100644 --- a/vfilter_test.go +++ b/vfilter_test.go @@ -1363,6 +1363,23 @@ LET F(X = 2, Y ) = FROM scope() SELECT * FROM F(Y=1) +`}, + {"Negative", + "LET X <= 1 + -2 SELECT X FROM scope()"}, + {"Negative index", + ` +LET X <= 2 +LET Bar(X) = X + 3 +LET Foo <= (1, 2+X, 3, 4+ -5) +LET Float <= 12.232 +LET StoredQuery = SELECT * FROM info() + +SELECT Foo[ -2 ], Foo[ -X ], -X, ` + "-`X`, " + ` + -Bar(X=3), -Float, -StoredQuery, + Foo[-2:], + Foo[-X:], + Foo[-Bar(X=-1):] +FROM scope() `}, } @@ -1563,7 +1580,7 @@ func TestMultiVQLQueries(t *testing.T) { // Store the result in ordered dict so we have a consistent golden file. result := ordereddict.NewDict() for i, testCase := range multiVQLTest { - if false && i != 66 { + if false && i != 93 { continue } scope := makeTestScope() diff --git a/visitor.go b/visitor.go index 03e3ed7..be3ade3 100644 --- a/visitor.go +++ b/visitor.go @@ -681,11 +681,6 @@ func (self *Visitor) visitValue(node *_Value) { self.Visit(node.Comments) node.maybeParseStrNumber(self.scope) - factor := 1.0 - if node.Negated { - factor = -1.0 - } - symbolref := node.SymbolRef if symbolref != nil { node.mu.Unlock() @@ -712,18 +707,14 @@ func (self *Visitor) visitValue(node *_Value) { } if node.Int != nil { - factor := int64(1) - if node.Negated { - factor = -1 - } - self.push(strconv.FormatInt(factor**node.Int, 10)) + self.push(strconv.FormatInt(*node.Int, 10)) node.mu.Unlock() return } if node.Float != nil { - result := strconv.FormatFloat(factor**node.Float, 'f', -1, 64) + result := strconv.FormatFloat(*node.Float, 'f', -1, 64) if !strings.Contains(result, ".") { result = result + ".0" }