Compare commits

...

5 Commits

Author SHA1 Message Date
Tushar Vats
9102e4ccc6 fix: removed validation on having in range request validations 2026-02-19 21:04:38 +05:30
Tushar Vats
74b1df2941 fix: added more unit tests 2026-02-19 20:54:38 +05:30
Tushar Vats
a8348b6395 fix: added antlr based parsing for validation 2026-02-19 17:47:24 +05:30
Tushar Vats
2aedf5f7e6 fix: added extra validation and unit tests 2026-02-19 15:49:37 +05:30
Tushar Vats
a77a4d4daa fix: added validations for having expression 2026-02-12 20:14:51 +05:30
37 changed files with 4646 additions and 34 deletions

188
grammar/HavingExpression.g4 Normal file
View File

@@ -0,0 +1,188 @@
grammar HavingExpression;
/*
* Parser Rules
*/
query
: expression EOF
;
// Expression with standard boolean precedence:
// - parentheses > NOT > AND > OR
expression
: orExpression
;
// OR expressions
orExpression
: andExpression ( OR andExpression )*
;
// AND expressions
andExpression
: primary ( AND primary )*
;
// Primary: an optionally negated parenthesized expression, or a comparison.
// NOT is only allowed on grouped expressions, not on bare comparisons.
// E.g.: NOT (count() > 100 AND sum(bytes) < 500)
primary
: NOT? LPAREN orExpression RPAREN
| comparison
;
/*
* Comparison between two arithmetic operands.
* E.g.: count() > 100, total_duration >= 500, __result_0 != 0
*/
comparison
: operand compOp operand
;
compOp
: EQUALS
| NOT_EQUALS
| NEQ
| LT
| LE
| GT
| GE
;
/*
* Operands support additive arithmetic (+/-).
* E.g.: sum(a) + sum(b) > 1000, count() - 10 > 0
*/
operand
: operand (PLUS | MINUS) term
| term
;
/*
* Terms support multiplicative arithmetic (*, /, %)
* E.g.: count() * 2 > 100, sum(bytes) / 1024 > 10
*/
term
: term (STAR | SLASH | PERCENT) factor
| factor
;
/*
* Factors: atoms or parenthesized operands for arithmetic grouping.
* E.g.: (sum(a) + sum(b)) * 2 > 100
*/
factor
: LPAREN operand RPAREN
| atom
;
/*
* Atoms are the basic building blocks of arithmetic operands:
* - aggregate function calls: count(), sum(bytes), avg(duration)
* - identifier references: aliases, result refs (__result, __result_0, __result0)
* - numeric literals: 100, 0.5, 1e6
*
* Quoted string literals are intentionally excluded — HAVING expressions
* compare aggregate results which are always numeric.
*/
atom
: functionCall
| identifier
| NUMBER
;
/*
* Aggregate function calls:
* - count()
* - sum(bytes)
* - avg(duration_nano)
*
* Arguments are restricted to a single column identifier.
* Nested function calls are not valid in HAVING expressions —
* reference nested aggregations by alias or expression string instead.
*/
functionCall
: IDENTIFIER LPAREN functionArgs? RPAREN
;
functionArgs
: functionArg ( COMMA functionArg )*
;
// Function argument: a column being aggregated (e.g. bytes, duration_nano)
functionArg
: IDENTIFIER
;
// Identifier references: aliases, field names, result references
// Examples: total_logs, error_count, __result, __result_0, __result0, p99
identifier
: IDENTIFIER
;
/*
* Lexer Rules
*/
// Punctuation
LPAREN : '(' ;
RPAREN : ')' ;
COMMA : ',' ;
// Comparison operators
EQUALS : '=' | '==' ;
NOT_EQUALS : '!=' ;
NEQ : '<>' ; // alternate not-equals operator
LT : '<' ;
LE : '<=' ;
GT : '>' ;
GE : '>=' ;
// Arithmetic operators
PLUS : '+' ;
MINUS : '-' ;
STAR : '*' ;
SLASH : '/' ;
PERCENT : '%' ;
// Boolean logic (case-insensitive)
NOT : [Nn][Oo][Tt] ;
AND : [Aa][Nn][Dd] ;
OR : [Oo][Rr] ;
// Boolean constants (case-insensitive)
BOOL
: [Tt][Rr][Uu][Ee]
| [Ff][Aa][Ll][Ss][Ee]
;
fragment SIGN : [+-] ;
// Numbers: optional sign, digits, optional decimal, optional scientific notation
// E.g.: 100, -10, 0.5, 1.5e3, .75, -3.14
NUMBER
: SIGN? DIGIT+ ('.' DIGIT*)? ([eE] SIGN? DIGIT+)?
| SIGN? '.' DIGIT+ ([eE] SIGN? DIGIT+)?
;
// Quoted string literals (double or single-quoted, with escape support)
QUOTED_TEXT
: '"' ( ~["\\] | '\\' . )* '"'
| '\'' ( ~['\\] | '\\' . )* '\''
;
// Identifiers: start with a letter or underscore, followed by alphanumeric/underscores.
// Optionally dotted for nested field paths.
// Covers: count, sum, p99, total_logs, error_count, __result, __result_0, __result0,
// service.name, span.duration
IDENTIFIER
: [a-zA-Z_] [a-zA-Z0-9_]* ( '.' [a-zA-Z_] [a-zA-Z0-9_]* )*
;
// Skip whitespace
WS
: [ \t\r\n]+ -> skip
;
fragment DIGIT : [0-9] ;

View File

@@ -5,7 +5,7 @@ import (
"slices"
"strings"
parser "github.com/SigNoz/signoz/pkg/parser/grammar"
parser "github.com/SigNoz/signoz/pkg/parser/grammar/filterquery"
"github.com/antlr4-go/antlr/v4"
"golang.org/x/exp/maps"

View File

@@ -1,6 +1,6 @@
// Code generated from grammar/FilterQuery.g4 by ANTLR 4.13.2. DO NOT EDIT.
package parser // FilterQuery
package filterquery // FilterQuery
import "github.com/antlr4-go/antlr/v4"

View File

@@ -1,6 +1,6 @@
// Code generated from grammar/FilterQuery.g4 by ANTLR 4.13.2. DO NOT EDIT.
package parser // FilterQuery
package filterquery // FilterQuery
import "github.com/antlr4-go/antlr/v4"

View File

@@ -1,6 +1,6 @@
// Code generated from grammar/FilterQuery.g4 by ANTLR 4.13.2. DO NOT EDIT.
package parser
package filterquery
import (
"fmt"

View File

@@ -1,6 +1,6 @@
// Code generated from grammar/FilterQuery.g4 by ANTLR 4.13.2. DO NOT EDIT.
package parser // FilterQuery
package filterquery // FilterQuery
import "github.com/antlr4-go/antlr/v4"

View File

@@ -1,6 +1,6 @@
// Code generated from grammar/FilterQuery.g4 by ANTLR 4.13.2. DO NOT EDIT.
package parser // FilterQuery
package filterquery // FilterQuery
import (
"fmt"

View File

@@ -1,6 +1,6 @@
// Code generated from grammar/FilterQuery.g4 by ANTLR 4.13.2. DO NOT EDIT.
package parser // FilterQuery
package filterquery // FilterQuery
import "github.com/antlr4-go/antlr/v4"

View File

@@ -0,0 +1,72 @@
token literal names:
null
'('
')'
','
null
'!='
'<>'
'<'
'<='
'>'
'>='
'+'
'-'
'*'
'/'
'%'
null
null
null
null
null
null
null
null
token symbolic names:
null
LPAREN
RPAREN
COMMA
EQUALS
NOT_EQUALS
NEQ
LT
LE
GT
GE
PLUS
MINUS
STAR
SLASH
PERCENT
NOT
AND
OR
BOOL
NUMBER
QUOTED_TEXT
IDENTIFIER
WS
rule names:
query
expression
orExpression
andExpression
primary
comparison
compOp
operand
term
factor
atom
functionCall
functionArgs
functionArg
identifier
atn:
[4, 1, 23, 121, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 5, 2, 39, 8, 2, 10, 2, 12, 2, 42, 9, 2, 1, 3, 1, 3, 1, 3, 5, 3, 47, 8, 3, 10, 3, 12, 3, 50, 9, 3, 1, 4, 3, 4, 53, 8, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 3, 4, 60, 8, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 5, 7, 74, 8, 7, 10, 7, 12, 7, 77, 9, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 5, 8, 85, 8, 8, 10, 8, 12, 8, 88, 9, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 3, 9, 95, 8, 9, 1, 10, 1, 10, 1, 10, 3, 10, 100, 8, 10, 1, 11, 1, 11, 1, 11, 3, 11, 105, 8, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 5, 12, 112, 8, 12, 10, 12, 12, 12, 115, 9, 12, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 0, 2, 14, 16, 15, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 0, 3, 1, 0, 4, 10, 1, 0, 11, 12, 1, 0, 13, 15, 116, 0, 30, 1, 0, 0, 0, 2, 33, 1, 0, 0, 0, 4, 35, 1, 0, 0, 0, 6, 43, 1, 0, 0, 0, 8, 59, 1, 0, 0, 0, 10, 61, 1, 0, 0, 0, 12, 65, 1, 0, 0, 0, 14, 67, 1, 0, 0, 0, 16, 78, 1, 0, 0, 0, 18, 94, 1, 0, 0, 0, 20, 99, 1, 0, 0, 0, 22, 101, 1, 0, 0, 0, 24, 108, 1, 0, 0, 0, 26, 116, 1, 0, 0, 0, 28, 118, 1, 0, 0, 0, 30, 31, 3, 2, 1, 0, 31, 32, 5, 0, 0, 1, 32, 1, 1, 0, 0, 0, 33, 34, 3, 4, 2, 0, 34, 3, 1, 0, 0, 0, 35, 40, 3, 6, 3, 0, 36, 37, 5, 18, 0, 0, 37, 39, 3, 6, 3, 0, 38, 36, 1, 0, 0, 0, 39, 42, 1, 0, 0, 0, 40, 38, 1, 0, 0, 0, 40, 41, 1, 0, 0, 0, 41, 5, 1, 0, 0, 0, 42, 40, 1, 0, 0, 0, 43, 48, 3, 8, 4, 0, 44, 45, 5, 17, 0, 0, 45, 47, 3, 8, 4, 0, 46, 44, 1, 0, 0, 0, 47, 50, 1, 0, 0, 0, 48, 46, 1, 0, 0, 0, 48, 49, 1, 0, 0, 0, 49, 7, 1, 0, 0, 0, 50, 48, 1, 0, 0, 0, 51, 53, 5, 16, 0, 0, 52, 51, 1, 0, 0, 0, 52, 53, 1, 0, 0, 0, 53, 54, 1, 0, 0, 0, 54, 55, 5, 1, 0, 0, 55, 56, 3, 4, 2, 0, 56, 57, 5, 2, 0, 0, 57, 60, 1, 0, 0, 0, 58, 60, 3, 10, 5, 0, 59, 52, 1, 0, 0, 0, 59, 58, 1, 0, 0, 0, 60, 9, 1, 0, 0, 0, 61, 62, 3, 14, 7, 0, 62, 63, 3, 12, 6, 0, 63, 64, 3, 14, 7, 0, 64, 11, 1, 0, 0, 0, 65, 66, 7, 0, 0, 0, 66, 13, 1, 0, 0, 0, 67, 68, 6, 7, -1, 0, 68, 69, 3, 16, 8, 0, 69, 75, 1, 0, 0, 0, 70, 71, 10, 2, 0, 0, 71, 72, 7, 1, 0, 0, 72, 74, 3, 16, 8, 0, 73, 70, 1, 0, 0, 0, 74, 77, 1, 0, 0, 0, 75, 73, 1, 0, 0, 0, 75, 76, 1, 0, 0, 0, 76, 15, 1, 0, 0, 0, 77, 75, 1, 0, 0, 0, 78, 79, 6, 8, -1, 0, 79, 80, 3, 18, 9, 0, 80, 86, 1, 0, 0, 0, 81, 82, 10, 2, 0, 0, 82, 83, 7, 2, 0, 0, 83, 85, 3, 18, 9, 0, 84, 81, 1, 0, 0, 0, 85, 88, 1, 0, 0, 0, 86, 84, 1, 0, 0, 0, 86, 87, 1, 0, 0, 0, 87, 17, 1, 0, 0, 0, 88, 86, 1, 0, 0, 0, 89, 90, 5, 1, 0, 0, 90, 91, 3, 14, 7, 0, 91, 92, 5, 2, 0, 0, 92, 95, 1, 0, 0, 0, 93, 95, 3, 20, 10, 0, 94, 89, 1, 0, 0, 0, 94, 93, 1, 0, 0, 0, 95, 19, 1, 0, 0, 0, 96, 100, 3, 22, 11, 0, 97, 100, 3, 28, 14, 0, 98, 100, 5, 20, 0, 0, 99, 96, 1, 0, 0, 0, 99, 97, 1, 0, 0, 0, 99, 98, 1, 0, 0, 0, 100, 21, 1, 0, 0, 0, 101, 102, 5, 22, 0, 0, 102, 104, 5, 1, 0, 0, 103, 105, 3, 24, 12, 0, 104, 103, 1, 0, 0, 0, 104, 105, 1, 0, 0, 0, 105, 106, 1, 0, 0, 0, 106, 107, 5, 2, 0, 0, 107, 23, 1, 0, 0, 0, 108, 113, 3, 26, 13, 0, 109, 110, 5, 3, 0, 0, 110, 112, 3, 26, 13, 0, 111, 109, 1, 0, 0, 0, 112, 115, 1, 0, 0, 0, 113, 111, 1, 0, 0, 0, 113, 114, 1, 0, 0, 0, 114, 25, 1, 0, 0, 0, 115, 113, 1, 0, 0, 0, 116, 117, 5, 22, 0, 0, 117, 27, 1, 0, 0, 0, 118, 119, 5, 22, 0, 0, 119, 29, 1, 0, 0, 0, 10, 40, 48, 52, 59, 75, 86, 94, 99, 104, 113]

View File

@@ -0,0 +1,37 @@
LPAREN=1
RPAREN=2
COMMA=3
EQUALS=4
NOT_EQUALS=5
NEQ=6
LT=7
LE=8
GT=9
GE=10
PLUS=11
MINUS=12
STAR=13
SLASH=14
PERCENT=15
NOT=16
AND=17
OR=18
BOOL=19
NUMBER=20
QUOTED_TEXT=21
IDENTIFIER=22
WS=23
'('=1
')'=2
','=3
'!='=5
'<>'=6
'<'=7
'<='=8
'>'=9
'>='=10
'+'=11
'-'=12
'*'=13
'/'=14
'%'=15

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,37 @@
LPAREN=1
RPAREN=2
COMMA=3
EQUALS=4
NOT_EQUALS=5
NEQ=6
LT=7
LE=8
GT=9
GE=10
PLUS=11
MINUS=12
STAR=13
SLASH=14
PERCENT=15
NOT=16
AND=17
OR=18
BOOL=19
NUMBER=20
QUOTED_TEXT=21
IDENTIFIER=22
WS=23
'('=1
')'=2
','=3
'!='=5
'<>'=6
'<'=7
'<='=8
'>'=9
'>='=10
'+'=11
'-'=12
'*'=13
'/'=14
'%'=15

View File

@@ -0,0 +1,111 @@
// Code generated from /Users/tvats/SigNoz/signoz/grammar/HavingExpression.g4 by ANTLR 4.13.2. DO NOT EDIT.
package havingexpression // HavingExpression
import "github.com/antlr4-go/antlr/v4"
// BaseHavingExpressionListener is a complete listener for a parse tree produced by HavingExpressionParser.
type BaseHavingExpressionListener struct{}
var _ HavingExpressionListener = &BaseHavingExpressionListener{}
// VisitTerminal is called when a terminal node is visited.
func (s *BaseHavingExpressionListener) VisitTerminal(node antlr.TerminalNode) {}
// VisitErrorNode is called when an error node is visited.
func (s *BaseHavingExpressionListener) VisitErrorNode(node antlr.ErrorNode) {}
// EnterEveryRule is called when any rule is entered.
func (s *BaseHavingExpressionListener) EnterEveryRule(ctx antlr.ParserRuleContext) {}
// ExitEveryRule is called when any rule is exited.
func (s *BaseHavingExpressionListener) ExitEveryRule(ctx antlr.ParserRuleContext) {}
// EnterQuery is called when production query is entered.
func (s *BaseHavingExpressionListener) EnterQuery(ctx *QueryContext) {}
// ExitQuery is called when production query is exited.
func (s *BaseHavingExpressionListener) ExitQuery(ctx *QueryContext) {}
// EnterExpression is called when production expression is entered.
func (s *BaseHavingExpressionListener) EnterExpression(ctx *ExpressionContext) {}
// ExitExpression is called when production expression is exited.
func (s *BaseHavingExpressionListener) ExitExpression(ctx *ExpressionContext) {}
// EnterOrExpression is called when production orExpression is entered.
func (s *BaseHavingExpressionListener) EnterOrExpression(ctx *OrExpressionContext) {}
// ExitOrExpression is called when production orExpression is exited.
func (s *BaseHavingExpressionListener) ExitOrExpression(ctx *OrExpressionContext) {}
// EnterAndExpression is called when production andExpression is entered.
func (s *BaseHavingExpressionListener) EnterAndExpression(ctx *AndExpressionContext) {}
// ExitAndExpression is called when production andExpression is exited.
func (s *BaseHavingExpressionListener) ExitAndExpression(ctx *AndExpressionContext) {}
// EnterPrimary is called when production primary is entered.
func (s *BaseHavingExpressionListener) EnterPrimary(ctx *PrimaryContext) {}
// ExitPrimary is called when production primary is exited.
func (s *BaseHavingExpressionListener) ExitPrimary(ctx *PrimaryContext) {}
// EnterComparison is called when production comparison is entered.
func (s *BaseHavingExpressionListener) EnterComparison(ctx *ComparisonContext) {}
// ExitComparison is called when production comparison is exited.
func (s *BaseHavingExpressionListener) ExitComparison(ctx *ComparisonContext) {}
// EnterCompOp is called when production compOp is entered.
func (s *BaseHavingExpressionListener) EnterCompOp(ctx *CompOpContext) {}
// ExitCompOp is called when production compOp is exited.
func (s *BaseHavingExpressionListener) ExitCompOp(ctx *CompOpContext) {}
// EnterOperand is called when production operand is entered.
func (s *BaseHavingExpressionListener) EnterOperand(ctx *OperandContext) {}
// ExitOperand is called when production operand is exited.
func (s *BaseHavingExpressionListener) ExitOperand(ctx *OperandContext) {}
// EnterTerm is called when production term is entered.
func (s *BaseHavingExpressionListener) EnterTerm(ctx *TermContext) {}
// ExitTerm is called when production term is exited.
func (s *BaseHavingExpressionListener) ExitTerm(ctx *TermContext) {}
// EnterFactor is called when production factor is entered.
func (s *BaseHavingExpressionListener) EnterFactor(ctx *FactorContext) {}
// ExitFactor is called when production factor is exited.
func (s *BaseHavingExpressionListener) ExitFactor(ctx *FactorContext) {}
// EnterAtom is called when production atom is entered.
func (s *BaseHavingExpressionListener) EnterAtom(ctx *AtomContext) {}
// ExitAtom is called when production atom is exited.
func (s *BaseHavingExpressionListener) ExitAtom(ctx *AtomContext) {}
// EnterFunctionCall is called when production functionCall is entered.
func (s *BaseHavingExpressionListener) EnterFunctionCall(ctx *FunctionCallContext) {}
// ExitFunctionCall is called when production functionCall is exited.
func (s *BaseHavingExpressionListener) ExitFunctionCall(ctx *FunctionCallContext) {}
// EnterFunctionArgs is called when production functionArgs is entered.
func (s *BaseHavingExpressionListener) EnterFunctionArgs(ctx *FunctionArgsContext) {}
// ExitFunctionArgs is called when production functionArgs is exited.
func (s *BaseHavingExpressionListener) ExitFunctionArgs(ctx *FunctionArgsContext) {}
// EnterFunctionArg is called when production functionArg is entered.
func (s *BaseHavingExpressionListener) EnterFunctionArg(ctx *FunctionArgContext) {}
// ExitFunctionArg is called when production functionArg is exited.
func (s *BaseHavingExpressionListener) ExitFunctionArg(ctx *FunctionArgContext) {}
// EnterIdentifier is called when production identifier is entered.
func (s *BaseHavingExpressionListener) EnterIdentifier(ctx *IdentifierContext) {}
// ExitIdentifier is called when production identifier is exited.
func (s *BaseHavingExpressionListener) ExitIdentifier(ctx *IdentifierContext) {}

View File

@@ -0,0 +1,68 @@
// Code generated from /Users/tvats/SigNoz/signoz/grammar/HavingExpression.g4 by ANTLR 4.13.2. DO NOT EDIT.
package havingexpression // HavingExpression
import "github.com/antlr4-go/antlr/v4"
type BaseHavingExpressionVisitor struct {
*antlr.BaseParseTreeVisitor
}
func (v *BaseHavingExpressionVisitor) VisitQuery(ctx *QueryContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseHavingExpressionVisitor) VisitExpression(ctx *ExpressionContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseHavingExpressionVisitor) VisitOrExpression(ctx *OrExpressionContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseHavingExpressionVisitor) VisitAndExpression(ctx *AndExpressionContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseHavingExpressionVisitor) VisitPrimary(ctx *PrimaryContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseHavingExpressionVisitor) VisitComparison(ctx *ComparisonContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseHavingExpressionVisitor) VisitCompOp(ctx *CompOpContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseHavingExpressionVisitor) VisitOperand(ctx *OperandContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseHavingExpressionVisitor) VisitTerm(ctx *TermContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseHavingExpressionVisitor) VisitFactor(ctx *FactorContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseHavingExpressionVisitor) VisitAtom(ctx *AtomContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseHavingExpressionVisitor) VisitFunctionCall(ctx *FunctionCallContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseHavingExpressionVisitor) VisitFunctionArgs(ctx *FunctionArgsContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseHavingExpressionVisitor) VisitFunctionArg(ctx *FunctionArgContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseHavingExpressionVisitor) VisitIdentifier(ctx *IdentifierContext) interface{} {
return v.VisitChildren(ctx)
}

View File

@@ -0,0 +1,227 @@
// Code generated from /Users/tvats/SigNoz/signoz/grammar/HavingExpression.g4 by ANTLR 4.13.2. DO NOT EDIT.
package havingexpression
import (
"fmt"
"github.com/antlr4-go/antlr/v4"
"sync"
"unicode"
)
// Suppress unused import error
var _ = fmt.Printf
var _ = sync.Once{}
var _ = unicode.IsLetter
type HavingExpressionLexer struct {
*antlr.BaseLexer
channelNames []string
modeNames []string
// TODO: EOF string
}
var HavingExpressionLexerLexerStaticData struct {
once sync.Once
serializedATN []int32
ChannelNames []string
ModeNames []string
LiteralNames []string
SymbolicNames []string
RuleNames []string
PredictionContextCache *antlr.PredictionContextCache
atn *antlr.ATN
decisionToDFA []*antlr.DFA
}
func havingexpressionlexerLexerInit() {
staticData := &HavingExpressionLexerLexerStaticData
staticData.ChannelNames = []string{
"DEFAULT_TOKEN_CHANNEL", "HIDDEN",
}
staticData.ModeNames = []string{
"DEFAULT_MODE",
}
staticData.LiteralNames = []string{
"", "'('", "')'", "','", "", "'!='", "'<>'", "'<'", "'<='", "'>'", "'>='",
"'+'", "'-'", "'*'", "'/'", "'%'",
}
staticData.SymbolicNames = []string{
"", "LPAREN", "RPAREN", "COMMA", "EQUALS", "NOT_EQUALS", "NEQ", "LT",
"LE", "GT", "GE", "PLUS", "MINUS", "STAR", "SLASH", "PERCENT", "NOT",
"AND", "OR", "BOOL", "NUMBER", "QUOTED_TEXT", "IDENTIFIER", "WS",
}
staticData.RuleNames = []string{
"LPAREN", "RPAREN", "COMMA", "EQUALS", "NOT_EQUALS", "NEQ", "LT", "LE",
"GT", "GE", "PLUS", "MINUS", "STAR", "SLASH", "PERCENT", "NOT", "AND",
"OR", "BOOL", "SIGN", "NUMBER", "QUOTED_TEXT", "IDENTIFIER", "WS", "DIGIT",
}
staticData.PredictionContextCache = antlr.NewPredictionContextCache()
staticData.serializedATN = []int32{
4, 0, 23, 213, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2,
4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2,
10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15,
7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7,
20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 1, 0, 1, 0,
1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 3, 3, 61, 8, 3, 1, 4, 1, 4, 1,
4, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 9, 1,
9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 13, 1, 13, 1, 14,
1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1,
17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18,
3, 18, 109, 8, 18, 1, 19, 1, 19, 1, 20, 3, 20, 114, 8, 20, 1, 20, 4, 20,
117, 8, 20, 11, 20, 12, 20, 118, 1, 20, 1, 20, 5, 20, 123, 8, 20, 10, 20,
12, 20, 126, 9, 20, 3, 20, 128, 8, 20, 1, 20, 1, 20, 3, 20, 132, 8, 20,
1, 20, 4, 20, 135, 8, 20, 11, 20, 12, 20, 136, 3, 20, 139, 8, 20, 1, 20,
3, 20, 142, 8, 20, 1, 20, 1, 20, 4, 20, 146, 8, 20, 11, 20, 12, 20, 147,
1, 20, 1, 20, 3, 20, 152, 8, 20, 1, 20, 4, 20, 155, 8, 20, 11, 20, 12,
20, 156, 3, 20, 159, 8, 20, 3, 20, 161, 8, 20, 1, 21, 1, 21, 1, 21, 1,
21, 5, 21, 167, 8, 21, 10, 21, 12, 21, 170, 9, 21, 1, 21, 1, 21, 1, 21,
1, 21, 1, 21, 5, 21, 177, 8, 21, 10, 21, 12, 21, 180, 9, 21, 1, 21, 3,
21, 183, 8, 21, 1, 22, 1, 22, 5, 22, 187, 8, 22, 10, 22, 12, 22, 190, 9,
22, 1, 22, 1, 22, 1, 22, 5, 22, 195, 8, 22, 10, 22, 12, 22, 198, 9, 22,
5, 22, 200, 8, 22, 10, 22, 12, 22, 203, 9, 22, 1, 23, 4, 23, 206, 8, 23,
11, 23, 12, 23, 207, 1, 23, 1, 23, 1, 24, 1, 24, 0, 0, 25, 1, 1, 3, 2,
5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25,
13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 0, 41, 20, 43,
21, 45, 22, 47, 23, 49, 0, 1, 0, 18, 2, 0, 78, 78, 110, 110, 2, 0, 79,
79, 111, 111, 2, 0, 84, 84, 116, 116, 2, 0, 65, 65, 97, 97, 2, 0, 68, 68,
100, 100, 2, 0, 82, 82, 114, 114, 2, 0, 85, 85, 117, 117, 2, 0, 69, 69,
101, 101, 2, 0, 70, 70, 102, 102, 2, 0, 76, 76, 108, 108, 2, 0, 83, 83,
115, 115, 2, 0, 43, 43, 45, 45, 2, 0, 34, 34, 92, 92, 2, 0, 39, 39, 92,
92, 3, 0, 65, 90, 95, 95, 97, 122, 4, 0, 48, 57, 65, 90, 95, 95, 97, 122,
3, 0, 9, 10, 13, 13, 32, 32, 1, 0, 48, 57, 234, 0, 1, 1, 0, 0, 0, 0, 3,
1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11,
1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0,
19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0,
0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0,
0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0,
0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 1, 51, 1, 0, 0, 0, 3, 53, 1,
0, 0, 0, 5, 55, 1, 0, 0, 0, 7, 60, 1, 0, 0, 0, 9, 62, 1, 0, 0, 0, 11, 65,
1, 0, 0, 0, 13, 68, 1, 0, 0, 0, 15, 70, 1, 0, 0, 0, 17, 73, 1, 0, 0, 0,
19, 75, 1, 0, 0, 0, 21, 78, 1, 0, 0, 0, 23, 80, 1, 0, 0, 0, 25, 82, 1,
0, 0, 0, 27, 84, 1, 0, 0, 0, 29, 86, 1, 0, 0, 0, 31, 88, 1, 0, 0, 0, 33,
92, 1, 0, 0, 0, 35, 96, 1, 0, 0, 0, 37, 108, 1, 0, 0, 0, 39, 110, 1, 0,
0, 0, 41, 160, 1, 0, 0, 0, 43, 182, 1, 0, 0, 0, 45, 184, 1, 0, 0, 0, 47,
205, 1, 0, 0, 0, 49, 211, 1, 0, 0, 0, 51, 52, 5, 40, 0, 0, 52, 2, 1, 0,
0, 0, 53, 54, 5, 41, 0, 0, 54, 4, 1, 0, 0, 0, 55, 56, 5, 44, 0, 0, 56,
6, 1, 0, 0, 0, 57, 61, 5, 61, 0, 0, 58, 59, 5, 61, 0, 0, 59, 61, 5, 61,
0, 0, 60, 57, 1, 0, 0, 0, 60, 58, 1, 0, 0, 0, 61, 8, 1, 0, 0, 0, 62, 63,
5, 33, 0, 0, 63, 64, 5, 61, 0, 0, 64, 10, 1, 0, 0, 0, 65, 66, 5, 60, 0,
0, 66, 67, 5, 62, 0, 0, 67, 12, 1, 0, 0, 0, 68, 69, 5, 60, 0, 0, 69, 14,
1, 0, 0, 0, 70, 71, 5, 60, 0, 0, 71, 72, 5, 61, 0, 0, 72, 16, 1, 0, 0,
0, 73, 74, 5, 62, 0, 0, 74, 18, 1, 0, 0, 0, 75, 76, 5, 62, 0, 0, 76, 77,
5, 61, 0, 0, 77, 20, 1, 0, 0, 0, 78, 79, 5, 43, 0, 0, 79, 22, 1, 0, 0,
0, 80, 81, 5, 45, 0, 0, 81, 24, 1, 0, 0, 0, 82, 83, 5, 42, 0, 0, 83, 26,
1, 0, 0, 0, 84, 85, 5, 47, 0, 0, 85, 28, 1, 0, 0, 0, 86, 87, 5, 37, 0,
0, 87, 30, 1, 0, 0, 0, 88, 89, 7, 0, 0, 0, 89, 90, 7, 1, 0, 0, 90, 91,
7, 2, 0, 0, 91, 32, 1, 0, 0, 0, 92, 93, 7, 3, 0, 0, 93, 94, 7, 0, 0, 0,
94, 95, 7, 4, 0, 0, 95, 34, 1, 0, 0, 0, 96, 97, 7, 1, 0, 0, 97, 98, 7,
5, 0, 0, 98, 36, 1, 0, 0, 0, 99, 100, 7, 2, 0, 0, 100, 101, 7, 5, 0, 0,
101, 102, 7, 6, 0, 0, 102, 109, 7, 7, 0, 0, 103, 104, 7, 8, 0, 0, 104,
105, 7, 3, 0, 0, 105, 106, 7, 9, 0, 0, 106, 107, 7, 10, 0, 0, 107, 109,
7, 7, 0, 0, 108, 99, 1, 0, 0, 0, 108, 103, 1, 0, 0, 0, 109, 38, 1, 0, 0,
0, 110, 111, 7, 11, 0, 0, 111, 40, 1, 0, 0, 0, 112, 114, 3, 39, 19, 0,
113, 112, 1, 0, 0, 0, 113, 114, 1, 0, 0, 0, 114, 116, 1, 0, 0, 0, 115,
117, 3, 49, 24, 0, 116, 115, 1, 0, 0, 0, 117, 118, 1, 0, 0, 0, 118, 116,
1, 0, 0, 0, 118, 119, 1, 0, 0, 0, 119, 127, 1, 0, 0, 0, 120, 124, 5, 46,
0, 0, 121, 123, 3, 49, 24, 0, 122, 121, 1, 0, 0, 0, 123, 126, 1, 0, 0,
0, 124, 122, 1, 0, 0, 0, 124, 125, 1, 0, 0, 0, 125, 128, 1, 0, 0, 0, 126,
124, 1, 0, 0, 0, 127, 120, 1, 0, 0, 0, 127, 128, 1, 0, 0, 0, 128, 138,
1, 0, 0, 0, 129, 131, 7, 7, 0, 0, 130, 132, 3, 39, 19, 0, 131, 130, 1,
0, 0, 0, 131, 132, 1, 0, 0, 0, 132, 134, 1, 0, 0, 0, 133, 135, 3, 49, 24,
0, 134, 133, 1, 0, 0, 0, 135, 136, 1, 0, 0, 0, 136, 134, 1, 0, 0, 0, 136,
137, 1, 0, 0, 0, 137, 139, 1, 0, 0, 0, 138, 129, 1, 0, 0, 0, 138, 139,
1, 0, 0, 0, 139, 161, 1, 0, 0, 0, 140, 142, 3, 39, 19, 0, 141, 140, 1,
0, 0, 0, 141, 142, 1, 0, 0, 0, 142, 143, 1, 0, 0, 0, 143, 145, 5, 46, 0,
0, 144, 146, 3, 49, 24, 0, 145, 144, 1, 0, 0, 0, 146, 147, 1, 0, 0, 0,
147, 145, 1, 0, 0, 0, 147, 148, 1, 0, 0, 0, 148, 158, 1, 0, 0, 0, 149,
151, 7, 7, 0, 0, 150, 152, 3, 39, 19, 0, 151, 150, 1, 0, 0, 0, 151, 152,
1, 0, 0, 0, 152, 154, 1, 0, 0, 0, 153, 155, 3, 49, 24, 0, 154, 153, 1,
0, 0, 0, 155, 156, 1, 0, 0, 0, 156, 154, 1, 0, 0, 0, 156, 157, 1, 0, 0,
0, 157, 159, 1, 0, 0, 0, 158, 149, 1, 0, 0, 0, 158, 159, 1, 0, 0, 0, 159,
161, 1, 0, 0, 0, 160, 113, 1, 0, 0, 0, 160, 141, 1, 0, 0, 0, 161, 42, 1,
0, 0, 0, 162, 168, 5, 34, 0, 0, 163, 167, 8, 12, 0, 0, 164, 165, 5, 92,
0, 0, 165, 167, 9, 0, 0, 0, 166, 163, 1, 0, 0, 0, 166, 164, 1, 0, 0, 0,
167, 170, 1, 0, 0, 0, 168, 166, 1, 0, 0, 0, 168, 169, 1, 0, 0, 0, 169,
171, 1, 0, 0, 0, 170, 168, 1, 0, 0, 0, 171, 183, 5, 34, 0, 0, 172, 178,
5, 39, 0, 0, 173, 177, 8, 13, 0, 0, 174, 175, 5, 92, 0, 0, 175, 177, 9,
0, 0, 0, 176, 173, 1, 0, 0, 0, 176, 174, 1, 0, 0, 0, 177, 180, 1, 0, 0,
0, 178, 176, 1, 0, 0, 0, 178, 179, 1, 0, 0, 0, 179, 181, 1, 0, 0, 0, 180,
178, 1, 0, 0, 0, 181, 183, 5, 39, 0, 0, 182, 162, 1, 0, 0, 0, 182, 172,
1, 0, 0, 0, 183, 44, 1, 0, 0, 0, 184, 188, 7, 14, 0, 0, 185, 187, 7, 15,
0, 0, 186, 185, 1, 0, 0, 0, 187, 190, 1, 0, 0, 0, 188, 186, 1, 0, 0, 0,
188, 189, 1, 0, 0, 0, 189, 201, 1, 0, 0, 0, 190, 188, 1, 0, 0, 0, 191,
192, 5, 46, 0, 0, 192, 196, 7, 14, 0, 0, 193, 195, 7, 15, 0, 0, 194, 193,
1, 0, 0, 0, 195, 198, 1, 0, 0, 0, 196, 194, 1, 0, 0, 0, 196, 197, 1, 0,
0, 0, 197, 200, 1, 0, 0, 0, 198, 196, 1, 0, 0, 0, 199, 191, 1, 0, 0, 0,
200, 203, 1, 0, 0, 0, 201, 199, 1, 0, 0, 0, 201, 202, 1, 0, 0, 0, 202,
46, 1, 0, 0, 0, 203, 201, 1, 0, 0, 0, 204, 206, 7, 16, 0, 0, 205, 204,
1, 0, 0, 0, 206, 207, 1, 0, 0, 0, 207, 205, 1, 0, 0, 0, 207, 208, 1, 0,
0, 0, 208, 209, 1, 0, 0, 0, 209, 210, 6, 23, 0, 0, 210, 48, 1, 0, 0, 0,
211, 212, 7, 17, 0, 0, 212, 50, 1, 0, 0, 0, 25, 0, 60, 108, 113, 118, 124,
127, 131, 136, 138, 141, 147, 151, 156, 158, 160, 166, 168, 176, 178, 182,
188, 196, 201, 207, 1, 6, 0, 0,
}
deserializer := antlr.NewATNDeserializer(nil)
staticData.atn = deserializer.Deserialize(staticData.serializedATN)
atn := staticData.atn
staticData.decisionToDFA = make([]*antlr.DFA, len(atn.DecisionToState))
decisionToDFA := staticData.decisionToDFA
for index, state := range atn.DecisionToState {
decisionToDFA[index] = antlr.NewDFA(state, index)
}
}
// HavingExpressionLexerInit initializes any static state used to implement HavingExpressionLexer. By default the
// static state used to implement the lexer is lazily initialized during the first call to
// NewHavingExpressionLexer(). You can call this function if you wish to initialize the static state ahead
// of time.
func HavingExpressionLexerInit() {
staticData := &HavingExpressionLexerLexerStaticData
staticData.once.Do(havingexpressionlexerLexerInit)
}
// NewHavingExpressionLexer produces a new lexer instance for the optional input antlr.CharStream.
func NewHavingExpressionLexer(input antlr.CharStream) *HavingExpressionLexer {
HavingExpressionLexerInit()
l := new(HavingExpressionLexer)
l.BaseLexer = antlr.NewBaseLexer(input)
staticData := &HavingExpressionLexerLexerStaticData
l.Interpreter = antlr.NewLexerATNSimulator(l, staticData.atn, staticData.decisionToDFA, staticData.PredictionContextCache)
l.channelNames = staticData.ChannelNames
l.modeNames = staticData.ModeNames
l.RuleNames = staticData.RuleNames
l.LiteralNames = staticData.LiteralNames
l.SymbolicNames = staticData.SymbolicNames
l.GrammarFileName = "HavingExpression.g4"
// TODO: l.EOF = antlr.TokenEOF
return l
}
// HavingExpressionLexer tokens.
const (
HavingExpressionLexerLPAREN = 1
HavingExpressionLexerRPAREN = 2
HavingExpressionLexerCOMMA = 3
HavingExpressionLexerEQUALS = 4
HavingExpressionLexerNOT_EQUALS = 5
HavingExpressionLexerNEQ = 6
HavingExpressionLexerLT = 7
HavingExpressionLexerLE = 8
HavingExpressionLexerGT = 9
HavingExpressionLexerGE = 10
HavingExpressionLexerPLUS = 11
HavingExpressionLexerMINUS = 12
HavingExpressionLexerSTAR = 13
HavingExpressionLexerSLASH = 14
HavingExpressionLexerPERCENT = 15
HavingExpressionLexerNOT = 16
HavingExpressionLexerAND = 17
HavingExpressionLexerOR = 18
HavingExpressionLexerBOOL = 19
HavingExpressionLexerNUMBER = 20
HavingExpressionLexerQUOTED_TEXT = 21
HavingExpressionLexerIDENTIFIER = 22
HavingExpressionLexerWS = 23
)

View File

@@ -0,0 +1,99 @@
// Code generated from /Users/tvats/SigNoz/signoz/grammar/HavingExpression.g4 by ANTLR 4.13.2. DO NOT EDIT.
package havingexpression // HavingExpression
import "github.com/antlr4-go/antlr/v4"
// HavingExpressionListener is a complete listener for a parse tree produced by HavingExpressionParser.
type HavingExpressionListener interface {
antlr.ParseTreeListener
// EnterQuery is called when entering the query production.
EnterQuery(c *QueryContext)
// EnterExpression is called when entering the expression production.
EnterExpression(c *ExpressionContext)
// EnterOrExpression is called when entering the orExpression production.
EnterOrExpression(c *OrExpressionContext)
// EnterAndExpression is called when entering the andExpression production.
EnterAndExpression(c *AndExpressionContext)
// EnterPrimary is called when entering the primary production.
EnterPrimary(c *PrimaryContext)
// EnterComparison is called when entering the comparison production.
EnterComparison(c *ComparisonContext)
// EnterCompOp is called when entering the compOp production.
EnterCompOp(c *CompOpContext)
// EnterOperand is called when entering the operand production.
EnterOperand(c *OperandContext)
// EnterTerm is called when entering the term production.
EnterTerm(c *TermContext)
// EnterFactor is called when entering the factor production.
EnterFactor(c *FactorContext)
// EnterAtom is called when entering the atom production.
EnterAtom(c *AtomContext)
// EnterFunctionCall is called when entering the functionCall production.
EnterFunctionCall(c *FunctionCallContext)
// EnterFunctionArgs is called when entering the functionArgs production.
EnterFunctionArgs(c *FunctionArgsContext)
// EnterFunctionArg is called when entering the functionArg production.
EnterFunctionArg(c *FunctionArgContext)
// EnterIdentifier is called when entering the identifier production.
EnterIdentifier(c *IdentifierContext)
// ExitQuery is called when exiting the query production.
ExitQuery(c *QueryContext)
// ExitExpression is called when exiting the expression production.
ExitExpression(c *ExpressionContext)
// ExitOrExpression is called when exiting the orExpression production.
ExitOrExpression(c *OrExpressionContext)
// ExitAndExpression is called when exiting the andExpression production.
ExitAndExpression(c *AndExpressionContext)
// ExitPrimary is called when exiting the primary production.
ExitPrimary(c *PrimaryContext)
// ExitComparison is called when exiting the comparison production.
ExitComparison(c *ComparisonContext)
// ExitCompOp is called when exiting the compOp production.
ExitCompOp(c *CompOpContext)
// ExitOperand is called when exiting the operand production.
ExitOperand(c *OperandContext)
// ExitTerm is called when exiting the term production.
ExitTerm(c *TermContext)
// ExitFactor is called when exiting the factor production.
ExitFactor(c *FactorContext)
// ExitAtom is called when exiting the atom production.
ExitAtom(c *AtomContext)
// ExitFunctionCall is called when exiting the functionCall production.
ExitFunctionCall(c *FunctionCallContext)
// ExitFunctionArgs is called when exiting the functionArgs production.
ExitFunctionArgs(c *FunctionArgsContext)
// ExitFunctionArg is called when exiting the functionArg production.
ExitFunctionArg(c *FunctionArgContext)
// ExitIdentifier is called when exiting the identifier production.
ExitIdentifier(c *IdentifierContext)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,54 @@
// Code generated from /Users/tvats/SigNoz/signoz/grammar/HavingExpression.g4 by ANTLR 4.13.2. DO NOT EDIT.
package havingexpression // HavingExpression
import "github.com/antlr4-go/antlr/v4"
// A complete Visitor for a parse tree produced by HavingExpressionParser.
type HavingExpressionVisitor interface {
antlr.ParseTreeVisitor
// Visit a parse tree produced by HavingExpressionParser#query.
VisitQuery(ctx *QueryContext) interface{}
// Visit a parse tree produced by HavingExpressionParser#expression.
VisitExpression(ctx *ExpressionContext) interface{}
// Visit a parse tree produced by HavingExpressionParser#orExpression.
VisitOrExpression(ctx *OrExpressionContext) interface{}
// Visit a parse tree produced by HavingExpressionParser#andExpression.
VisitAndExpression(ctx *AndExpressionContext) interface{}
// Visit a parse tree produced by HavingExpressionParser#primary.
VisitPrimary(ctx *PrimaryContext) interface{}
// Visit a parse tree produced by HavingExpressionParser#comparison.
VisitComparison(ctx *ComparisonContext) interface{}
// Visit a parse tree produced by HavingExpressionParser#compOp.
VisitCompOp(ctx *CompOpContext) interface{}
// Visit a parse tree produced by HavingExpressionParser#operand.
VisitOperand(ctx *OperandContext) interface{}
// Visit a parse tree produced by HavingExpressionParser#term.
VisitTerm(ctx *TermContext) interface{}
// Visit a parse tree produced by HavingExpressionParser#factor.
VisitFactor(ctx *FactorContext) interface{}
// Visit a parse tree produced by HavingExpressionParser#atom.
VisitAtom(ctx *AtomContext) interface{}
// Visit a parse tree produced by HavingExpressionParser#functionCall.
VisitFunctionCall(ctx *FunctionCallContext) interface{}
// Visit a parse tree produced by HavingExpressionParser#functionArgs.
VisitFunctionArgs(ctx *FunctionArgsContext) interface{}
// Visit a parse tree produced by HavingExpressionParser#functionArg.
VisitFunctionArg(ctx *FunctionArgContext) interface{}
// Visit a parse tree produced by HavingExpressionParser#identifier.
VisitIdentifier(ctx *IdentifierContext) interface{}
}

View File

@@ -0,0 +1,201 @@
package querybuilder
import (
"sort"
"strings"
"github.com/SigNoz/signoz/pkg/errors"
grammar "github.com/SigNoz/signoz/pkg/parser/grammar/havingexpression"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/antlr4-go/antlr/v4"
)
// havingExpressionSemanticValidator walks a rewritten HavingExpression parse tree and
// validates that every remaining identifier or function-call token is a known SQL column name.
//
// By the time this validator runs, rewriteExpression has already replaced all valid
// user-facing references (aliases, expression strings, __result_N, etc.) with their
// corresponding SQL column names (e.g. __result_0, value). Any identifier or function
// call that survived the rewrite was not in the column map and is therefore invalid.
//
// Validation strategy:
// - IdentifierContext nodes are checked against validColumns (the SQL column name set).
// - FunctionCallContext nodes are always invalid after rewriting (valid calls were
// already replaced); their component tokens are reported as unknown.
// - FunctionArg children are not visited individually — they are part of a function
// call unit and are only reported when the whole call is rejected.
type havingExpressionSemanticValidator struct {
validColumns map[string]bool // SQL column names (the TO side of columnMap)
invalid []string // tokens not recognised as valid SQL columns
seen map[string]bool // deduplication
}
func newHavingExpressionSemanticValidator(validColumns map[string]bool) *havingExpressionSemanticValidator {
return &havingExpressionSemanticValidator{
validColumns: validColumns,
seen: make(map[string]bool),
}
}
// Visit dispatches on parse-tree node type. Unrecognised nodes recurse into their children.
func (v *havingExpressionSemanticValidator) Visit(tree antlr.ParseTree) {
switch t := tree.(type) {
case *grammar.FunctionCallContext:
// Validate as a single unit; do NOT recurse further so that function-arg
// IDENTIFIERs are not checked individually.
v.visitFunctionCall(t)
case *grammar.IdentifierContext:
v.visitIdentifier(t)
default:
for i := 0; i < tree.GetChildCount(); i++ {
if child, ok := tree.GetChild(i).(antlr.ParseTree); ok {
v.Visit(child)
}
}
}
}
// visitFunctionCall reports any remaining function call as invalid.
// After rewriting, all valid function-call references (e.g. "sum(bytes)") have
// already been replaced with SQL column names, so any function call seen here
// was not in the column map. Report the function name as invalid; for args,
// only report those that are not valid column references (e.g. sum(__result_0)
// should report only "sum", since __result_0 is a valid column).
func (v *havingExpressionSemanticValidator) visitFunctionCall(ctx *grammar.FunctionCallContext) {
funcName := ctx.IDENTIFIER().GetText()
if !v.seen[funcName] {
v.invalid = append(v.invalid, funcName)
v.seen[funcName] = true
}
if ctx.FunctionArgs() != nil {
for _, arg := range ctx.FunctionArgs().AllFunctionArg() {
argText := arg.IDENTIFIER().GetText()
// Only report args that are not valid column references
if v.validColumns[argText] {
continue
}
if !v.seen[argText] {
v.invalid = append(v.invalid, argText)
v.seen[argText] = true
}
}
}
}
// visitIdentifier checks that a bare identifier is a known SQL column name.
// After rewriting, valid references have been replaced (e.g. alias → __result_0),
// so any identifier not in validColumns was not a recognised reference.
func (v *havingExpressionSemanticValidator) visitIdentifier(ctx *grammar.IdentifierContext) {
name := ctx.IDENTIFIER().GetText()
if v.validColumns[name] {
return
}
if !v.seen[name] {
v.invalid = append(v.invalid, name)
v.seen[name] = true
}
}
// validateWithANTLR parses the HAVING expression with the generated ANTLR lexer/parser
// and then performs semantic validation via havingExpressionSemanticValidator.
//
// It must be called BEFORE rewriteExpression so the expression still contains
// user-facing references (aliases, function-call strings, __result_N, etc.).
//
// Validation layers:
// 1. Quoted string literals are detected early in the token stream (QUOTED_TEXT token
// is intentionally excluded from the grammar's atom rule) → descriptive error.
// 2. ANTLR syntax errors (unbalanced parentheses, missing comparison operators,
// dangling AND/OR, etc.) → syntax error wrapping the ANTLR message.
// 3. Semantic errors (unknown identifiers / function calls) → lists the offending tokens.
func (r *HavingExpressionRewriter) validateWithANTLR(expression string) error {
input := antlr.NewInputStream(expression)
lexer := grammar.NewHavingExpressionLexer(input)
lexerErrListener := NewErrorListener()
lexer.RemoveErrorListeners()
lexer.AddErrorListener(lexerErrListener)
tokens := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel)
p := grammar.NewHavingExpressionParser(tokens)
parserErrListener := NewErrorListener()
p.RemoveErrorListeners()
p.AddErrorListener(parserErrListener)
// Layer 1 Reject quoted string literals before parsing.
// HAVING expressions compare numeric aggregate results; string literals are not valid.
for _, ch := range expression {
if ch == '\'' || ch == '"' {
return errors.NewInvalidInputf(
errors.CodeInvalidInput,
"HAVING expression cannot contain string literals; aggregate results are numeric",
)
}
}
// Build the parse tree.
tree := p.Query()
// Layer 2 ANTLR syntax errors.
allSyntaxErrors := append(lexerErrListener.SyntaxErrors, parserErrListener.SyntaxErrors...)
if len(allSyntaxErrors) > 0 {
msgs := make([]string, 0, len(allSyntaxErrors))
for _, se := range allSyntaxErrors {
if m := se.Error(); m != "" {
msgs = append(msgs, m)
}
}
detail := strings.Join(msgs, "; ")
if detail == "" {
detail = "check the expression syntax"
}
return errors.NewInvalidInputf(
errors.CodeInvalidInput,
"syntax error in HAVING expression: %s",
detail,
)
}
// Layer 3 Semantic validation: every remaining identifier must be a known
// SQL column name. Build validColumns from the TO side of columnMap.
validColumns := make(map[string]bool, len(r.columnMap))
for _, col := range r.columnMap {
validColumns[col] = true
}
sv := newHavingExpressionSemanticValidator(validColumns)
sv.Visit(tree)
if len(sv.invalid) > 0 {
sort.Strings(sv.invalid)
hasAggFunc := false
for _, ref := range sv.invalid {
if _, isAgg := AggreFuncMap[valuer.NewString(ref)]; isAgg {
hasAggFunc = true
break
}
}
if hasAggFunc {
// At least one invalid ref is an aggregation function — use tailored message
return errors.NewInvalidInputf(
errors.CodeInvalidInput,
"aggregation functions are not allowed in HAVING expression",
)
}
validKeys := make([]string, 0, len(r.columnMap))
for k := range r.columnMap {
validKeys = append(validKeys, k)
}
sort.Strings(validKeys)
return errors.NewInvalidInputf(
errors.CodeInvalidInput,
"invalid references in HAVING expression: [%s]. Valid references are: [%s]",
strings.Join(sv.invalid, ", "),
strings.Join(validKeys, ", "),
)
}
return nil
}

View File

@@ -19,19 +19,22 @@ func NewHavingExpressionRewriter() *HavingExpressionRewriter {
}
}
func (r *HavingExpressionRewriter) RewriteForTraces(expression string, aggregations []qbtypes.TraceAggregation) string {
func (r *HavingExpressionRewriter) RewriteForTraces(expression string, aggregations []qbtypes.TraceAggregation) (string, error) {
r.buildTraceColumnMap(aggregations)
return r.rewriteExpression(expression)
expression = r.rewriteExpression(expression)
return expression, r.validateWithANTLR(expression)
}
func (r *HavingExpressionRewriter) RewriteForLogs(expression string, aggregations []qbtypes.LogAggregation) string {
func (r *HavingExpressionRewriter) RewriteForLogs(expression string, aggregations []qbtypes.LogAggregation) (string, error) {
r.buildLogColumnMap(aggregations)
return r.rewriteExpression(expression)
expression = r.rewriteExpression(expression)
return expression, r.validateWithANTLR(expression)
}
func (r *HavingExpressionRewriter) RewriteForMetrics(expression string, aggregations []qbtypes.MetricAggregation) string {
func (r *HavingExpressionRewriter) RewriteForMetrics(expression string, aggregations []qbtypes.MetricAggregation) (string, error) {
r.buildMetricColumnMap(aggregations)
return r.rewriteExpression(expression)
expression = r.rewriteExpression(expression)
return expression, r.validateWithANTLR(expression)
}
func (r *HavingExpressionRewriter) buildTraceColumnMap(aggregations []qbtypes.TraceAggregation) {
@@ -151,3 +154,4 @@ func (r *HavingExpressionRewriter) rewriteExpression(expression string) string {
return expression
}

View File

@@ -0,0 +1,708 @@
package querybuilder
import (
"testing"
"github.com/SigNoz/signoz/pkg/types/metrictypes"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func toTraceAggregations(logs []qbtypes.LogAggregation) []qbtypes.TraceAggregation {
out := make([]qbtypes.TraceAggregation, len(logs))
for i, l := range logs {
out[i] = qbtypes.TraceAggregation{Expression: l.Expression, Alias: l.Alias}
}
return out
}
func TestRewriteForLogsAndTraces(t *testing.T) {
tests := []struct {
name string
expression string
aggregations []qbtypes.LogAggregation
wantExpression string
wantErr bool
wantErrMsg string
}{
// --- Happy path: reference types (alias, expression, __result variants) ---
{
name: "alias reference",
expression: "total_logs > 1000",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total_logs"},
},
wantExpression: "__result_0 > 1000",
},
{
name: "expression reference",
expression: "sum(bytes) > 1024000",
aggregations: []qbtypes.LogAggregation{
{Expression: "sum(bytes)"},
},
wantExpression: "__result_0 > 1024000",
},
{
name: "__result reference for single aggregation",
expression: "__result > 500",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()"},
},
wantExpression: "__result_0 > 500",
},
{
name: "__result0 indexed reference",
expression: "__result0 > 100 AND __result1 < 1000",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()"},
{Expression: "sum(bytes)"},
},
wantExpression: "__result_0 > 100 AND __result_1 < 1000",
},
{
name: "__result_0 underscore indexed reference",
expression: "__result_0 > 100",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()"},
},
wantExpression: "__result_0 > 100",
},
// --- Happy path: boolean logic (AND, OR, NOT, parentheses) ---
{
name: "complex boolean with parentheses",
expression: "(total > 100 AND avg_duration < 500) OR total > 10000",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total"},
{Expression: "avg(duration)", Alias: "avg_duration"},
},
wantExpression: "(__result_0 > 100 AND __result_1 < 500) OR __result_0 > 10000",
},
{
name: "mixed alias and expression reference",
expression: "error_count > 10 AND count() < 1000",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()"},
{Expression: "countIf(level='error')", Alias: "error_count"},
},
wantExpression: "__result_1 > 10 AND __result_0 < 1000",
},
{
name: "NOT on grouped expression",
expression: "NOT (__result_0 > 100 AND __result_1 < 500)",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()"},
{Expression: "sum(bytes)"},
},
wantExpression: "NOT (__result_0 > 100 AND __result_1 < 500)",
},
// --- Happy path: arithmetic operators (+, -, *, /, %) ---
{
name: "arithmetic on aggregations",
expression: "sum_a + sum_b > 1000",
aggregations: []qbtypes.LogAggregation{
{Expression: "sum(a)", Alias: "sum_a"},
{Expression: "sum(b)", Alias: "sum_b"},
},
wantExpression: "__result_0 + __result_1 > 1000",
},
// --- Happy path: comparison operators (=, ==, !=, <>, <, <=, >, >=) ---
{
name: "comparison operators != <> ==",
expression: "total != 0 AND count() <> 0 AND total == 100",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total"},
},
wantExpression: "__result_0 != 0 AND __result_0 <> 0 AND __result_0 == 100",
},
{
name: "comparison operators < <= > >=",
expression: "total < 100 AND total <= 500 AND total > 10 AND total >= 50",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total"},
},
wantExpression: "__result_0 < 100 AND __result_0 <= 500 AND __result_0 > 10 AND __result_0 >= 50",
},
// --- Happy path: numeric literals (negative, float, scientific notation) ---
{
name: "numeric literals",
expression: "total > -10 AND total > 500.5 AND total > 1e6",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total"},
},
wantExpression: "__result_0 > -10 AND __result_0 > 500.5 AND __result_0 > 1e6",
},
{
name: "arithmetic modulo subtraction division multiplication",
expression: "cnt % 10 = 0 AND cnt - 10 > 0 AND errors / total > 0.05 AND cnt * 2 > 100",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "cnt"},
{Expression: "sum(errors)", Alias: "errors"},
{Expression: "count()", Alias: "total"},
},
wantExpression: "__result_0 % 10 = 0 AND __result_0 - 10 > 0 AND __result_1 / __result_2 > 0.05 AND __result_0 * 2 > 100",
},
// --- Happy path: OR with multiple operands ---
{
name: "OR with three operands",
expression: "a > 1 OR b > 2 OR c > 3",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "a"},
{Expression: "count()", Alias: "b"},
{Expression: "count()", Alias: "c"},
},
wantExpression: "__result_0 > 1 OR __result_1 > 2 OR __result_2 > 3",
},
{
name: "NOT with single comparison",
expression: "NOT (total > 100)",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total"},
},
wantExpression: "NOT (__result_0 > 100)",
},
{
name: "nested parentheses with OR and AND",
expression: "(a > 10 OR b > 20) AND c > 5",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "a"},
{Expression: "count()", Alias: "b"},
{Expression: "count()", Alias: "c"},
},
wantExpression: "(__result_0 > 10 OR __result_1 > 20) AND __result_2 > 5",
},
// --- Happy path: comparison between two aggregation references ---
{
name: "comparison between aggregations",
expression: "error_count > warn_count AND errors = warnings",
aggregations: []qbtypes.LogAggregation{
{Expression: "sum(errors)", Alias: "error_count"},
{Expression: "sum(warnings)", Alias: "warn_count"},
{Expression: "sum(errors)", Alias: "errors"},
{Expression: "sum(warnings)", Alias: "warnings"},
},
wantExpression: "__result_0 > __result_1 AND __result_2 = __result_3",
},
// --- Happy path: edge case (reserved keyword used as alias) ---
{
name: "reserved keyword as alias",
expression: "sum > 100",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "sum"},
},
wantExpression: "__result_0 > 100",
},
// --- Error: bare operand / missing comparison operator ---
{
name: "urinary expression alias",
expression: "total_logs",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total_logs"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
{
name: "bare expression (function call)",
expression: "count()",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total_logs"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
{
name: "bare operand without comparison",
expression: "total_logs",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total_logs"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
// --- Error: parentheses mismatch (unclosed, unexpected, empty) ---
{
name: "unclosed parenthesis",
expression: "(total_logs > 100 AND count() < 500",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total_logs"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
{
name: "unexpected closing parenthesis",
expression: "total_logs > 100)",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total_logs"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
{
name: "dangling AND at end",
expression: "total_logs > 100 AND",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total_logs"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
{
name: "dangling OR at start",
expression: "OR total_logs > 100",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total_logs"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
// --- Error: dangling or malformed boolean operators (AND, OR) ---
{
name: "consecutive boolean operators",
expression: "total_logs > 100 AND AND count() < 500",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total_logs"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
// --- Error: invalid operand types (string literals, boolean literal) ---
{
name: "string literal as comparison value",
expression: "sum(bytes) = 'xyz'",
aggregations: []qbtypes.LogAggregation{
{Expression: "sum(bytes)"},
},
wantErr: true,
wantErrMsg: "HAVING expression cannot contain string literals",
},
{
name: "string literal without quotes as comparison value",
expression: "sum(bytes) = xyz",
aggregations: []qbtypes.LogAggregation{
{Expression: "sum(bytes)"},
},
wantErr: true,
wantErrMsg: "invalid references in HAVING expression: [xyz]",
},
{
name: "double-quoted string literal",
expression: `total > "threshold"`,
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total"},
},
wantErr: true,
wantErrMsg: "HAVING expression cannot contain string literals",
},
// --- Error: invalid or unknown references ---
{
name: "unknown identifier",
expression: "unknown_alias > 100",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total"},
},
wantErr: true,
wantErrMsg: "invalid references in HAVING expression: [unknown_alias]",
},
{
name: "expression not in column map",
expression: "sum(missing_field) > 100",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()"},
},
wantErr: true,
wantErrMsg: "aggregation functions are not allowed in HAVING expression",
},
{
name: "one valid one invalid reference",
expression: "total > 100 AND ghost > 50",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total"},
},
wantErr: true,
wantErrMsg: "invalid references in HAVING expression: [ghost]",
},
{
name: "__result ambiguous with multiple aggregations",
expression: "__result > 100",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()"},
{Expression: "sum(bytes)"},
},
wantErr: true,
wantErrMsg: "invalid references in HAVING expression: [__result]",
},
// --- Error: NOT syntax (must wrap comparison in parentheses) ---
{
name: "NOT without parentheses",
expression: "NOT count() > 100",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
{
name: "empty expression",
expression: "",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
// --- Error: malformed comparison (missing operand, operator, or connector) ---
{
name: "two comparisons without boolean connector",
expression: "total > 100 count() < 500",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
{
name: "out-of-range __result_N index",
expression: "__result_9 > 100",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()"},
},
wantErr: true,
wantErrMsg: "invalid references in HAVING expression: [__result_9]",
},
// --- Error: boolean literal as comparison value ---
{
name: "boolean literal as comparison value",
expression: "count() > true",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
{
name: "double NOT without valid grouping",
expression: "NOT NOT (count() > 100)",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
// --- Error: invalid function calls (cascaded, wrong args) ---
{
name: "cascaded function calls",
expression: "sum(count()) > 100",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()"},
},
wantErr: true,
wantErrMsg: "aggregation functions are not allowed in HAVING expression",
},
// --- Error: empty or whitespace-only expression ---
{
name: "whitespace only expression",
expression: " ",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
// --- Error: standalone parentheses ---
{
name: "only opening parenthesis",
expression: "(",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
{
name: "only closing parenthesis",
expression: ")",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
{
name: "empty parentheses",
expression: "()",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
// --- Error: missing comparison operands or operator ---
{
name: "missing left operand",
expression: "> 100",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
{
name: "missing right operand",
expression: "count() >",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
{
name: "missing operator",
expression: "count() 100",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
{
name: "dangling OR at end",
expression: "total > 100 OR",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
{
name: "AND OR without second operand",
expression: "total > 100 AND OR count() < 50",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
{
name: "NOT without parentheses on alias",
expression: "NOT total > 100",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total"},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
// --- Error: invalid function call (not in column map) ---
{
name: "function call with multiple args not in column map",
expression: "sum(a, b) > 100",
aggregations: []qbtypes.LogAggregation{
{Expression: "sum(a)"},
},
wantErr: true,
wantErrMsg: "aggregation functions are not allowed in HAVING expression",
},
// --- Error: out-of-range __result index ---
{
name: "__result_1 out of range for single aggregation",
expression: "__result_1 > 100",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()"},
},
wantErr: true,
wantErrMsg: "invalid references in HAVING expression: [__result_1]",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := NewHavingExpressionRewriter()
traceAggs := toTraceAggregations(tt.aggregations)
gotLogs, errLogs := r.RewriteForLogs(tt.expression, tt.aggregations)
r2 := NewHavingExpressionRewriter()
gotTraces, errTraces := r2.RewriteForTraces(tt.expression, traceAggs)
if tt.wantErr {
require.Error(t, errLogs)
assert.ErrorContains(t, errLogs, tt.wantErrMsg)
require.Error(t, errTraces)
assert.ErrorContains(t, errTraces, tt.wantErrMsg)
} else {
require.NoError(t, errLogs)
assert.Equal(t, tt.wantExpression, gotLogs)
require.NoError(t, errTraces)
assert.Equal(t, tt.wantExpression, gotTraces)
}
})
}
}
func TestRewriteForMetrics(t *testing.T) {
tests := []struct {
name string
expression string
aggregations []qbtypes.MetricAggregation
wantExpression string
wantErr bool
wantErrMsg string
}{
// --- Happy path: reference types (time/space aggregation, __result, bare metric) ---
{
name: "time aggregation reference",
expression: "sum(cpu_usage) > 80",
aggregations: []qbtypes.MetricAggregation{
{
MetricName: "cpu_usage",
TimeAggregation: metrictypes.TimeAggregationSum,
SpaceAggregation: metrictypes.SpaceAggregationUnspecified,
},
},
wantExpression: "value > 80",
},
{
name: "space aggregation reference",
expression: "avg(cpu_usage) > 50",
aggregations: []qbtypes.MetricAggregation{
{
MetricName: "cpu_usage",
SpaceAggregation: metrictypes.SpaceAggregationAvg,
TimeAggregation: metrictypes.TimeAggregationUnspecified,
},
},
wantExpression: "value > 50",
},
{
name: "__result reference",
expression: "__result > 90",
aggregations: []qbtypes.MetricAggregation{
{
MetricName: "cpu_usage",
TimeAggregation: metrictypes.TimeAggregationSum,
SpaceAggregation: metrictypes.SpaceAggregationUnspecified,
},
},
wantExpression: "value > 90",
},
{
name: "bare metric name when no aggregations set",
expression: "cpu_usage > 80",
aggregations: []qbtypes.MetricAggregation{
{
MetricName: "cpu_usage",
TimeAggregation: metrictypes.TimeAggregationUnspecified,
SpaceAggregation: metrictypes.SpaceAggregationUnspecified,
},
},
wantExpression: "value > 80",
},
{
name: "combined space and time aggregation",
expression: "avg(sum(cpu_usage)) > 50",
aggregations: []qbtypes.MetricAggregation{
{
MetricName: "cpu_usage",
TimeAggregation: metrictypes.TimeAggregationSum,
SpaceAggregation: metrictypes.SpaceAggregationAvg,
},
},
wantExpression: "value > 50",
},
// --- Happy path: comparison operators and arithmetic ---
{
name: "comparison operators and arithmetic",
expression: "sum(cpu_usage) < 100 AND sum(cpu_usage) * 2 > 50",
aggregations: []qbtypes.MetricAggregation{
{
MetricName: "cpu_usage",
TimeAggregation: metrictypes.TimeAggregationSum,
SpaceAggregation: metrictypes.SpaceAggregationUnspecified,
},
},
wantExpression: "value < 100 AND value * 2 > 50",
},
// --- Error: invalid or unknown metric reference ---
{
name: "unknown metric reference",
expression: "wrong_metric > 80",
aggregations: []qbtypes.MetricAggregation{
{
MetricName: "cpu_usage",
TimeAggregation: metrictypes.TimeAggregationSum,
SpaceAggregation: metrictypes.SpaceAggregationUnspecified,
},
},
wantErr: true,
wantErrMsg: "invalid references in HAVING expression: [wrong_metric]",
},
// --- Error: empty or bare operand ---
{
name: "empty expression",
expression: "",
aggregations: []qbtypes.MetricAggregation{
{
MetricName: "cpu_usage",
TimeAggregation: metrictypes.TimeAggregationSum,
SpaceAggregation: metrictypes.SpaceAggregationUnspecified,
},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
// --- Error: string literal (not allowed in HAVING) ---
{
name: "string literal rejected",
expression: "cpu_usage = 'high'",
aggregations: []qbtypes.MetricAggregation{
{
MetricName: "cpu_usage",
TimeAggregation: metrictypes.TimeAggregationSum,
SpaceAggregation: metrictypes.SpaceAggregationUnspecified,
},
},
wantErr: true,
wantErrMsg: "HAVING expression cannot contain string literals",
},
// --- Error: bare operand (no comparison) ---
{
name: "bare operand without comparison",
expression: "cpu_usage",
aggregations: []qbtypes.MetricAggregation{
{
MetricName: "cpu_usage",
TimeAggregation: metrictypes.TimeAggregationSum,
SpaceAggregation: metrictypes.SpaceAggregationUnspecified,
},
},
wantErr: true,
wantErrMsg: "syntax error in HAVING expression",
},
// --- Error: aggregation not in column map ---
{
name: "aggregation not in column map",
expression: "count(cpu_usage) > 10",
aggregations: []qbtypes.MetricAggregation{
{
MetricName: "cpu_usage",
TimeAggregation: metrictypes.TimeAggregationSum,
SpaceAggregation: metrictypes.SpaceAggregationUnspecified,
},
},
wantErr: true,
wantErrMsg: "invalid references in HAVING expression",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := NewHavingExpressionRewriter()
got, err := r.RewriteForMetrics(tt.expression, tt.aggregations)
if tt.wantErr {
require.Error(t, err)
assert.ErrorContains(t, err, tt.wantErrMsg)
} else {
require.NoError(t, err)
assert.Equal(t, tt.wantExpression, got)
}
})
}
}

View File

@@ -6,7 +6,7 @@ import (
"strings"
"github.com/SigNoz/signoz/pkg/errors"
grammar "github.com/SigNoz/signoz/pkg/parser/grammar"
grammar "github.com/SigNoz/signoz/pkg/parser/grammar/filterquery"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/antlr4-go/antlr/v4"
)

View File

@@ -1,7 +1,7 @@
package querybuilder
import (
grammar "github.com/SigNoz/signoz/pkg/parser/grammar"
grammar "github.com/SigNoz/signoz/pkg/parser/grammar/filterquery"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/antlr4-go/antlr/v4"
)

View File

@@ -8,7 +8,7 @@ import (
"strings"
"github.com/SigNoz/signoz/pkg/errors"
grammar "github.com/SigNoz/signoz/pkg/parser/grammar"
grammar "github.com/SigNoz/signoz/pkg/parser/grammar/filterquery"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/antlr4-go/antlr/v4"

View File

@@ -5,7 +5,7 @@ import (
"strings"
"testing"
grammar "github.com/SigNoz/signoz/pkg/parser/grammar"
grammar "github.com/SigNoz/signoz/pkg/parser/grammar/filterquery"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/antlr4-go/antlr/v4"

View File

@@ -412,7 +412,10 @@ func (b *logQueryStatementBuilder) buildTimeSeriesQuery(
if query.Having != nil && query.Having.Expression != "" {
// Rewrite having expression to use SQL column names
rewriter := querybuilder.NewHavingExpressionRewriter()
rewrittenExpr := rewriter.RewriteForLogs(query.Having.Expression, query.Aggregations)
rewrittenExpr, err := rewriter.RewriteForLogs(query.Having.Expression, query.Aggregations)
if err != nil {
return nil, err
}
sb.Having(rewrittenExpr)
}
@@ -438,7 +441,10 @@ func (b *logQueryStatementBuilder) buildTimeSeriesQuery(
sb.GroupBy(querybuilder.GroupByKeys(query.GroupBy)...)
if query.Having != nil && query.Having.Expression != "" {
rewriter := querybuilder.NewHavingExpressionRewriter()
rewrittenExpr := rewriter.RewriteForLogs(query.Having.Expression, query.Aggregations)
rewrittenExpr, err := rewriter.RewriteForLogs(query.Having.Expression, query.Aggregations)
if err != nil {
return nil, err
}
sb.Having(rewrittenExpr)
}
@@ -545,7 +551,10 @@ func (b *logQueryStatementBuilder) buildScalarQuery(
// Add having clause if needed
if query.Having != nil && query.Having.Expression != "" {
rewriter := querybuilder.NewHavingExpressionRewriter()
rewrittenExpr := rewriter.RewriteForLogs(query.Having.Expression, query.Aggregations)
rewrittenExpr, err := rewriter.RewriteForLogs(query.Having.Expression, query.Aggregations)
if err != nil {
return nil, err
}
sb.Having(rewrittenExpr)
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/SigNoz/signoz/pkg/querybuilder"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestHavingExpressionRewriter_LogQueries(t *testing.T) {
@@ -14,6 +15,7 @@ func TestHavingExpressionRewriter_LogQueries(t *testing.T) {
havingExpression string
aggregations []qbtypes.LogAggregation
expectedExpression string
wantErr bool
}{
{
name: "single aggregation with alias",
@@ -66,13 +68,34 @@ func TestHavingExpressionRewriter_LogQueries(t *testing.T) {
},
expectedExpression: "__result_1 > 10 AND __result_0 < 1000",
},
{
name: "string literal in having expression",
havingExpression: "count() > 'threshold'",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: ""},
},
wantErr: true,
},
{
name: "unknown reference",
havingExpression: "no_such_alias > 100",
aggregations: []qbtypes.LogAggregation{
{Expression: "count()", Alias: "total_logs"},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rewriter := querybuilder.NewHavingExpressionRewriter()
result := rewriter.RewriteForLogs(tt.havingExpression, tt.aggregations)
assert.Equal(t, tt.expectedExpression, result)
result, err := rewriter.RewriteForLogs(tt.havingExpression, tt.aggregations)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, tt.expectedExpression, result)
}
})
}
}

View File

@@ -647,7 +647,10 @@ func (b *MetricQueryStatementBuilder) BuildFinalSelect(
sb.GroupBy("ts")
if query.Having != nil && query.Having.Expression != "" {
rewriter := querybuilder.NewHavingExpressionRewriter()
rewrittenExpr := rewriter.RewriteForMetrics(query.Having.Expression, query.Aggregations)
rewrittenExpr, err := rewriter.RewriteForMetrics(query.Having.Expression, query.Aggregations)
if err != nil {
return nil, err
}
sb.Having(rewrittenExpr)
}
} else {
@@ -655,7 +658,10 @@ func (b *MetricQueryStatementBuilder) BuildFinalSelect(
sb.From("__spatial_aggregation_cte")
if query.Having != nil && query.Having.Expression != "" {
rewriter := querybuilder.NewHavingExpressionRewriter()
rewrittenExpr := rewriter.RewriteForMetrics(query.Having.Expression, query.Aggregations)
rewrittenExpr, err := rewriter.RewriteForMetrics(query.Having.Expression, query.Aggregations)
if err != nil {
return nil, err
}
sb.Where(rewrittenExpr)
}
}

View File

@@ -569,7 +569,10 @@ func (b *traceQueryStatementBuilder) buildTimeSeriesQuery(
sb.GroupBy(querybuilder.GroupByKeys(query.GroupBy)...)
if query.Having != nil && query.Having.Expression != "" {
rewriter := querybuilder.NewHavingExpressionRewriter()
rewrittenExpr := rewriter.RewriteForTraces(query.Having.Expression, query.Aggregations)
rewrittenExpr, err := rewriter.RewriteForTraces(query.Having.Expression, query.Aggregations)
if err != nil {
return nil, err
}
sb.Having(rewrittenExpr)
}
@@ -595,7 +598,10 @@ func (b *traceQueryStatementBuilder) buildTimeSeriesQuery(
sb.GroupBy(querybuilder.GroupByKeys(query.GroupBy)...)
if query.Having != nil && query.Having.Expression != "" {
rewriter := querybuilder.NewHavingExpressionRewriter()
rewrittenExpr := rewriter.RewriteForTraces(query.Having.Expression, query.Aggregations)
rewrittenExpr, err := rewriter.RewriteForTraces(query.Having.Expression, query.Aggregations)
if err != nil {
return nil, err
}
sb.Having(rewrittenExpr)
}
@@ -701,7 +707,10 @@ func (b *traceQueryStatementBuilder) buildScalarQuery(
// Add having clause if needed
if query.Having != nil && query.Having.Expression != "" && !skipHaving {
rewriter := querybuilder.NewHavingExpressionRewriter()
rewrittenExpr := rewriter.RewriteForTraces(query.Having.Expression, query.Aggregations)
rewrittenExpr, err := rewriter.RewriteForTraces(query.Having.Expression, query.Aggregations)
if err != nil {
return nil, err
}
sb.Having(rewrittenExpr)
}

View File

@@ -3,7 +3,7 @@ package telemetrytraces
import (
"strings"
grammar "github.com/SigNoz/signoz/pkg/parser/grammar"
grammar "github.com/SigNoz/signoz/pkg/parser/grammar/filterquery"
"github.com/antlr4-go/antlr/v4"
)

View File

@@ -615,7 +615,10 @@ func (b *traceOperatorCTEBuilder) buildTimeSeriesQuery(ctx context.Context, sele
combinedArgs := append(allGroupByArgs, allAggChArgs...)
// Add HAVING clause if specified
b.addHavingClause(sb)
err = b.addHavingClause(sb)
if err != nil {
return nil, err
}
sql, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse, combinedArgs...)
return &qbtypes.Statement{
@@ -727,7 +730,10 @@ func (b *traceOperatorCTEBuilder) buildTraceQuery(ctx context.Context, selectFro
sb.GroupBy(groupByKeys...)
}
b.addHavingClause(sb)
err = b.addHavingClause(sb)
if err != nil {
return nil, err
}
orderApplied := false
for _, orderBy := range b.operator.Order {
@@ -866,7 +872,10 @@ func (b *traceOperatorCTEBuilder) buildScalarQuery(ctx context.Context, selectFr
combinedArgs := append(allGroupByArgs, allAggChArgs...)
// Add HAVING clause if specified
b.addHavingClause(sb)
err = b.addHavingClause(sb)
if err != nil {
return nil, err
}
sql, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse, combinedArgs...)
return &qbtypes.Statement{
@@ -875,12 +884,16 @@ func (b *traceOperatorCTEBuilder) buildScalarQuery(ctx context.Context, selectFr
}, nil
}
func (b *traceOperatorCTEBuilder) addHavingClause(sb *sqlbuilder.SelectBuilder) {
func (b *traceOperatorCTEBuilder) addHavingClause(sb *sqlbuilder.SelectBuilder) error {
if b.operator.Having != nil && b.operator.Having.Expression != "" {
rewriter := querybuilder.NewHavingExpressionRewriter()
rewrittenExpr := rewriter.RewriteForTraces(b.operator.Having.Expression, b.operator.Aggregations)
rewrittenExpr, err := rewriter.RewriteForTraces(b.operator.Having.Expression, b.operator.Aggregations)
if err != nil {
return err
}
sb.Having(rewrittenExpr)
}
return nil
}
func (b *traceOperatorCTEBuilder) addCTE(name, sql string, args []any, dependsOn []string) {

View File

@@ -209,6 +209,12 @@ func (q *QueryBuilderQuery[T]) validateAggregations() error {
}
aliases[v.Alias] = true
}
if strings.Contains(strings.ToLower(v.Expression), " as ") {
return errors.NewInvalidInputf(
errors.CodeInvalidInput,
"aliasing is not allowed in expression. Use `alias` field instead",
)
}
case LogAggregation:
if v.Expression == "" {
aggId := fmt.Sprintf("aggregation #%d", i+1)
@@ -231,6 +237,12 @@ func (q *QueryBuilderQuery[T]) validateAggregations() error {
}
aliases[v.Alias] = true
}
if strings.Contains(strings.ToLower(v.Expression), " as ") {
return errors.NewInvalidInputf(
errors.CodeInvalidInput,
"aliasing is not allowed in expression. Use `alias` field instead",
)
}
}
}

View File

@@ -6,7 +6,7 @@ import (
"strings"
"github.com/SigNoz/signoz/pkg/errors"
grammar "github.com/SigNoz/signoz/pkg/parser/grammar"
grammar "github.com/SigNoz/signoz/pkg/parser/grammar/filterquery"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/antlr4-go/antlr/v4"
)