mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-10 04:49:02 +08:00
chore: add where clause visitor implementation for query expression (#7564)
This commit is contained in:
parent
054dea366e
commit
b0d19035a4
@ -104,7 +104,7 @@ fullText
|
||||
* ...
|
||||
*/
|
||||
functionCall
|
||||
: (HAS | HASANY | HASALL | HASNONE) LPAREN functionParamList RPAREN
|
||||
: (HAS | HASANY | HASALL) LPAREN functionParamList RPAREN
|
||||
;
|
||||
|
||||
// Function parameters can be keys, single scalar values, or arrays
|
||||
@ -182,7 +182,6 @@ OR : [Oo][Rr] ;
|
||||
HAS : [Hh][Aa][Ss] ;
|
||||
HASANY : [Hh][Aa][Ss][Aa][Nn][Yy] ;
|
||||
HASALL : [Hh][Aa][Ss][Aa][Ll][Ll] ;
|
||||
HASNONE : [Hh][Aa][Ss][Nn][Oo][Nn][Ee] ;
|
||||
|
||||
// Potential boolean constants
|
||||
BOOL
|
||||
@ -205,7 +204,7 @@ QUOTED_TEXT
|
||||
// Keys can have letters, digits, underscores, dots, and bracket pairs
|
||||
// e.g. service.name, service.namespace, db.queries[].query_duration
|
||||
KEY
|
||||
: [a-zA-Z0-9_] [a-zA-Z0-9_.[\]]*
|
||||
: [a-zA-Z0-9_] [a-zA-Z0-9_.*[\]]*
|
||||
;
|
||||
|
||||
// Ignore whitespace
|
||||
@ -218,4 +217,4 @@ fragment DIGIT
|
||||
: [0-9]
|
||||
;
|
||||
|
||||
FREETEXT : (~[ \t\r\n=()'"<>![\]])+ ;
|
||||
FREETEXT : (~[ \t\r\n=()'"<>!,[\]])+ ;
|
||||
|
File diff suppressed because one or more lines are too long
@ -25,13 +25,12 @@ OR=24
|
||||
HAS=25
|
||||
HASANY=26
|
||||
HASALL=27
|
||||
HASNONE=28
|
||||
BOOL=29
|
||||
NUMBER=30
|
||||
QUOTED_TEXT=31
|
||||
KEY=32
|
||||
WS=33
|
||||
FREETEXT=34
|
||||
BOOL=28
|
||||
NUMBER=29
|
||||
QUOTED_TEXT=30
|
||||
KEY=31
|
||||
WS=32
|
||||
FREETEXT=33
|
||||
'('=1
|
||||
')'=2
|
||||
'['=3
|
||||
|
File diff suppressed because one or more lines are too long
@ -25,13 +25,12 @@ OR=24
|
||||
HAS=25
|
||||
HASANY=26
|
||||
HASALL=27
|
||||
HASNONE=28
|
||||
BOOL=29
|
||||
NUMBER=30
|
||||
QUOTED_TEXT=31
|
||||
KEY=32
|
||||
WS=33
|
||||
FREETEXT=34
|
||||
BOOL=28
|
||||
NUMBER=29
|
||||
QUOTED_TEXT=30
|
||||
KEY=31
|
||||
WS=32
|
||||
FREETEXT=33
|
||||
'('=1
|
||||
')'=2
|
||||
'['=3
|
||||
|
@ -50,150 +50,146 @@ func filterquerylexerLexerInit() {
|
||||
"", "LPAREN", "RPAREN", "LBRACK", "RBRACK", "COMMA", "EQUALS", "NOT_EQUALS",
|
||||
"NEQ", "LT", "LE", "GT", "GE", "LIKE", "NOT_LIKE", "ILIKE", "NOT_ILIKE",
|
||||
"BETWEEN", "EXISTS", "REGEXP", "CONTAINS", "IN", "NOT", "AND", "OR",
|
||||
"HAS", "HASANY", "HASALL", "HASNONE", "BOOL", "NUMBER", "QUOTED_TEXT",
|
||||
"KEY", "WS", "FREETEXT",
|
||||
"HAS", "HASANY", "HASALL", "BOOL", "NUMBER", "QUOTED_TEXT", "KEY", "WS",
|
||||
"FREETEXT",
|
||||
}
|
||||
staticData.RuleNames = []string{
|
||||
"LPAREN", "RPAREN", "LBRACK", "RBRACK", "COMMA", "EQUALS", "NOT_EQUALS",
|
||||
"NEQ", "LT", "LE", "GT", "GE", "LIKE", "NOT_LIKE", "ILIKE", "NOT_ILIKE",
|
||||
"BETWEEN", "EXISTS", "REGEXP", "CONTAINS", "IN", "NOT", "AND", "OR",
|
||||
"HAS", "HASANY", "HASALL", "HASNONE", "BOOL", "NUMBER", "QUOTED_TEXT",
|
||||
"KEY", "WS", "DIGIT", "FREETEXT",
|
||||
"HAS", "HASANY", "HASALL", "BOOL", "NUMBER", "QUOTED_TEXT", "KEY", "WS",
|
||||
"DIGIT", "FREETEXT",
|
||||
}
|
||||
staticData.PredictionContextCache = antlr.NewPredictionContextCache()
|
||||
staticData.serializedATN = []int32{
|
||||
4, 0, 34, 280, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2,
|
||||
4, 0, 33, 270, 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, 2, 25, 7, 25,
|
||||
2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2,
|
||||
31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 1, 0, 1, 0, 1, 1,
|
||||
1, 1, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 3, 5, 85, 8,
|
||||
5, 1, 6, 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, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13,
|
||||
1, 13, 1, 13, 1, 13, 4, 13, 112, 8, 13, 11, 13, 12, 13, 113, 1, 13, 1,
|
||||
13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15,
|
||||
1, 15, 1, 15, 1, 15, 4, 15, 131, 8, 15, 11, 15, 12, 15, 132, 1, 15, 1,
|
||||
15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16,
|
||||
1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 155, 8,
|
||||
17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19,
|
||||
1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 3, 19, 172, 8, 19, 1, 20, 1, 20, 1,
|
||||
20, 1, 21, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23,
|
||||
1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1,
|
||||
25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27,
|
||||
1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1,
|
||||
28, 1, 28, 1, 28, 1, 28, 1, 28, 3, 28, 223, 8, 28, 1, 29, 4, 29, 226, 8,
|
||||
29, 11, 29, 12, 29, 227, 1, 29, 1, 29, 4, 29, 232, 8, 29, 11, 29, 12, 29,
|
||||
233, 3, 29, 236, 8, 29, 1, 30, 1, 30, 1, 30, 1, 30, 5, 30, 242, 8, 30,
|
||||
10, 30, 12, 30, 245, 9, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 5, 30, 252,
|
||||
8, 30, 10, 30, 12, 30, 255, 9, 30, 1, 30, 3, 30, 258, 8, 30, 1, 31, 1,
|
||||
31, 5, 31, 262, 8, 31, 10, 31, 12, 31, 265, 9, 31, 1, 32, 4, 32, 268, 8,
|
||||
32, 11, 32, 12, 32, 269, 1, 32, 1, 32, 1, 33, 1, 33, 1, 34, 4, 34, 277,
|
||||
8, 34, 11, 34, 12, 34, 278, 0, 0, 35, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11,
|
||||
31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1,
|
||||
2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 3, 5, 83, 8, 5, 1, 6, 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, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13,
|
||||
1, 13, 4, 13, 110, 8, 13, 11, 13, 12, 13, 111, 1, 13, 1, 13, 1, 13, 1,
|
||||
13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15,
|
||||
1, 15, 4, 15, 129, 8, 15, 11, 15, 12, 15, 130, 1, 15, 1, 15, 1, 15, 1,
|
||||
15, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16,
|
||||
1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 153, 8, 17, 1, 18, 1,
|
||||
18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19,
|
||||
1, 19, 1, 19, 1, 19, 3, 19, 170, 8, 19, 1, 20, 1, 20, 1, 20, 1, 21, 1,
|
||||
21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 1, 23, 1, 24,
|
||||
1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1,
|
||||
26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27,
|
||||
1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 3, 27, 213, 8, 27, 1, 28, 4, 28, 216,
|
||||
8, 28, 11, 28, 12, 28, 217, 1, 28, 1, 28, 4, 28, 222, 8, 28, 11, 28, 12,
|
||||
28, 223, 3, 28, 226, 8, 28, 1, 29, 1, 29, 1, 29, 1, 29, 5, 29, 232, 8,
|
||||
29, 10, 29, 12, 29, 235, 9, 29, 1, 29, 1, 29, 1, 29, 1, 29, 1, 29, 5, 29,
|
||||
242, 8, 29, 10, 29, 12, 29, 245, 9, 29, 1, 29, 3, 29, 248, 8, 29, 1, 30,
|
||||
1, 30, 5, 30, 252, 8, 30, 10, 30, 12, 30, 255, 9, 30, 1, 31, 4, 31, 258,
|
||||
8, 31, 11, 31, 12, 31, 259, 1, 31, 1, 31, 1, 32, 1, 32, 1, 33, 4, 33, 267,
|
||||
8, 33, 11, 33, 12, 33, 268, 0, 0, 34, 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, 20, 41, 21, 43, 22, 45, 23, 47, 24,
|
||||
49, 25, 51, 26, 53, 27, 55, 28, 57, 29, 59, 30, 61, 31, 63, 32, 65, 33,
|
||||
67, 0, 69, 34, 1, 0, 29, 2, 0, 76, 76, 108, 108, 2, 0, 73, 73, 105, 105,
|
||||
2, 0, 75, 75, 107, 107, 2, 0, 69, 69, 101, 101, 2, 0, 78, 78, 110, 110,
|
||||
2, 0, 79, 79, 111, 111, 2, 0, 84, 84, 116, 116, 2, 0, 9, 9, 32, 32, 2,
|
||||
0, 66, 66, 98, 98, 2, 0, 87, 87, 119, 119, 2, 0, 88, 88, 120, 120, 2, 0,
|
||||
83, 83, 115, 115, 2, 0, 82, 82, 114, 114, 2, 0, 71, 71, 103, 103, 2, 0,
|
||||
80, 80, 112, 112, 2, 0, 67, 67, 99, 99, 2, 0, 65, 65, 97, 97, 2, 0, 68,
|
||||
68, 100, 100, 2, 0, 72, 72, 104, 104, 2, 0, 89, 89, 121, 121, 2, 0, 85,
|
||||
85, 117, 117, 2, 0, 70, 70, 102, 102, 2, 0, 34, 34, 92, 92, 2, 0, 39, 39,
|
||||
92, 92, 4, 0, 48, 57, 65, 90, 95, 95, 97, 122, 6, 0, 46, 46, 48, 57, 65,
|
||||
49, 25, 51, 26, 53, 27, 55, 28, 57, 29, 59, 30, 61, 31, 63, 32, 65, 0,
|
||||
67, 33, 1, 0, 29, 2, 0, 76, 76, 108, 108, 2, 0, 73, 73, 105, 105, 2, 0,
|
||||
75, 75, 107, 107, 2, 0, 69, 69, 101, 101, 2, 0, 78, 78, 110, 110, 2, 0,
|
||||
79, 79, 111, 111, 2, 0, 84, 84, 116, 116, 2, 0, 9, 9, 32, 32, 2, 0, 66,
|
||||
66, 98, 98, 2, 0, 87, 87, 119, 119, 2, 0, 88, 88, 120, 120, 2, 0, 83, 83,
|
||||
115, 115, 2, 0, 82, 82, 114, 114, 2, 0, 71, 71, 103, 103, 2, 0, 80, 80,
|
||||
112, 112, 2, 0, 67, 67, 99, 99, 2, 0, 65, 65, 97, 97, 2, 0, 68, 68, 100,
|
||||
100, 2, 0, 72, 72, 104, 104, 2, 0, 89, 89, 121, 121, 2, 0, 85, 85, 117,
|
||||
117, 2, 0, 70, 70, 102, 102, 2, 0, 34, 34, 92, 92, 2, 0, 39, 39, 92, 92,
|
||||
4, 0, 48, 57, 65, 90, 95, 95, 97, 122, 7, 0, 42, 42, 46, 46, 48, 57, 65,
|
||||
91, 93, 93, 95, 95, 97, 122, 3, 0, 9, 10, 13, 13, 32, 32, 1, 0, 48, 57,
|
||||
7, 0, 9, 10, 13, 13, 32, 34, 39, 41, 60, 62, 91, 91, 93, 93, 295, 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, 39, 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, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55,
|
||||
1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 61, 1, 0, 0, 0, 0,
|
||||
63, 1, 0, 0, 0, 0, 65, 1, 0, 0, 0, 0, 69, 1, 0, 0, 0, 1, 71, 1, 0, 0, 0,
|
||||
3, 73, 1, 0, 0, 0, 5, 75, 1, 0, 0, 0, 7, 77, 1, 0, 0, 0, 9, 79, 1, 0, 0,
|
||||
0, 11, 84, 1, 0, 0, 0, 13, 86, 1, 0, 0, 0, 15, 89, 1, 0, 0, 0, 17, 92,
|
||||
1, 0, 0, 0, 19, 94, 1, 0, 0, 0, 21, 97, 1, 0, 0, 0, 23, 99, 1, 0, 0, 0,
|
||||
25, 102, 1, 0, 0, 0, 27, 107, 1, 0, 0, 0, 29, 120, 1, 0, 0, 0, 31, 126,
|
||||
1, 0, 0, 0, 33, 140, 1, 0, 0, 0, 35, 148, 1, 0, 0, 0, 37, 156, 1, 0, 0,
|
||||
0, 39, 163, 1, 0, 0, 0, 41, 173, 1, 0, 0, 0, 43, 176, 1, 0, 0, 0, 45, 180,
|
||||
1, 0, 0, 0, 47, 184, 1, 0, 0, 0, 49, 187, 1, 0, 0, 0, 51, 191, 1, 0, 0,
|
||||
0, 53, 198, 1, 0, 0, 0, 55, 205, 1, 0, 0, 0, 57, 222, 1, 0, 0, 0, 59, 225,
|
||||
1, 0, 0, 0, 61, 257, 1, 0, 0, 0, 63, 259, 1, 0, 0, 0, 65, 267, 1, 0, 0,
|
||||
0, 67, 273, 1, 0, 0, 0, 69, 276, 1, 0, 0, 0, 71, 72, 5, 40, 0, 0, 72, 2,
|
||||
1, 0, 0, 0, 73, 74, 5, 41, 0, 0, 74, 4, 1, 0, 0, 0, 75, 76, 5, 91, 0, 0,
|
||||
76, 6, 1, 0, 0, 0, 77, 78, 5, 93, 0, 0, 78, 8, 1, 0, 0, 0, 79, 80, 5, 44,
|
||||
0, 0, 80, 10, 1, 0, 0, 0, 81, 85, 5, 61, 0, 0, 82, 83, 5, 61, 0, 0, 83,
|
||||
85, 5, 61, 0, 0, 84, 81, 1, 0, 0, 0, 84, 82, 1, 0, 0, 0, 85, 12, 1, 0,
|
||||
0, 0, 86, 87, 5, 33, 0, 0, 87, 88, 5, 61, 0, 0, 88, 14, 1, 0, 0, 0, 89,
|
||||
90, 5, 60, 0, 0, 90, 91, 5, 62, 0, 0, 91, 16, 1, 0, 0, 0, 92, 93, 5, 60,
|
||||
0, 0, 93, 18, 1, 0, 0, 0, 94, 95, 5, 60, 0, 0, 95, 96, 5, 61, 0, 0, 96,
|
||||
20, 1, 0, 0, 0, 97, 98, 5, 62, 0, 0, 98, 22, 1, 0, 0, 0, 99, 100, 5, 62,
|
||||
0, 0, 100, 101, 5, 61, 0, 0, 101, 24, 1, 0, 0, 0, 102, 103, 7, 0, 0, 0,
|
||||
103, 104, 7, 1, 0, 0, 104, 105, 7, 2, 0, 0, 105, 106, 7, 3, 0, 0, 106,
|
||||
26, 1, 0, 0, 0, 107, 108, 7, 4, 0, 0, 108, 109, 7, 5, 0, 0, 109, 111, 7,
|
||||
6, 0, 0, 110, 112, 7, 7, 0, 0, 111, 110, 1, 0, 0, 0, 112, 113, 1, 0, 0,
|
||||
0, 113, 111, 1, 0, 0, 0, 113, 114, 1, 0, 0, 0, 114, 115, 1, 0, 0, 0, 115,
|
||||
116, 7, 0, 0, 0, 116, 117, 7, 1, 0, 0, 117, 118, 7, 2, 0, 0, 118, 119,
|
||||
7, 3, 0, 0, 119, 28, 1, 0, 0, 0, 120, 121, 7, 1, 0, 0, 121, 122, 7, 0,
|
||||
0, 0, 122, 123, 7, 1, 0, 0, 123, 124, 7, 2, 0, 0, 124, 125, 7, 3, 0, 0,
|
||||
125, 30, 1, 0, 0, 0, 126, 127, 7, 4, 0, 0, 127, 128, 7, 5, 0, 0, 128, 130,
|
||||
7, 6, 0, 0, 129, 131, 7, 7, 0, 0, 130, 129, 1, 0, 0, 0, 131, 132, 1, 0,
|
||||
0, 0, 132, 130, 1, 0, 0, 0, 132, 133, 1, 0, 0, 0, 133, 134, 1, 0, 0, 0,
|
||||
134, 135, 7, 1, 0, 0, 135, 136, 7, 0, 0, 0, 136, 137, 7, 1, 0, 0, 137,
|
||||
138, 7, 2, 0, 0, 138, 139, 7, 3, 0, 0, 139, 32, 1, 0, 0, 0, 140, 141, 7,
|
||||
8, 0, 0, 141, 142, 7, 3, 0, 0, 142, 143, 7, 6, 0, 0, 143, 144, 7, 9, 0,
|
||||
0, 144, 145, 7, 3, 0, 0, 145, 146, 7, 3, 0, 0, 146, 147, 7, 4, 0, 0, 147,
|
||||
34, 1, 0, 0, 0, 148, 149, 7, 3, 0, 0, 149, 150, 7, 10, 0, 0, 150, 151,
|
||||
7, 1, 0, 0, 151, 152, 7, 11, 0, 0, 152, 154, 7, 6, 0, 0, 153, 155, 7, 11,
|
||||
0, 0, 154, 153, 1, 0, 0, 0, 154, 155, 1, 0, 0, 0, 155, 36, 1, 0, 0, 0,
|
||||
156, 157, 7, 12, 0, 0, 157, 158, 7, 3, 0, 0, 158, 159, 7, 13, 0, 0, 159,
|
||||
160, 7, 3, 0, 0, 160, 161, 7, 10, 0, 0, 161, 162, 7, 14, 0, 0, 162, 38,
|
||||
1, 0, 0, 0, 163, 164, 7, 15, 0, 0, 164, 165, 7, 5, 0, 0, 165, 166, 7, 4,
|
||||
0, 0, 166, 167, 7, 6, 0, 0, 167, 168, 7, 16, 0, 0, 168, 169, 7, 1, 0, 0,
|
||||
169, 171, 7, 4, 0, 0, 170, 172, 7, 11, 0, 0, 171, 170, 1, 0, 0, 0, 171,
|
||||
172, 1, 0, 0, 0, 172, 40, 1, 0, 0, 0, 173, 174, 7, 1, 0, 0, 174, 175, 7,
|
||||
4, 0, 0, 175, 42, 1, 0, 0, 0, 176, 177, 7, 4, 0, 0, 177, 178, 7, 5, 0,
|
||||
0, 178, 179, 7, 6, 0, 0, 179, 44, 1, 0, 0, 0, 180, 181, 7, 16, 0, 0, 181,
|
||||
182, 7, 4, 0, 0, 182, 183, 7, 17, 0, 0, 183, 46, 1, 0, 0, 0, 184, 185,
|
||||
7, 5, 0, 0, 185, 186, 7, 12, 0, 0, 186, 48, 1, 0, 0, 0, 187, 188, 7, 18,
|
||||
0, 0, 188, 189, 7, 16, 0, 0, 189, 190, 7, 11, 0, 0, 190, 50, 1, 0, 0, 0,
|
||||
191, 192, 7, 18, 0, 0, 192, 193, 7, 16, 0, 0, 193, 194, 7, 11, 0, 0, 194,
|
||||
195, 7, 16, 0, 0, 195, 196, 7, 4, 0, 0, 196, 197, 7, 19, 0, 0, 197, 52,
|
||||
1, 0, 0, 0, 198, 199, 7, 18, 0, 0, 199, 200, 7, 16, 0, 0, 200, 201, 7,
|
||||
11, 0, 0, 201, 202, 7, 16, 0, 0, 202, 203, 7, 0, 0, 0, 203, 204, 7, 0,
|
||||
0, 0, 204, 54, 1, 0, 0, 0, 205, 206, 7, 18, 0, 0, 206, 207, 7, 16, 0, 0,
|
||||
207, 208, 7, 11, 0, 0, 208, 209, 7, 4, 0, 0, 209, 210, 7, 5, 0, 0, 210,
|
||||
211, 7, 4, 0, 0, 211, 212, 7, 3, 0, 0, 212, 56, 1, 0, 0, 0, 213, 214, 7,
|
||||
6, 0, 0, 214, 215, 7, 12, 0, 0, 215, 216, 7, 20, 0, 0, 216, 223, 7, 3,
|
||||
0, 0, 217, 218, 7, 21, 0, 0, 218, 219, 7, 16, 0, 0, 219, 220, 7, 0, 0,
|
||||
0, 220, 221, 7, 11, 0, 0, 221, 223, 7, 3, 0, 0, 222, 213, 1, 0, 0, 0, 222,
|
||||
217, 1, 0, 0, 0, 223, 58, 1, 0, 0, 0, 224, 226, 3, 67, 33, 0, 225, 224,
|
||||
1, 0, 0, 0, 226, 227, 1, 0, 0, 0, 227, 225, 1, 0, 0, 0, 227, 228, 1, 0,
|
||||
0, 0, 228, 235, 1, 0, 0, 0, 229, 231, 5, 46, 0, 0, 230, 232, 3, 67, 33,
|
||||
0, 231, 230, 1, 0, 0, 0, 232, 233, 1, 0, 0, 0, 233, 231, 1, 0, 0, 0, 233,
|
||||
234, 1, 0, 0, 0, 234, 236, 1, 0, 0, 0, 235, 229, 1, 0, 0, 0, 235, 236,
|
||||
1, 0, 0, 0, 236, 60, 1, 0, 0, 0, 237, 243, 5, 34, 0, 0, 238, 242, 8, 22,
|
||||
0, 0, 239, 240, 5, 92, 0, 0, 240, 242, 9, 0, 0, 0, 241, 238, 1, 0, 0, 0,
|
||||
241, 239, 1, 0, 0, 0, 242, 245, 1, 0, 0, 0, 243, 241, 1, 0, 0, 0, 243,
|
||||
244, 1, 0, 0, 0, 244, 246, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, 246, 258,
|
||||
5, 34, 0, 0, 247, 253, 5, 39, 0, 0, 248, 252, 8, 23, 0, 0, 249, 250, 5,
|
||||
92, 0, 0, 250, 252, 9, 0, 0, 0, 251, 248, 1, 0, 0, 0, 251, 249, 1, 0, 0,
|
||||
0, 252, 255, 1, 0, 0, 0, 253, 251, 1, 0, 0, 0, 253, 254, 1, 0, 0, 0, 254,
|
||||
256, 1, 0, 0, 0, 255, 253, 1, 0, 0, 0, 256, 258, 5, 39, 0, 0, 257, 237,
|
||||
1, 0, 0, 0, 257, 247, 1, 0, 0, 0, 258, 62, 1, 0, 0, 0, 259, 263, 7, 24,
|
||||
0, 0, 260, 262, 7, 25, 0, 0, 261, 260, 1, 0, 0, 0, 262, 265, 1, 0, 0, 0,
|
||||
263, 261, 1, 0, 0, 0, 263, 264, 1, 0, 0, 0, 264, 64, 1, 0, 0, 0, 265, 263,
|
||||
1, 0, 0, 0, 266, 268, 7, 26, 0, 0, 267, 266, 1, 0, 0, 0, 268, 269, 1, 0,
|
||||
0, 0, 269, 267, 1, 0, 0, 0, 269, 270, 1, 0, 0, 0, 270, 271, 1, 0, 0, 0,
|
||||
271, 272, 6, 32, 0, 0, 272, 66, 1, 0, 0, 0, 273, 274, 7, 27, 0, 0, 274,
|
||||
68, 1, 0, 0, 0, 275, 277, 8, 28, 0, 0, 276, 275, 1, 0, 0, 0, 277, 278,
|
||||
1, 0, 0, 0, 278, 276, 1, 0, 0, 0, 278, 279, 1, 0, 0, 0, 279, 70, 1, 0,
|
||||
0, 0, 18, 0, 84, 113, 132, 154, 171, 222, 227, 233, 235, 241, 243, 251,
|
||||
253, 257, 263, 269, 278, 1, 6, 0, 0,
|
||||
8, 0, 9, 10, 13, 13, 32, 34, 39, 41, 44, 44, 60, 62, 91, 91, 93, 93, 285,
|
||||
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, 39,
|
||||
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, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0,
|
||||
0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 61, 1, 0, 0,
|
||||
0, 0, 63, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 1, 69, 1, 0, 0, 0, 3, 71, 1, 0,
|
||||
0, 0, 5, 73, 1, 0, 0, 0, 7, 75, 1, 0, 0, 0, 9, 77, 1, 0, 0, 0, 11, 82,
|
||||
1, 0, 0, 0, 13, 84, 1, 0, 0, 0, 15, 87, 1, 0, 0, 0, 17, 90, 1, 0, 0, 0,
|
||||
19, 92, 1, 0, 0, 0, 21, 95, 1, 0, 0, 0, 23, 97, 1, 0, 0, 0, 25, 100, 1,
|
||||
0, 0, 0, 27, 105, 1, 0, 0, 0, 29, 118, 1, 0, 0, 0, 31, 124, 1, 0, 0, 0,
|
||||
33, 138, 1, 0, 0, 0, 35, 146, 1, 0, 0, 0, 37, 154, 1, 0, 0, 0, 39, 161,
|
||||
1, 0, 0, 0, 41, 171, 1, 0, 0, 0, 43, 174, 1, 0, 0, 0, 45, 178, 1, 0, 0,
|
||||
0, 47, 182, 1, 0, 0, 0, 49, 185, 1, 0, 0, 0, 51, 189, 1, 0, 0, 0, 53, 196,
|
||||
1, 0, 0, 0, 55, 212, 1, 0, 0, 0, 57, 215, 1, 0, 0, 0, 59, 247, 1, 0, 0,
|
||||
0, 61, 249, 1, 0, 0, 0, 63, 257, 1, 0, 0, 0, 65, 263, 1, 0, 0, 0, 67, 266,
|
||||
1, 0, 0, 0, 69, 70, 5, 40, 0, 0, 70, 2, 1, 0, 0, 0, 71, 72, 5, 41, 0, 0,
|
||||
72, 4, 1, 0, 0, 0, 73, 74, 5, 91, 0, 0, 74, 6, 1, 0, 0, 0, 75, 76, 5, 93,
|
||||
0, 0, 76, 8, 1, 0, 0, 0, 77, 78, 5, 44, 0, 0, 78, 10, 1, 0, 0, 0, 79, 83,
|
||||
5, 61, 0, 0, 80, 81, 5, 61, 0, 0, 81, 83, 5, 61, 0, 0, 82, 79, 1, 0, 0,
|
||||
0, 82, 80, 1, 0, 0, 0, 83, 12, 1, 0, 0, 0, 84, 85, 5, 33, 0, 0, 85, 86,
|
||||
5, 61, 0, 0, 86, 14, 1, 0, 0, 0, 87, 88, 5, 60, 0, 0, 88, 89, 5, 62, 0,
|
||||
0, 89, 16, 1, 0, 0, 0, 90, 91, 5, 60, 0, 0, 91, 18, 1, 0, 0, 0, 92, 93,
|
||||
5, 60, 0, 0, 93, 94, 5, 61, 0, 0, 94, 20, 1, 0, 0, 0, 95, 96, 5, 62, 0,
|
||||
0, 96, 22, 1, 0, 0, 0, 97, 98, 5, 62, 0, 0, 98, 99, 5, 61, 0, 0, 99, 24,
|
||||
1, 0, 0, 0, 100, 101, 7, 0, 0, 0, 101, 102, 7, 1, 0, 0, 102, 103, 7, 2,
|
||||
0, 0, 103, 104, 7, 3, 0, 0, 104, 26, 1, 0, 0, 0, 105, 106, 7, 4, 0, 0,
|
||||
106, 107, 7, 5, 0, 0, 107, 109, 7, 6, 0, 0, 108, 110, 7, 7, 0, 0, 109,
|
||||
108, 1, 0, 0, 0, 110, 111, 1, 0, 0, 0, 111, 109, 1, 0, 0, 0, 111, 112,
|
||||
1, 0, 0, 0, 112, 113, 1, 0, 0, 0, 113, 114, 7, 0, 0, 0, 114, 115, 7, 1,
|
||||
0, 0, 115, 116, 7, 2, 0, 0, 116, 117, 7, 3, 0, 0, 117, 28, 1, 0, 0, 0,
|
||||
118, 119, 7, 1, 0, 0, 119, 120, 7, 0, 0, 0, 120, 121, 7, 1, 0, 0, 121,
|
||||
122, 7, 2, 0, 0, 122, 123, 7, 3, 0, 0, 123, 30, 1, 0, 0, 0, 124, 125, 7,
|
||||
4, 0, 0, 125, 126, 7, 5, 0, 0, 126, 128, 7, 6, 0, 0, 127, 129, 7, 7, 0,
|
||||
0, 128, 127, 1, 0, 0, 0, 129, 130, 1, 0, 0, 0, 130, 128, 1, 0, 0, 0, 130,
|
||||
131, 1, 0, 0, 0, 131, 132, 1, 0, 0, 0, 132, 133, 7, 1, 0, 0, 133, 134,
|
||||
7, 0, 0, 0, 134, 135, 7, 1, 0, 0, 135, 136, 7, 2, 0, 0, 136, 137, 7, 3,
|
||||
0, 0, 137, 32, 1, 0, 0, 0, 138, 139, 7, 8, 0, 0, 139, 140, 7, 3, 0, 0,
|
||||
140, 141, 7, 6, 0, 0, 141, 142, 7, 9, 0, 0, 142, 143, 7, 3, 0, 0, 143,
|
||||
144, 7, 3, 0, 0, 144, 145, 7, 4, 0, 0, 145, 34, 1, 0, 0, 0, 146, 147, 7,
|
||||
3, 0, 0, 147, 148, 7, 10, 0, 0, 148, 149, 7, 1, 0, 0, 149, 150, 7, 11,
|
||||
0, 0, 150, 152, 7, 6, 0, 0, 151, 153, 7, 11, 0, 0, 152, 151, 1, 0, 0, 0,
|
||||
152, 153, 1, 0, 0, 0, 153, 36, 1, 0, 0, 0, 154, 155, 7, 12, 0, 0, 155,
|
||||
156, 7, 3, 0, 0, 156, 157, 7, 13, 0, 0, 157, 158, 7, 3, 0, 0, 158, 159,
|
||||
7, 10, 0, 0, 159, 160, 7, 14, 0, 0, 160, 38, 1, 0, 0, 0, 161, 162, 7, 15,
|
||||
0, 0, 162, 163, 7, 5, 0, 0, 163, 164, 7, 4, 0, 0, 164, 165, 7, 6, 0, 0,
|
||||
165, 166, 7, 16, 0, 0, 166, 167, 7, 1, 0, 0, 167, 169, 7, 4, 0, 0, 168,
|
||||
170, 7, 11, 0, 0, 169, 168, 1, 0, 0, 0, 169, 170, 1, 0, 0, 0, 170, 40,
|
||||
1, 0, 0, 0, 171, 172, 7, 1, 0, 0, 172, 173, 7, 4, 0, 0, 173, 42, 1, 0,
|
||||
0, 0, 174, 175, 7, 4, 0, 0, 175, 176, 7, 5, 0, 0, 176, 177, 7, 6, 0, 0,
|
||||
177, 44, 1, 0, 0, 0, 178, 179, 7, 16, 0, 0, 179, 180, 7, 4, 0, 0, 180,
|
||||
181, 7, 17, 0, 0, 181, 46, 1, 0, 0, 0, 182, 183, 7, 5, 0, 0, 183, 184,
|
||||
7, 12, 0, 0, 184, 48, 1, 0, 0, 0, 185, 186, 7, 18, 0, 0, 186, 187, 7, 16,
|
||||
0, 0, 187, 188, 7, 11, 0, 0, 188, 50, 1, 0, 0, 0, 189, 190, 7, 18, 0, 0,
|
||||
190, 191, 7, 16, 0, 0, 191, 192, 7, 11, 0, 0, 192, 193, 7, 16, 0, 0, 193,
|
||||
194, 7, 4, 0, 0, 194, 195, 7, 19, 0, 0, 195, 52, 1, 0, 0, 0, 196, 197,
|
||||
7, 18, 0, 0, 197, 198, 7, 16, 0, 0, 198, 199, 7, 11, 0, 0, 199, 200, 7,
|
||||
16, 0, 0, 200, 201, 7, 0, 0, 0, 201, 202, 7, 0, 0, 0, 202, 54, 1, 0, 0,
|
||||
0, 203, 204, 7, 6, 0, 0, 204, 205, 7, 12, 0, 0, 205, 206, 7, 20, 0, 0,
|
||||
206, 213, 7, 3, 0, 0, 207, 208, 7, 21, 0, 0, 208, 209, 7, 16, 0, 0, 209,
|
||||
210, 7, 0, 0, 0, 210, 211, 7, 11, 0, 0, 211, 213, 7, 3, 0, 0, 212, 203,
|
||||
1, 0, 0, 0, 212, 207, 1, 0, 0, 0, 213, 56, 1, 0, 0, 0, 214, 216, 3, 65,
|
||||
32, 0, 215, 214, 1, 0, 0, 0, 216, 217, 1, 0, 0, 0, 217, 215, 1, 0, 0, 0,
|
||||
217, 218, 1, 0, 0, 0, 218, 225, 1, 0, 0, 0, 219, 221, 5, 46, 0, 0, 220,
|
||||
222, 3, 65, 32, 0, 221, 220, 1, 0, 0, 0, 222, 223, 1, 0, 0, 0, 223, 221,
|
||||
1, 0, 0, 0, 223, 224, 1, 0, 0, 0, 224, 226, 1, 0, 0, 0, 225, 219, 1, 0,
|
||||
0, 0, 225, 226, 1, 0, 0, 0, 226, 58, 1, 0, 0, 0, 227, 233, 5, 34, 0, 0,
|
||||
228, 232, 8, 22, 0, 0, 229, 230, 5, 92, 0, 0, 230, 232, 9, 0, 0, 0, 231,
|
||||
228, 1, 0, 0, 0, 231, 229, 1, 0, 0, 0, 232, 235, 1, 0, 0, 0, 233, 231,
|
||||
1, 0, 0, 0, 233, 234, 1, 0, 0, 0, 234, 236, 1, 0, 0, 0, 235, 233, 1, 0,
|
||||
0, 0, 236, 248, 5, 34, 0, 0, 237, 243, 5, 39, 0, 0, 238, 242, 8, 23, 0,
|
||||
0, 239, 240, 5, 92, 0, 0, 240, 242, 9, 0, 0, 0, 241, 238, 1, 0, 0, 0, 241,
|
||||
239, 1, 0, 0, 0, 242, 245, 1, 0, 0, 0, 243, 241, 1, 0, 0, 0, 243, 244,
|
||||
1, 0, 0, 0, 244, 246, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, 246, 248, 5, 39,
|
||||
0, 0, 247, 227, 1, 0, 0, 0, 247, 237, 1, 0, 0, 0, 248, 60, 1, 0, 0, 0,
|
||||
249, 253, 7, 24, 0, 0, 250, 252, 7, 25, 0, 0, 251, 250, 1, 0, 0, 0, 252,
|
||||
255, 1, 0, 0, 0, 253, 251, 1, 0, 0, 0, 253, 254, 1, 0, 0, 0, 254, 62, 1,
|
||||
0, 0, 0, 255, 253, 1, 0, 0, 0, 256, 258, 7, 26, 0, 0, 257, 256, 1, 0, 0,
|
||||
0, 258, 259, 1, 0, 0, 0, 259, 257, 1, 0, 0, 0, 259, 260, 1, 0, 0, 0, 260,
|
||||
261, 1, 0, 0, 0, 261, 262, 6, 31, 0, 0, 262, 64, 1, 0, 0, 0, 263, 264,
|
||||
7, 27, 0, 0, 264, 66, 1, 0, 0, 0, 265, 267, 8, 28, 0, 0, 266, 265, 1, 0,
|
||||
0, 0, 267, 268, 1, 0, 0, 0, 268, 266, 1, 0, 0, 0, 268, 269, 1, 0, 0, 0,
|
||||
269, 68, 1, 0, 0, 0, 18, 0, 82, 111, 130, 152, 169, 212, 217, 223, 225,
|
||||
231, 233, 241, 243, 247, 253, 259, 268, 1, 6, 0, 0,
|
||||
}
|
||||
deserializer := antlr.NewATNDeserializer(nil)
|
||||
staticData.atn = deserializer.Deserialize(staticData.serializedATN)
|
||||
@ -261,11 +257,10 @@ const (
|
||||
FilterQueryLexerHAS = 25
|
||||
FilterQueryLexerHASANY = 26
|
||||
FilterQueryLexerHASALL = 27
|
||||
FilterQueryLexerHASNONE = 28
|
||||
FilterQueryLexerBOOL = 29
|
||||
FilterQueryLexerNUMBER = 30
|
||||
FilterQueryLexerQUOTED_TEXT = 31
|
||||
FilterQueryLexerKEY = 32
|
||||
FilterQueryLexerWS = 33
|
||||
FilterQueryLexerFREETEXT = 34
|
||||
FilterQueryLexerBOOL = 28
|
||||
FilterQueryLexerNUMBER = 29
|
||||
FilterQueryLexerQUOTED_TEXT = 30
|
||||
FilterQueryLexerKEY = 31
|
||||
FilterQueryLexerWS = 32
|
||||
FilterQueryLexerFREETEXT = 33
|
||||
)
|
||||
|
@ -40,8 +40,8 @@ func filterqueryParserInit() {
|
||||
"", "LPAREN", "RPAREN", "LBRACK", "RBRACK", "COMMA", "EQUALS", "NOT_EQUALS",
|
||||
"NEQ", "LT", "LE", "GT", "GE", "LIKE", "NOT_LIKE", "ILIKE", "NOT_ILIKE",
|
||||
"BETWEEN", "EXISTS", "REGEXP", "CONTAINS", "IN", "NOT", "AND", "OR",
|
||||
"HAS", "HASANY", "HASALL", "HASNONE", "BOOL", "NUMBER", "QUOTED_TEXT",
|
||||
"KEY", "WS", "FREETEXT",
|
||||
"HAS", "HASANY", "HASALL", "BOOL", "NUMBER", "QUOTED_TEXT", "KEY", "WS",
|
||||
"FREETEXT",
|
||||
}
|
||||
staticData.RuleNames = []string{
|
||||
"query", "expression", "orExpression", "andExpression", "unaryExpression",
|
||||
@ -51,7 +51,7 @@ func filterqueryParserInit() {
|
||||
}
|
||||
staticData.PredictionContextCache = antlr.NewPredictionContextCache()
|
||||
staticData.serializedATN = []int32{
|
||||
4, 1, 34, 212, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7,
|
||||
4, 1, 33, 212, 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, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 5, 2, 43,
|
||||
@ -72,7 +72,7 @@ func filterqueryParserInit() {
|
||||
13, 1, 13, 1, 13, 3, 13, 202, 8, 13, 1, 14, 1, 14, 1, 14, 1, 14, 1, 15,
|
||||
1, 15, 1, 16, 1, 16, 1, 16, 0, 0, 17, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18,
|
||||
20, 22, 24, 26, 28, 30, 32, 0, 6, 1, 0, 7, 8, 2, 0, 13, 13, 15, 15, 2,
|
||||
0, 14, 14, 16, 16, 2, 0, 31, 31, 34, 34, 1, 0, 25, 28, 1, 0, 29, 32, 225,
|
||||
0, 14, 14, 16, 16, 2, 0, 30, 30, 33, 33, 1, 0, 25, 27, 1, 0, 28, 31, 225,
|
||||
0, 34, 1, 0, 0, 0, 2, 37, 1, 0, 0, 0, 4, 39, 1, 0, 0, 0, 6, 47, 1, 0, 0,
|
||||
0, 8, 57, 1, 0, 0, 0, 10, 69, 1, 0, 0, 0, 12, 147, 1, 0, 0, 0, 14, 159,
|
||||
1, 0, 0, 0, 16, 173, 1, 0, 0, 0, 18, 175, 1, 0, 0, 0, 20, 183, 1, 0, 0,
|
||||
@ -140,7 +140,7 @@ func filterqueryParserInit() {
|
||||
201, 198, 1, 0, 0, 0, 201, 199, 1, 0, 0, 0, 201, 200, 1, 0, 0, 0, 202,
|
||||
27, 1, 0, 0, 0, 203, 204, 5, 3, 0, 0, 204, 205, 3, 18, 9, 0, 205, 206,
|
||||
5, 4, 0, 0, 206, 29, 1, 0, 0, 0, 207, 208, 7, 5, 0, 0, 208, 31, 1, 0, 0,
|
||||
0, 209, 210, 5, 32, 0, 0, 210, 33, 1, 0, 0, 0, 11, 44, 51, 53, 57, 69,
|
||||
0, 209, 210, 5, 31, 0, 0, 210, 33, 1, 0, 0, 0, 11, 44, 51, 53, 57, 69,
|
||||
147, 159, 173, 180, 195, 201,
|
||||
}
|
||||
deserializer := antlr.NewATNDeserializer(nil)
|
||||
@ -207,13 +207,12 @@ const (
|
||||
FilterQueryParserHAS = 25
|
||||
FilterQueryParserHASANY = 26
|
||||
FilterQueryParserHASALL = 27
|
||||
FilterQueryParserHASNONE = 28
|
||||
FilterQueryParserBOOL = 29
|
||||
FilterQueryParserNUMBER = 30
|
||||
FilterQueryParserQUOTED_TEXT = 31
|
||||
FilterQueryParserKEY = 32
|
||||
FilterQueryParserWS = 33
|
||||
FilterQueryParserFREETEXT = 34
|
||||
FilterQueryParserBOOL = 28
|
||||
FilterQueryParserNUMBER = 29
|
||||
FilterQueryParserQUOTED_TEXT = 30
|
||||
FilterQueryParserKEY = 31
|
||||
FilterQueryParserWS = 32
|
||||
FilterQueryParserFREETEXT = 33
|
||||
)
|
||||
|
||||
// FilterQueryParser rules.
|
||||
@ -803,7 +802,7 @@ func (p *FilterQueryParser) AndExpression() (localctx IAndExpressionContext) {
|
||||
}
|
||||
_la = p.GetTokenStream().LA(1)
|
||||
|
||||
for (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&24138219522) != 0 {
|
||||
for (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&12058624002) != 0 {
|
||||
p.SetState(51)
|
||||
p.GetErrorHandler().Sync(p)
|
||||
if p.HasError() {
|
||||
@ -825,7 +824,7 @@ func (p *FilterQueryParser) AndExpression() (localctx IAndExpressionContext) {
|
||||
p.UnaryExpression()
|
||||
}
|
||||
|
||||
case FilterQueryParserLPAREN, FilterQueryParserNOT, FilterQueryParserHAS, FilterQueryParserHASANY, FilterQueryParserHASALL, FilterQueryParserHASNONE, FilterQueryParserQUOTED_TEXT, FilterQueryParserKEY, FilterQueryParserFREETEXT:
|
||||
case FilterQueryParserLPAREN, FilterQueryParserNOT, FilterQueryParserHAS, FilterQueryParserHASANY, FilterQueryParserHASALL, FilterQueryParserQUOTED_TEXT, FilterQueryParserKEY, FilterQueryParserFREETEXT:
|
||||
{
|
||||
p.SetState(50)
|
||||
p.UnaryExpression()
|
||||
@ -2653,7 +2652,6 @@ type IFunctionCallContext interface {
|
||||
HAS() antlr.TerminalNode
|
||||
HASANY() antlr.TerminalNode
|
||||
HASALL() antlr.TerminalNode
|
||||
HASNONE() antlr.TerminalNode
|
||||
|
||||
// IsFunctionCallContext differentiates from other interfaces.
|
||||
IsFunctionCallContext()
|
||||
@ -2727,10 +2725,6 @@ func (s *FunctionCallContext) HASALL() antlr.TerminalNode {
|
||||
return s.GetToken(FilterQueryParserHASALL, 0)
|
||||
}
|
||||
|
||||
func (s *FunctionCallContext) HASNONE() antlr.TerminalNode {
|
||||
return s.GetToken(FilterQueryParserHASNONE, 0)
|
||||
}
|
||||
|
||||
func (s *FunctionCallContext) GetRuleContext() antlr.RuleContext {
|
||||
return s
|
||||
}
|
||||
@ -2771,7 +2765,7 @@ func (p *FilterQueryParser) FunctionCall() (localctx IFunctionCallContext) {
|
||||
p.SetState(185)
|
||||
_la = p.GetTokenStream().LA(1)
|
||||
|
||||
if !((int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&503316480) != 0) {
|
||||
if !((int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&234881024) != 0) {
|
||||
p.GetErrorHandler().RecoverInline(p)
|
||||
} else {
|
||||
p.GetErrorHandler().ReportMatch(p)
|
||||
@ -3411,7 +3405,7 @@ func (p *FilterQueryParser) Value() (localctx IValueContext) {
|
||||
p.SetState(207)
|
||||
_la = p.GetTokenStream().LA(1)
|
||||
|
||||
if !((int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&8053063680) != 0) {
|
||||
if !((int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&4026531840) != 0) {
|
||||
p.GetErrorHandler().RecoverInline(p)
|
||||
} else {
|
||||
p.GetErrorHandler().ReportMatch(p)
|
||||
|
48
pkg/parser/grammar/query_to_keys.go
Normal file
48
pkg/parser/grammar/query_to_keys.go
Normal file
@ -0,0 +1,48 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/antlr4-go/antlr/v4"
|
||||
)
|
||||
|
||||
// QueryStringToKeysSelectors converts a query string to a list of field key selectors
|
||||
//
|
||||
// e.g. "service.name="query-service" AND http.status_code=200 AND resource.k8s.namespace.name="application"" -> []*telemetrytypes.FieldKeySelector{
|
||||
// {
|
||||
// Name: "service.name",
|
||||
// FieldContext: telemetrytypes.FieldContextUnspecified,
|
||||
// FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
||||
// },
|
||||
// {
|
||||
// Name: "http.status_code",
|
||||
// FieldContext: telemetrytypes.FieldContextUnspecified,
|
||||
// FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
||||
// },
|
||||
// {
|
||||
// Name: "resource.k8s.namespace.name",
|
||||
// FieldContext: telemetrytypes.FieldContextResource,
|
||||
// FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
||||
// },
|
||||
// }
|
||||
func QueryStringToKeysSelectors(query string) ([]*telemetrytypes.FieldKeySelector, error) {
|
||||
lexer := NewFilterQueryLexer(antlr.NewInputStream(query))
|
||||
keys := []*telemetrytypes.FieldKeySelector{}
|
||||
for {
|
||||
tok := lexer.NextToken()
|
||||
if tok.GetTokenType() == antlr.TokenEOF {
|
||||
break
|
||||
}
|
||||
|
||||
if tok.GetTokenType() == FilterQueryLexerKEY {
|
||||
key := telemetrytypes.GetFieldKeyFromKeyText(tok.GetText())
|
||||
keys = append(keys, &telemetrytypes.FieldKeySelector{
|
||||
Name: key.Name,
|
||||
Signal: key.Signal,
|
||||
FieldContext: key.FieldContext,
|
||||
FieldDataType: key.FieldDataType,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return keys, nil
|
||||
}
|
101
pkg/parser/grammar/query_to_keys_test.go
Normal file
101
pkg/parser/grammar/query_to_keys_test.go
Normal file
@ -0,0 +1,101 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
|
||||
func TestQueryToKeys(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
query string
|
||||
expectedKeys []telemetrytypes.FieldKeySelector
|
||||
}{
|
||||
{
|
||||
query: `service.name="redis"`,
|
||||
expectedKeys: []telemetrytypes.FieldKeySelector{
|
||||
{
|
||||
Name: "service.name",
|
||||
Signal: telemetrytypes.SignalUnspecified,
|
||||
FieldContext: telemetrytypes.FieldContextUnspecified,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
query: `resource.service.name="redis"`,
|
||||
expectedKeys: []telemetrytypes.FieldKeySelector{
|
||||
{
|
||||
Name: "service.name",
|
||||
Signal: telemetrytypes.SignalUnspecified,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
query: `service.name="redis" AND http.status_code=200`,
|
||||
expectedKeys: []telemetrytypes.FieldKeySelector{
|
||||
{
|
||||
Name: "service.name",
|
||||
Signal: telemetrytypes.SignalUnspecified,
|
||||
FieldContext: telemetrytypes.FieldContextUnspecified,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
||||
},
|
||||
{
|
||||
Name: "http.status_code",
|
||||
Signal: telemetrytypes.SignalUnspecified,
|
||||
FieldContext: telemetrytypes.FieldContextUnspecified,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
query: `has(payload.user_ids, 123)`,
|
||||
expectedKeys: []telemetrytypes.FieldKeySelector{
|
||||
{
|
||||
Name: "payload.user_ids",
|
||||
Signal: telemetrytypes.SignalUnspecified,
|
||||
FieldContext: telemetrytypes.FieldContextUnspecified,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
query: `body.user_ids[*] = 123`,
|
||||
expectedKeys: []telemetrytypes.FieldKeySelector{
|
||||
{
|
||||
Name: "body.user_ids[*]",
|
||||
Signal: telemetrytypes.SignalUnspecified,
|
||||
FieldContext: telemetrytypes.FieldContextUnspecified,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeUnspecified,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
keys, err := QueryStringToKeysSelectors(testCase.query)
|
||||
if err != nil {
|
||||
t.Fatalf("Error: %v", err)
|
||||
}
|
||||
if len(keys) != len(testCase.expectedKeys) {
|
||||
t.Fatalf("Expected %d keys, got %d", len(testCase.expectedKeys), len(keys))
|
||||
}
|
||||
for i, key := range keys {
|
||||
if key.Name != testCase.expectedKeys[i].Name {
|
||||
t.Fatalf("Expected key %v, got %v", testCase.expectedKeys[i], key)
|
||||
}
|
||||
if key.Signal != testCase.expectedKeys[i].Signal {
|
||||
t.Fatalf("Expected signal %v, got %v", testCase.expectedKeys[i].Signal, key.Signal)
|
||||
}
|
||||
if key.FieldContext != testCase.expectedKeys[i].FieldContext {
|
||||
t.Fatalf("Expected field context %v, got %v", testCase.expectedKeys[i].FieldContext, key.FieldContext)
|
||||
}
|
||||
if key.FieldDataType != testCase.expectedKeys[i].FieldDataType {
|
||||
t.Fatalf("Expected field data type %v, got %v", testCase.expectedKeys[i].FieldDataType, key.FieldDataType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
587
pkg/parser/grammar/where_clause_visitor.go
Normal file
587
pkg/parser/grammar/where_clause_visitor.go
Normal file
@ -0,0 +1,587 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrylogs"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/antlr4-go/antlr/v4"
|
||||
|
||||
sqlbuilder "github.com/huandu/go-sqlbuilder"
|
||||
)
|
||||
|
||||
// WhereClauseVisitor implements the FilterQueryVisitor interface
|
||||
// to convert the parsed filter expressions into ClickHouse WHERE clause
|
||||
type WhereClauseVisitor struct {
|
||||
conditionBuilder qbtypes.ConditionBuilder
|
||||
warnings []error
|
||||
fieldKeys map[string][]telemetrytypes.TelemetryFieldKey
|
||||
errors []error
|
||||
builder *sqlbuilder.SelectBuilder
|
||||
fullTextColumn telemetrytypes.TelemetryFieldKey
|
||||
}
|
||||
|
||||
// NewWhereClauseVisitor creates a new WhereClauseVisitor
|
||||
func NewWhereClauseVisitor(
|
||||
conditionBuilder qbtypes.ConditionBuilder,
|
||||
fieldKeys map[string][]telemetrytypes.TelemetryFieldKey,
|
||||
builder *sqlbuilder.SelectBuilder,
|
||||
fullTextColumn telemetrytypes.TelemetryFieldKey,
|
||||
) *WhereClauseVisitor {
|
||||
return &WhereClauseVisitor{
|
||||
conditionBuilder: conditionBuilder,
|
||||
fieldKeys: fieldKeys,
|
||||
builder: builder,
|
||||
fullTextColumn: fullTextColumn,
|
||||
}
|
||||
}
|
||||
|
||||
type SyntaxError struct {
|
||||
line, column int
|
||||
msg string
|
||||
}
|
||||
|
||||
func (e *SyntaxError) Error() string {
|
||||
return fmt.Sprintf("line %d:%d %s", e.line, e.column, e.msg)
|
||||
}
|
||||
|
||||
// ErrorListener is a custom error listener to capture syntax errors
|
||||
type ErrorListener struct {
|
||||
*antlr.DefaultErrorListener
|
||||
Errors []error
|
||||
}
|
||||
|
||||
// NewErrorListener creates a new error listener
|
||||
func NewErrorListener() *ErrorListener {
|
||||
return &ErrorListener{
|
||||
DefaultErrorListener: antlr.NewDefaultErrorListener(),
|
||||
Errors: []error{},
|
||||
}
|
||||
}
|
||||
|
||||
// SyntaxError captures syntax errors during parsing
|
||||
func (l *ErrorListener) SyntaxError(recognizer antlr.Recognizer, offendingSymbol any, line, column int, msg string, e antlr.RecognitionException) {
|
||||
l.Errors = append(l.Errors, &SyntaxError{line: line, column: column, msg: msg})
|
||||
}
|
||||
|
||||
// PrepareWhereClause generates a ClickHouse compatible WHERE clause from the filter query
|
||||
func PrepareWhereClause(
|
||||
query string,
|
||||
fieldKeys map[string][]telemetrytypes.TelemetryFieldKey,
|
||||
conditionBuilder qbtypes.ConditionBuilder,
|
||||
fullTextColumn telemetrytypes.TelemetryFieldKey,
|
||||
) (string, []any, []error, error) {
|
||||
// Setup the ANTLR parsing pipeline
|
||||
input := antlr.NewInputStream(query)
|
||||
lexer := NewFilterQueryLexer(input)
|
||||
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
|
||||
visitor := NewWhereClauseVisitor(conditionBuilder, fieldKeys, sb, fullTextColumn)
|
||||
|
||||
// Set up error handling
|
||||
lexerErrorListener := NewErrorListener()
|
||||
lexer.RemoveErrorListeners()
|
||||
lexer.AddErrorListener(lexerErrorListener)
|
||||
|
||||
tokens := antlr.NewCommonTokenStream(lexer, 0)
|
||||
parserErrorListener := NewErrorListener()
|
||||
parser := NewFilterQueryParser(tokens)
|
||||
parser.RemoveErrorListeners()
|
||||
parser.AddErrorListener(parserErrorListener)
|
||||
|
||||
// Parse the query
|
||||
tree := parser.Query()
|
||||
|
||||
// Handle syntax errors
|
||||
if len(parserErrorListener.Errors) > 0 {
|
||||
combinedErrors := errors.Newf(
|
||||
errors.TypeInvalidInput,
|
||||
errors.CodeInvalidInput,
|
||||
"found %d syntax errors while parsing the search expression: %v",
|
||||
len(parserErrorListener.Errors),
|
||||
parserErrorListener.Errors,
|
||||
)
|
||||
return "", nil, nil, combinedErrors
|
||||
}
|
||||
|
||||
// Visit the parse tree with our ClickHouse visitor
|
||||
cond := visitor.Visit(tree).(string)
|
||||
|
||||
if len(visitor.errors) > 0 {
|
||||
// combine all errors into a single error
|
||||
combinedErrors := errors.Newf(
|
||||
errors.TypeInvalidInput,
|
||||
errors.CodeInvalidInput,
|
||||
"found %d errors while parsing the search expression: %v",
|
||||
len(visitor.errors),
|
||||
visitor.errors,
|
||||
)
|
||||
return "", nil, nil, combinedErrors
|
||||
}
|
||||
|
||||
whereClause, args := visitor.builder.Where(cond).BuildWithFlavor(sqlbuilder.ClickHouse)
|
||||
|
||||
return whereClause, args, visitor.warnings, nil
|
||||
}
|
||||
|
||||
// Visit dispatches to the specific visit method based on node type
|
||||
func (v *WhereClauseVisitor) Visit(tree antlr.ParseTree) any {
|
||||
// Handle nil nodes to prevent panic
|
||||
if tree == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch t := tree.(type) {
|
||||
case *QueryContext:
|
||||
return v.VisitQuery(t)
|
||||
case *ExpressionContext:
|
||||
return v.VisitExpression(t)
|
||||
case *OrExpressionContext:
|
||||
return v.VisitOrExpression(t)
|
||||
case *AndExpressionContext:
|
||||
return v.VisitAndExpression(t)
|
||||
case *UnaryExpressionContext:
|
||||
return v.VisitUnaryExpression(t)
|
||||
case *PrimaryContext:
|
||||
return v.VisitPrimary(t)
|
||||
case *ComparisonContext:
|
||||
return v.VisitComparison(t)
|
||||
case *InClauseContext:
|
||||
return v.VisitInClause(t)
|
||||
case *NotInClauseContext:
|
||||
return v.VisitNotInClause(t)
|
||||
case *ValueListContext:
|
||||
return v.VisitValueList(t)
|
||||
case *FullTextContext:
|
||||
return v.VisitFullText(t)
|
||||
case *FunctionCallContext:
|
||||
return v.VisitFunctionCall(t)
|
||||
case *FunctionParamListContext:
|
||||
return v.VisitFunctionParamList(t)
|
||||
case *FunctionParamContext:
|
||||
return v.VisitFunctionParam(t)
|
||||
case *ArrayContext:
|
||||
return v.VisitArray(t)
|
||||
case *ValueContext:
|
||||
return v.VisitValue(t)
|
||||
case *KeyContext:
|
||||
return v.VisitKey(t)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (v *WhereClauseVisitor) VisitQuery(ctx *QueryContext) any {
|
||||
|
||||
return v.Visit(ctx.Expression())
|
||||
}
|
||||
|
||||
// VisitExpression passes through to the orExpression
|
||||
func (v *WhereClauseVisitor) VisitExpression(ctx *ExpressionContext) any {
|
||||
return v.Visit(ctx.OrExpression())
|
||||
}
|
||||
|
||||
// VisitOrExpression handles OR expressions
|
||||
func (v *WhereClauseVisitor) VisitOrExpression(ctx *OrExpressionContext) any {
|
||||
andExpressions := ctx.AllAndExpression()
|
||||
|
||||
andExpressionConditions := make([]string, len(andExpressions))
|
||||
for i, expr := range andExpressions {
|
||||
andExpressionConditions[i] = v.Visit(expr).(string)
|
||||
}
|
||||
|
||||
if len(andExpressionConditions) == 1 {
|
||||
return andExpressionConditions[0]
|
||||
}
|
||||
|
||||
return v.builder.Or(andExpressionConditions...)
|
||||
}
|
||||
|
||||
// VisitAndExpression handles AND expressions
|
||||
func (v *WhereClauseVisitor) VisitAndExpression(ctx *AndExpressionContext) any {
|
||||
unaryExpressions := ctx.AllUnaryExpression()
|
||||
|
||||
unaryExpressionConditions := make([]string, len(unaryExpressions))
|
||||
for i, expr := range unaryExpressions {
|
||||
unaryExpressionConditions[i] = v.Visit(expr).(string)
|
||||
}
|
||||
|
||||
if len(unaryExpressionConditions) == 1 {
|
||||
return unaryExpressionConditions[0]
|
||||
}
|
||||
|
||||
return v.builder.And(unaryExpressionConditions...)
|
||||
}
|
||||
|
||||
// VisitUnaryExpression handles NOT expressions
|
||||
func (v *WhereClauseVisitor) VisitUnaryExpression(ctx *UnaryExpressionContext) any {
|
||||
result := v.Visit(ctx.Primary()).(string)
|
||||
|
||||
// Check if this is a NOT expression
|
||||
if ctx.NOT() != nil {
|
||||
return fmt.Sprintf("NOT (%s)", result)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// VisitPrimary handles grouped expressions, comparisons, function calls, and full-text search
|
||||
func (v *WhereClauseVisitor) VisitPrimary(ctx *PrimaryContext) any {
|
||||
if ctx.OrExpression() != nil {
|
||||
// This is a parenthesized expression
|
||||
return fmt.Sprintf("(%s)", v.Visit(ctx.OrExpression()).(string))
|
||||
} else if ctx.Comparison() != nil {
|
||||
return v.Visit(ctx.Comparison())
|
||||
} else if ctx.FunctionCall() != nil {
|
||||
return v.Visit(ctx.FunctionCall())
|
||||
} else if ctx.FullText() != nil {
|
||||
return v.Visit(ctx.FullText())
|
||||
}
|
||||
|
||||
// Handle standalone key as a full text search term
|
||||
if ctx.GetChildCount() == 1 {
|
||||
child := ctx.GetChild(0)
|
||||
if keyCtx, ok := child.(*KeyContext); ok {
|
||||
// create a full text search condition on the body field
|
||||
keyText := keyCtx.GetText()
|
||||
cond, err := v.conditionBuilder.GetCondition(context.Background(), &v.fullTextColumn, qbtypes.FilterOperatorRegexp, keyText, v.builder)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return cond
|
||||
}
|
||||
}
|
||||
|
||||
return "" // Should not happen with valid input
|
||||
}
|
||||
|
||||
// VisitComparison handles all comparison operators
|
||||
func (v *WhereClauseVisitor) VisitComparison(ctx *ComparisonContext) any {
|
||||
keys := v.Visit(ctx.Key()).([]telemetrytypes.TelemetryFieldKey)
|
||||
|
||||
// Handle EXISTS specially
|
||||
if ctx.EXISTS() != nil {
|
||||
op := qbtypes.FilterOperatorExists
|
||||
if ctx.NOT() != nil {
|
||||
op = qbtypes.FilterOperatorNotExists
|
||||
}
|
||||
var conds []string
|
||||
for _, key := range keys {
|
||||
condition, err := v.conditionBuilder.GetCondition(context.Background(), &key, op, nil, v.builder)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
conds = append(conds, condition)
|
||||
}
|
||||
return v.builder.Or(conds...)
|
||||
}
|
||||
|
||||
// Handle IN clause
|
||||
if ctx.InClause() != nil || ctx.NotInClause() != nil {
|
||||
values := v.Visit(ctx.InClause()).([]any)
|
||||
op := qbtypes.FilterOperatorIn
|
||||
if ctx.NotInClause() != nil {
|
||||
op = qbtypes.FilterOperatorNotIn
|
||||
}
|
||||
var conds []string
|
||||
for _, key := range keys {
|
||||
condition, err := v.conditionBuilder.GetCondition(context.Background(), &key, op, values, v.builder)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
conds = append(conds, condition)
|
||||
}
|
||||
return v.builder.Or(conds...)
|
||||
}
|
||||
|
||||
// Handle BETWEEN
|
||||
if ctx.BETWEEN() != nil {
|
||||
op := qbtypes.FilterOperatorBetween
|
||||
if ctx.NOT() != nil {
|
||||
op = qbtypes.FilterOperatorNotBetween
|
||||
}
|
||||
|
||||
values := ctx.AllValue()
|
||||
if len(values) != 2 {
|
||||
return ""
|
||||
}
|
||||
|
||||
value1 := v.Visit(values[0])
|
||||
value2 := v.Visit(values[1])
|
||||
|
||||
var conds []string
|
||||
for _, key := range keys {
|
||||
condition, err := v.conditionBuilder.GetCondition(context.Background(), &key, op, []any{value1, value2}, v.builder)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
conds = append(conds, condition)
|
||||
}
|
||||
return v.builder.Or(conds...)
|
||||
}
|
||||
|
||||
// Get all values for operations that need them
|
||||
values := ctx.AllValue()
|
||||
if len(values) > 0 {
|
||||
value := v.Visit(values[0])
|
||||
|
||||
var op qbtypes.FilterOperator
|
||||
|
||||
// Handle each type of comparison
|
||||
if ctx.EQUALS() != nil {
|
||||
op = qbtypes.FilterOperatorEqual
|
||||
} else if ctx.NOT_EQUALS() != nil || ctx.NEQ() != nil {
|
||||
op = qbtypes.FilterOperatorNotEqual
|
||||
} else if ctx.LT() != nil {
|
||||
op = qbtypes.FilterOperatorLessThan
|
||||
} else if ctx.LE() != nil {
|
||||
op = qbtypes.FilterOperatorLessThanOrEq
|
||||
} else if ctx.GT() != nil {
|
||||
op = qbtypes.FilterOperatorGreaterThan
|
||||
} else if ctx.GE() != nil {
|
||||
op = qbtypes.FilterOperatorGreaterThanOrEq
|
||||
} else if ctx.LIKE() != nil {
|
||||
op = qbtypes.FilterOperatorLike
|
||||
} else if ctx.ILIKE() != nil {
|
||||
op = qbtypes.FilterOperatorLike
|
||||
} else if ctx.NOT_LIKE() != nil {
|
||||
op = qbtypes.FilterOperatorNotLike
|
||||
} else if ctx.NOT_ILIKE() != nil {
|
||||
op = qbtypes.FilterOperatorNotLike
|
||||
} else if ctx.REGEXP() != nil {
|
||||
op = qbtypes.FilterOperatorRegexp
|
||||
} else if ctx.NOT() != nil && ctx.REGEXP() != nil {
|
||||
op = qbtypes.FilterOperatorNotRegexp
|
||||
} else if ctx.CONTAINS() != nil {
|
||||
op = qbtypes.FilterOperatorContains
|
||||
} else if ctx.NOT() != nil && ctx.CONTAINS() != nil {
|
||||
op = qbtypes.FilterOperatorNotContains
|
||||
}
|
||||
|
||||
var conds []string
|
||||
for _, key := range keys {
|
||||
condition, err := v.conditionBuilder.GetCondition(context.Background(), &key, op, value, v.builder)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
conds = append(conds, condition)
|
||||
}
|
||||
return v.builder.Or(conds...)
|
||||
}
|
||||
|
||||
return "" // Should not happen with valid input
|
||||
}
|
||||
|
||||
// VisitInClause handles IN expressions
|
||||
func (v *WhereClauseVisitor) VisitInClause(ctx *InClauseContext) any {
|
||||
return v.Visit(ctx.ValueList())
|
||||
}
|
||||
|
||||
// VisitNotInClause handles NOT IN expressions
|
||||
func (v *WhereClauseVisitor) VisitNotInClause(ctx *NotInClauseContext) any {
|
||||
return v.Visit(ctx.ValueList())
|
||||
}
|
||||
|
||||
// VisitValueList handles comma-separated value lists
|
||||
func (v *WhereClauseVisitor) VisitValueList(ctx *ValueListContext) any {
|
||||
values := ctx.AllValue()
|
||||
|
||||
parts := []any{}
|
||||
for _, val := range values {
|
||||
parts = append(parts, v.Visit(val))
|
||||
}
|
||||
|
||||
return parts
|
||||
}
|
||||
|
||||
// VisitFullText handles standalone quoted strings for full-text search
|
||||
func (v *WhereClauseVisitor) VisitFullText(ctx *FullTextContext) any {
|
||||
// remove quotes from the quotedText
|
||||
quotedText := strings.Trim(ctx.QUOTED_TEXT().GetText(), "\"'")
|
||||
cond, err := v.conditionBuilder.GetCondition(context.Background(), &v.fullTextColumn, qbtypes.FilterOperatorRegexp, quotedText, v.builder)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
// VisitFunctionCall handles function calls like has(), hasAny(), etc.
|
||||
func (v *WhereClauseVisitor) VisitFunctionCall(ctx *FunctionCallContext) any {
|
||||
// Get function name based on which token is present
|
||||
var functionName string
|
||||
if ctx.HAS() != nil {
|
||||
functionName = "has"
|
||||
} else if ctx.HASANY() != nil {
|
||||
functionName = "hasAny"
|
||||
} else if ctx.HASALL() != nil {
|
||||
functionName = "hasAll"
|
||||
} else {
|
||||
// Default fallback
|
||||
v.errors = append(v.errors, errors.Newf(
|
||||
errors.TypeInvalidInput,
|
||||
errors.CodeInvalidInput,
|
||||
"unknown function `%s`",
|
||||
ctx.GetText(),
|
||||
))
|
||||
return ""
|
||||
}
|
||||
params := v.Visit(ctx.FunctionParamList()).([]any)
|
||||
|
||||
if len(params) < 2 {
|
||||
v.errors = append(v.errors, errors.Newf(
|
||||
errors.TypeInvalidInput,
|
||||
errors.CodeInvalidInput,
|
||||
"function `%s` expects key and value parameters",
|
||||
functionName,
|
||||
))
|
||||
return ""
|
||||
}
|
||||
|
||||
keys, ok := params[0].([]telemetrytypes.TelemetryFieldKey)
|
||||
if !ok {
|
||||
v.errors = append(v.errors, errors.Newf(
|
||||
errors.TypeInvalidInput,
|
||||
errors.CodeInvalidInput,
|
||||
"function `%s` expects key parameter to be a field key",
|
||||
functionName,
|
||||
))
|
||||
return ""
|
||||
}
|
||||
value := params[1:]
|
||||
var conds []string
|
||||
for _, key := range keys {
|
||||
var fieldName string
|
||||
|
||||
if strings.HasPrefix(key.Name, telemetrylogs.BodyJSONStringSearchPrefix) {
|
||||
fieldName, _ = telemetrylogs.GetBodyJSONKey(context.Background(), &key, qbtypes.FilterOperatorUnknown, value)
|
||||
} else {
|
||||
fieldName, _ = v.conditionBuilder.GetTableFieldName(context.Background(), &key)
|
||||
}
|
||||
|
||||
var cond string
|
||||
// Map our functions to ClickHouse equivalents
|
||||
switch functionName {
|
||||
case "has":
|
||||
cond = fmt.Sprintf("has(%s, %s)", fieldName, v.builder.Var(value[0]))
|
||||
case "hasAny":
|
||||
cond = fmt.Sprintf("hasAny(%s, %s)", fieldName, v.builder.Var(value))
|
||||
case "hasAll":
|
||||
cond = fmt.Sprintf("hasAll(%s, %s)", fieldName, v.builder.Var(value))
|
||||
}
|
||||
conds = append(conds, cond)
|
||||
}
|
||||
|
||||
return v.builder.Or(conds...)
|
||||
}
|
||||
|
||||
// VisitFunctionParamList handles the parameter list for function calls
|
||||
func (v *WhereClauseVisitor) VisitFunctionParamList(ctx *FunctionParamListContext) any {
|
||||
params := ctx.AllFunctionParam()
|
||||
parts := make([]any, len(params))
|
||||
|
||||
for i, param := range params {
|
||||
parts[i] = v.Visit(param)
|
||||
}
|
||||
|
||||
return parts
|
||||
}
|
||||
|
||||
// VisitFunctionParam handles individual parameters in function calls
|
||||
func (v *WhereClauseVisitor) VisitFunctionParam(ctx *FunctionParamContext) any {
|
||||
if ctx.Key() != nil {
|
||||
return v.Visit(ctx.Key())
|
||||
} else if ctx.Value() != nil {
|
||||
return v.Visit(ctx.Value())
|
||||
} else if ctx.Array() != nil {
|
||||
return v.Visit(ctx.Array())
|
||||
}
|
||||
|
||||
return "" // Should not happen with valid input
|
||||
}
|
||||
|
||||
// VisitArray handles array literals
|
||||
func (v *WhereClauseVisitor) VisitArray(ctx *ArrayContext) any {
|
||||
return v.Visit(ctx.ValueList())
|
||||
}
|
||||
|
||||
// VisitValue handles literal values: strings, numbers, booleans
|
||||
func (v *WhereClauseVisitor) VisitValue(ctx *ValueContext) any {
|
||||
if ctx.QUOTED_TEXT() != nil {
|
||||
txt := ctx.QUOTED_TEXT().GetText()
|
||||
// trim quotes and return the value
|
||||
return strings.Trim(txt, "\"'")
|
||||
} else if ctx.NUMBER() != nil {
|
||||
number, err := strconv.ParseFloat(ctx.NUMBER().GetText(), 64)
|
||||
if err != nil {
|
||||
v.errors = append(v.errors, errors.Newf(
|
||||
errors.TypeInvalidInput,
|
||||
errors.CodeInvalidInput,
|
||||
"failed to parse number %s",
|
||||
ctx.NUMBER().GetText(),
|
||||
))
|
||||
return ""
|
||||
}
|
||||
return number
|
||||
} else if ctx.BOOL() != nil {
|
||||
// Convert to ClickHouse boolean literal
|
||||
boolText := strings.ToLower(ctx.BOOL().GetText())
|
||||
return boolText == "true"
|
||||
} else if ctx.KEY() != nil {
|
||||
// Why do we have a KEY context here?
|
||||
// When the user writes an expression like `service.name=redis`
|
||||
// The `redis` part is a VALUE context but parsed as a KEY token
|
||||
// so we return the text as is
|
||||
return ctx.KEY().GetText()
|
||||
}
|
||||
|
||||
return "" // Should not happen with valid input
|
||||
}
|
||||
|
||||
// VisitKey handles field/column references
|
||||
func (v *WhereClauseVisitor) VisitKey(ctx *KeyContext) any {
|
||||
|
||||
fieldKey := telemetrytypes.GetFieldKeyFromKeyText(ctx.KEY().GetText())
|
||||
|
||||
keyName := strings.TrimPrefix(fieldKey.Name, telemetrylogs.BodyJSONStringSearchPrefix)
|
||||
|
||||
fieldKeysForName := v.fieldKeys[keyName]
|
||||
|
||||
// for the body json search, we need to add search on the body field even
|
||||
// if there is a field with the same name as attribute/resource attribute
|
||||
// Since it will ORed with the fieldKeysForName, it will not result empty
|
||||
// when either of them have values
|
||||
if strings.HasPrefix(fieldKey.Name, telemetrylogs.BodyJSONStringSearchPrefix) {
|
||||
fieldKeysForName = append(fieldKeysForName, fieldKey)
|
||||
}
|
||||
|
||||
// TODO(srikanthccv): do we want to return an error here?
|
||||
// should we infer the type and auto-magically build a key for expression?
|
||||
if len(fieldKeysForName) == 0 {
|
||||
v.errors = append(v.errors, errors.Newf(
|
||||
errors.TypeInvalidInput,
|
||||
errors.CodeInvalidInput,
|
||||
"key `%s` not found",
|
||||
fieldKey.Name,
|
||||
))
|
||||
}
|
||||
|
||||
if len(fieldKeysForName) > 1 {
|
||||
// this is warning state, we must have a unambiguous key
|
||||
v.warnings = append(v.warnings, errors.Newf(
|
||||
errors.TypeInvalidInput,
|
||||
errors.CodeInvalidInput,
|
||||
"key `%s` is ambiguous, found %d different combinations of field context and data type: %v",
|
||||
fieldKey.Name,
|
||||
len(fieldKeysForName),
|
||||
fieldKeysForName,
|
||||
))
|
||||
}
|
||||
|
||||
return fieldKeysForName
|
||||
}
|
638
pkg/parser/grammar/where_clause_visitor_test.go
Normal file
638
pkg/parser/grammar/where_clause_visitor_test.go
Normal file
@ -0,0 +1,638 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/telemetrylogs"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrytraces"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
|
||||
func TestConvertToClickHouseLogsQuery(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
fieldKeys map[string][]telemetrytypes.TelemetryFieldKey
|
||||
query string
|
||||
expectedSearchString string
|
||||
expectedSearchArgs []any
|
||||
}{
|
||||
{
|
||||
name: "test-simple-service-name-filter",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"service.name": {
|
||||
{
|
||||
Name: "service.name",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "service.name=redis",
|
||||
expectedSearchString: "WHERE (resources_string['service.name'] = ?)",
|
||||
expectedSearchArgs: []any{"redis"},
|
||||
},
|
||||
{
|
||||
name: "test-simple-service-name-filter-with-materialised-column",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"service.name": {
|
||||
{
|
||||
Name: "service.name",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
Materialized: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "service.name=redis",
|
||||
expectedSearchString: "WHERE (resource_string_service$$name = ?)",
|
||||
expectedSearchArgs: []any{"redis"},
|
||||
},
|
||||
{
|
||||
name: "http-status-code-multiple-data-types",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"http.status_code": {
|
||||
{
|
||||
Name: "http.status_code",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeFloat64,
|
||||
},
|
||||
{
|
||||
Name: "http.status_code",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "http.status_code=200",
|
||||
expectedSearchString: "WHERE (attributes_number['http.status_code'] = ? OR toFloat64OrNull(attributes_string['http.status_code']) = ?)",
|
||||
expectedSearchArgs: []any{float64(200), float64(200)},
|
||||
},
|
||||
{
|
||||
name: "http-status-code-multiple-data-types-between-operator",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"http.status_code": {
|
||||
{
|
||||
Name: "http.status_code",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeFloat64,
|
||||
},
|
||||
{
|
||||
Name: "http.status_code",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "http.status_code between 200 and 300",
|
||||
expectedSearchString: "WHERE (attributes_number['http.status_code'] BETWEEN ? AND ? OR toFloat64OrNull(attributes_string['http.status_code']) BETWEEN ? AND ?)",
|
||||
expectedSearchArgs: []any{float64(200), float64(300), float64(200), float64(300)},
|
||||
},
|
||||
{
|
||||
name: "response-body-multiple-data-types-string-contains",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"response.body": {
|
||||
{
|
||||
Name: "response.body",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeFloat64,
|
||||
},
|
||||
{
|
||||
Name: "response.body",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "response.body contains error",
|
||||
expectedSearchString: "WHERE (LOWER(toString(attributes_number['response.body'])) LIKE LOWER(?) OR LOWER(attributes_string['response.body']) LIKE LOWER(?))",
|
||||
expectedSearchArgs: []any{"%error%", "%error%"},
|
||||
},
|
||||
{
|
||||
name: "search-on-top-level-key",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"severity_text": {
|
||||
{
|
||||
Name: "severity_text",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextLog,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "severity_text=error",
|
||||
expectedSearchString: "WHERE (severity_text = ?)",
|
||||
expectedSearchArgs: []any{"error"},
|
||||
},
|
||||
{
|
||||
name: "search-on-top-level-key-conflict-with-attribute",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"severity_text": {
|
||||
{
|
||||
Name: "severity_text",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextLog,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
{
|
||||
Name: "severity_text",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "severity_text=error",
|
||||
expectedSearchString: "WHERE (severity_text = ? OR attributes_string['severity_text'] = ?)",
|
||||
expectedSearchArgs: []any{"error", "error"},
|
||||
},
|
||||
{
|
||||
name: "collision-with-attribute-field-and-resource-attribute",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"k8s.namespace.name": {
|
||||
{
|
||||
Name: "k8s.namespace.name",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
{
|
||||
Name: "k8s.namespace.name",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "k8s.namespace.name=test",
|
||||
expectedSearchString: "WHERE (resources_string['k8s.namespace.name'] = ? OR attributes_string['k8s.namespace.name'] = ?)",
|
||||
expectedSearchArgs: []any{"test", "test"},
|
||||
},
|
||||
{
|
||||
name: "collision-with-attribute-field-and-resource-attribute-materialised-column",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"k8s.namespace.name": {
|
||||
{
|
||||
Name: "k8s.namespace.name",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
Materialized: true,
|
||||
},
|
||||
{
|
||||
Name: "k8s.namespace.name",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "k8s.namespace.name=test",
|
||||
expectedSearchString: "WHERE (resource_string_k8s$$namespace$$name = ? OR attributes_string['k8s.namespace.name'] = ?)",
|
||||
expectedSearchArgs: []any{"test", "test"},
|
||||
},
|
||||
{
|
||||
name: "boolean-collision-with-attribute-field-and-data-type-boolean",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"did_user_login": {
|
||||
{
|
||||
Name: "did_user_login",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeBool,
|
||||
},
|
||||
{
|
||||
Name: "did_user_login",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "did_user_login=true",
|
||||
expectedSearchString: "WHERE (attributes_bool['did_user_login'] = ? OR attributes_string['did_user_login'] = ?)",
|
||||
expectedSearchArgs: []any{true, "true"},
|
||||
},
|
||||
{
|
||||
name: "regexp-search",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"k8s.namespace.name": {
|
||||
{
|
||||
Name: "k8s.namespace.name",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
"service.name": {
|
||||
{
|
||||
Name: "service.name",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "k8s.namespace.name REGEXP 'test' OR service.name='redis'",
|
||||
expectedSearchString: "WHERE (((match(attributes_string['k8s.namespace.name'], ?))) OR (resources_string['service.name'] = ?))",
|
||||
expectedSearchArgs: []any{"test", "redis"},
|
||||
},
|
||||
{
|
||||
name: "full-text-search-multiple-words",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{},
|
||||
query: "waiting for response",
|
||||
expectedSearchString: "WHERE ((match(body, ?)) AND (match(body, ?)) AND (match(body, ?)))",
|
||||
expectedSearchArgs: []any{"waiting", "for", "response"},
|
||||
},
|
||||
{
|
||||
name: "full-text-search-with-phrase-search",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{},
|
||||
query: `"waiting for response"`,
|
||||
expectedSearchString: "WHERE (match(body, ?))",
|
||||
expectedSearchArgs: []any{"waiting for response"},
|
||||
},
|
||||
{
|
||||
name: "full-text-search-with-word-and-not-word",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{},
|
||||
query: "error NOT buggy_app",
|
||||
expectedSearchString: "WHERE ((match(body, ?)) AND NOT ((match(body, ?))))",
|
||||
expectedSearchArgs: []any{"error", "buggy_app"},
|
||||
},
|
||||
{
|
||||
name: "full-text-search-with-word-and-not-word-and-not-word",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{},
|
||||
query: "error NOT buggy_app NOT redis",
|
||||
expectedSearchString: "WHERE ((match(body, ?)) AND NOT ((match(body, ?))) AND NOT ((match(body, ?))))",
|
||||
expectedSearchArgs: []any{"error", "buggy_app", "redis"},
|
||||
},
|
||||
{
|
||||
name: "full-text-search-with-word-and-not-word-and-not-word-tricky",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{},
|
||||
query: "error NOT buggy_app OR redis",
|
||||
expectedSearchString: "WHERE (((match(body, ?)) AND NOT ((match(body, ?)))) OR (match(body, ?)))",
|
||||
expectedSearchArgs: []any{"error", "buggy_app", "redis"},
|
||||
},
|
||||
{
|
||||
name: "has-function",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"service.name": {
|
||||
{
|
||||
Name: "service.name",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
},
|
||||
},
|
||||
"payload.user_ids": {
|
||||
{
|
||||
Name: "payload.user_ids",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "has(service.name, 'redis')",
|
||||
expectedSearchString: "WHERE (has(resources_string['service.name'], ?))",
|
||||
expectedSearchArgs: []any{"redis"},
|
||||
},
|
||||
{
|
||||
name: "has-from-list-of-values",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{},
|
||||
query: "has(body.payload.user_ids[*], 'u1292')",
|
||||
expectedSearchString: "WHERE (has(JSONExtract(JSON_QUERY(body, '$.payload.user_ids[*]'), 'Array(String)'), ?))",
|
||||
expectedSearchArgs: []any{"u1292"},
|
||||
},
|
||||
{
|
||||
name: "body-json-search-that-also-has-attribute-with-same-name",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"http.status_code": {
|
||||
{
|
||||
Name: "http.status_code",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeFloat64,
|
||||
Materialized: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "body.http.status_code=200",
|
||||
expectedSearchString: "WHERE (attribute_number_http$$status_code = ? OR JSONExtract(JSON_VALUE(body, '$.http.status_code'), 'Float64') = ?)",
|
||||
expectedSearchArgs: []any{float64(200), float64(200)},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Logf("running test %s", c.name)
|
||||
chQuery, chQueryArgs, _, err := PrepareWhereClause(c.query, c.fieldKeys, telemetrylogs.NewConditionBuilder(), telemetrytypes.TelemetryFieldKey{
|
||||
Name: "body",
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
FieldContext: telemetrytypes.FieldContextLog,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Error converting query to ClickHouse: %v", err)
|
||||
}
|
||||
if chQuery != c.expectedSearchString {
|
||||
t.Errorf("Expected %s, got %s", c.expectedSearchString, chQuery)
|
||||
}
|
||||
if !reflect.DeepEqual(chQueryArgs, c.expectedSearchArgs) {
|
||||
for i, arg := range chQueryArgs {
|
||||
t.Logf("Expected %v with type %T, got %v with type %T\n", c.expectedSearchArgs[i], c.expectedSearchArgs[i], arg, arg)
|
||||
}
|
||||
t.Errorf("Expected %v, got %v", c.expectedSearchArgs, chQueryArgs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertToClickHouseSpansQuery(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
fieldKeys map[string][]telemetrytypes.TelemetryFieldKey
|
||||
query string
|
||||
expectedSearchString string
|
||||
expectedSearchArgs []any
|
||||
}{
|
||||
{
|
||||
name: "test-simple-service-name-filter",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"service.name": {
|
||||
{
|
||||
Name: "service.name",
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "service.name=redis",
|
||||
expectedSearchString: "WHERE (resources_string['service.name'] = ?)",
|
||||
expectedSearchArgs: []any{"redis"},
|
||||
},
|
||||
{
|
||||
name: "test-simple-service-name-filter-with-materialised-column",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"service.name": {
|
||||
{
|
||||
Name: "service.name",
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
Materialized: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "service.name=redis",
|
||||
expectedSearchString: "WHERE (resource_string_service$$name = ?)",
|
||||
expectedSearchArgs: []any{"redis"},
|
||||
},
|
||||
{
|
||||
name: "http-status-code-multiple-data-types",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"http.status_code": {
|
||||
{
|
||||
Name: "http.status_code",
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeFloat64,
|
||||
},
|
||||
{
|
||||
Name: "http.status_code",
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "http.status_code=200",
|
||||
expectedSearchString: "WHERE (attributes_number['http.status_code'] = ? OR toFloat64OrNull(attributes_string['http.status_code']) = ?)",
|
||||
expectedSearchArgs: []any{float64(200), float64(200)},
|
||||
},
|
||||
{
|
||||
name: "http-status-code-multiple-data-types-between-operator",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"http.status_code": {
|
||||
{
|
||||
Name: "http.status_code",
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeFloat64,
|
||||
},
|
||||
{
|
||||
Name: "http.status_code",
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "http.status_code between 200 and 300",
|
||||
expectedSearchString: "WHERE (attributes_number['http.status_code'] BETWEEN ? AND ? OR toFloat64OrNull(attributes_string['http.status_code']) BETWEEN ? AND ?)",
|
||||
expectedSearchArgs: []any{float64(200), float64(300), float64(200), float64(300)},
|
||||
},
|
||||
{
|
||||
name: "response-body-multiple-data-types-string-contains",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"response.body": {
|
||||
{
|
||||
Name: "response.body",
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeFloat64,
|
||||
},
|
||||
{
|
||||
Name: "response.body",
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "response.body contains error",
|
||||
expectedSearchString: "WHERE (LOWER(toString(attributes_number['response.body'])) LIKE LOWER(?) OR LOWER(attributes_string['response.body']) LIKE LOWER(?))",
|
||||
expectedSearchArgs: []any{"%error%", "%error%"},
|
||||
},
|
||||
{
|
||||
name: "collision-with-attribute-field-and-resource-attribute",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"k8s.namespace.name": {
|
||||
{
|
||||
Name: "k8s.namespace.name",
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
{
|
||||
Name: "k8s.namespace.name",
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "k8s.namespace.name=test",
|
||||
expectedSearchString: "WHERE (resources_string['k8s.namespace.name'] = ? OR attributes_string['k8s.namespace.name'] = ?)",
|
||||
expectedSearchArgs: []any{"test", "test"},
|
||||
},
|
||||
{
|
||||
name: "collision-with-attribute-field-and-resource-attribute-materialised-column",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"k8s.namespace.name": {
|
||||
{
|
||||
Name: "k8s.namespace.name",
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
Materialized: true,
|
||||
},
|
||||
{
|
||||
Name: "k8s.namespace.name",
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "k8s.namespace.name=test",
|
||||
expectedSearchString: "WHERE (resource_string_k8s$$namespace$$name = ? OR attributes_string['k8s.namespace.name'] = ?)",
|
||||
expectedSearchArgs: []any{"test", "test"},
|
||||
},
|
||||
{
|
||||
name: "boolean-collision-with-attribute-field-and-data-type-boolean",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"did_user_login": {
|
||||
{
|
||||
Name: "did_user_login",
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeBool,
|
||||
},
|
||||
{
|
||||
Name: "did_user_login",
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "did_user_login=true",
|
||||
expectedSearchString: "WHERE (attributes_bool['did_user_login'] = ? OR attributes_string['did_user_login'] = ?)",
|
||||
expectedSearchArgs: []any{true, "true"},
|
||||
},
|
||||
{
|
||||
name: "regexp-search",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{
|
||||
"k8s.namespace.name": {
|
||||
{
|
||||
Name: "k8s.namespace.name",
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
FieldContext: telemetrytypes.FieldContextAttribute,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
"service.name": {
|
||||
{
|
||||
Name: "service.name",
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
},
|
||||
query: "k8s.namespace.name REGEXP 'test' OR service.name='redis'",
|
||||
expectedSearchString: "WHERE (((match(attributes_string['k8s.namespace.name'], ?))) OR (resources_string['service.name'] = ?))",
|
||||
expectedSearchArgs: []any{"test", "redis"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
chQuery, chQueryArgs, _, err := PrepareWhereClause(c.query, c.fieldKeys, telemetrytraces.NewConditionBuilder(), telemetrytypes.TelemetryFieldKey{
|
||||
Name: "dummy",
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
FieldContext: telemetrytypes.FieldContextSpan,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Error converting query to ClickHouse: %v", err)
|
||||
}
|
||||
if chQuery != c.expectedSearchString {
|
||||
t.Errorf("Expected %s, got %s", c.expectedSearchString, chQuery)
|
||||
}
|
||||
if !reflect.DeepEqual(chQueryArgs, c.expectedSearchArgs) {
|
||||
for i, arg := range chQueryArgs {
|
||||
t.Logf("Expected %v with type %T, got %v with type %T\n", c.expectedSearchArgs[i], c.expectedSearchArgs[i], arg, arg)
|
||||
}
|
||||
t.Errorf("Expected %v, got %v", c.expectedSearchArgs, chQueryArgs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertToClickHouseSpansQueryWithErrors(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
fieldKeys map[string][]telemetrytypes.TelemetryFieldKey
|
||||
query string
|
||||
expectedSearchString string
|
||||
expectedSearchArgs []any
|
||||
expectedErrorSubString string
|
||||
expectedWarnings []error
|
||||
}{
|
||||
{
|
||||
name: "has-function-with-multiple-values",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{},
|
||||
query: "key.that.does.not.exist = 'redis'",
|
||||
expectedSearchString: "",
|
||||
expectedSearchArgs: []any{},
|
||||
expectedErrorSubString: "key `key.that.does.not.exist` not found",
|
||||
expectedWarnings: []error{},
|
||||
},
|
||||
{
|
||||
name: "unknown-function",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{},
|
||||
query: "unknown.function()",
|
||||
expectedSearchString: "",
|
||||
expectedSearchArgs: []any{},
|
||||
expectedErrorSubString: "expecting {'(', NOT, HAS, HASANY, HASALL, QUOTED_TEXT, KEY, FREETEXT}",
|
||||
expectedWarnings: []error{},
|
||||
},
|
||||
{
|
||||
name: "has-function-not-enough-params",
|
||||
fieldKeys: map[string][]telemetrytypes.TelemetryFieldKey{},
|
||||
query: "has(key.that.does.not.exist)",
|
||||
expectedSearchString: "",
|
||||
expectedSearchArgs: []any{},
|
||||
expectedErrorSubString: "function `has` expects key and value parameters",
|
||||
expectedWarnings: []error{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
_, _, warnings, err := PrepareWhereClause(c.query, c.fieldKeys, telemetrytraces.NewConditionBuilder(), telemetrytypes.TelemetryFieldKey{
|
||||
Name: "dummy",
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
FieldContext: telemetrytypes.FieldContextSpan,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
})
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), c.expectedErrorSubString) {
|
||||
t.Errorf("Expected error %v, got %v", c.expectedErrorSubString, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(warnings) != len(c.expectedWarnings) {
|
||||
t.Errorf("Expected %d warnings, got %d", len(c.expectedWarnings), len(warnings))
|
||||
}
|
||||
for i, warning := range warnings {
|
||||
if warning.Error() != c.expectedWarnings[i].Error() {
|
||||
t.Errorf("Expected warning %d to be %v, got %v", i, c.expectedWarnings[i], warning)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,10 +3,13 @@ package telemetrylogs
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
schema "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
)
|
||||
@ -81,9 +84,13 @@ func (c *conditionBuilder) GetColumn(ctx context.Context, key *telemetrytypes.Te
|
||||
case telemetrytypes.FieldDataTypeBool:
|
||||
return logsV2Columns["attributes_bool"], nil
|
||||
}
|
||||
case telemetrytypes.FieldContextLog:
|
||||
case telemetrytypes.FieldContextLog, telemetrytypes.FieldContextUnspecified:
|
||||
col, ok := logsV2Columns[key.Name]
|
||||
if !ok {
|
||||
// check if the key has body JSON search
|
||||
if strings.HasPrefix(key.Name, BodyJSONStringSearchPrefix) {
|
||||
return logsV2Columns["body"], nil
|
||||
}
|
||||
return nil, qbtypes.ErrColumnNotFound
|
||||
}
|
||||
return col, nil
|
||||
@ -137,6 +144,83 @@ func (c *conditionBuilder) GetTableFieldName(ctx context.Context, key *telemetry
|
||||
return column.Name, nil
|
||||
}
|
||||
|
||||
func parseStrValue(valueStr string, operator qbtypes.FilterOperator) (telemetrytypes.FieldDataType, any) {
|
||||
|
||||
valueType := telemetrytypes.FieldDataTypeString
|
||||
|
||||
// return the value as is for the following operators
|
||||
// as they are always string
|
||||
if operator == qbtypes.FilterOperatorContains || operator == qbtypes.FilterOperatorNotContains ||
|
||||
operator == qbtypes.FilterOperatorRegexp || operator == qbtypes.FilterOperatorNotRegexp ||
|
||||
operator == qbtypes.FilterOperatorLike || operator == qbtypes.FilterOperatorNotLike ||
|
||||
operator == qbtypes.FilterOperatorILike || operator == qbtypes.FilterOperatorNotILike {
|
||||
return valueType, valueStr
|
||||
}
|
||||
|
||||
var err error
|
||||
var parsedValue any
|
||||
if parsedValue, err = strconv.ParseBool(valueStr); err == nil {
|
||||
valueType = telemetrytypes.FieldDataTypeBool
|
||||
} else if parsedValue, err = strconv.ParseInt(valueStr, 10, 64); err == nil {
|
||||
valueType = telemetrytypes.FieldDataTypeInt64
|
||||
} else if parsedValue, err = strconv.ParseFloat(valueStr, 64); err == nil {
|
||||
valueType = telemetrytypes.FieldDataTypeFloat64
|
||||
} else {
|
||||
parsedValue = valueStr
|
||||
valueType = telemetrytypes.FieldDataTypeString
|
||||
}
|
||||
|
||||
return valueType, parsedValue
|
||||
}
|
||||
|
||||
func inferDataType(value any, operator qbtypes.FilterOperator, key *telemetrytypes.TelemetryFieldKey) (telemetrytypes.FieldDataType, any) {
|
||||
// check if the value is a int, float, string, bool
|
||||
valueType := telemetrytypes.FieldDataTypeUnspecified
|
||||
switch v := value.(type) {
|
||||
case []any:
|
||||
// take the first element and infer the type
|
||||
if len(v) > 0 {
|
||||
valueType, _ = inferDataType(v[0], operator, key)
|
||||
}
|
||||
return valueType, v
|
||||
case uint8, uint16, uint32, uint64, int, int8, int16, int32, int64:
|
||||
valueType = telemetrytypes.FieldDataTypeInt64
|
||||
case float32, float64:
|
||||
valueType = telemetrytypes.FieldDataTypeFloat64
|
||||
case string:
|
||||
valueType, value = parseStrValue(v, operator)
|
||||
case bool:
|
||||
valueType = telemetrytypes.FieldDataTypeBool
|
||||
}
|
||||
|
||||
// check if it is array
|
||||
if strings.HasSuffix(key.Name, "[*]") {
|
||||
valueType = telemetrytypes.FieldDataType{String: valuer.NewString(fmt.Sprintf("[]%s", valueType.StringValue()))}
|
||||
}
|
||||
|
||||
return valueType, value
|
||||
}
|
||||
|
||||
func GetBodyJSONKey(_ context.Context, key *telemetrytypes.TelemetryFieldKey, operator qbtypes.FilterOperator, value any) (string, any) {
|
||||
|
||||
dataType, value := inferDataType(value, operator, key)
|
||||
|
||||
// all body json keys are of the form body.
|
||||
path := strings.Join(strings.Split(key.Name, ".")[1:], ".")
|
||||
|
||||
// for array types, we need to extract the value from the JSON_QUERY
|
||||
if dataType == telemetrytypes.FieldDataTypeArrayInt64 ||
|
||||
dataType == telemetrytypes.FieldDataTypeArrayFloat64 ||
|
||||
dataType == telemetrytypes.FieldDataTypeArrayString ||
|
||||
dataType == telemetrytypes.FieldDataTypeArrayBool ||
|
||||
dataType == telemetrytypes.FieldDataTypeArrayNumber {
|
||||
return fmt.Sprintf("JSONExtract(JSON_QUERY(body, '$.%s'), '%s')", path, dataType.CHDataType()), value
|
||||
}
|
||||
|
||||
// for all other types, we need to extract the value from the JSON_VALUE
|
||||
return fmt.Sprintf("JSONExtract(JSON_VALUE(body, '$.%s'), '%s')", path, dataType.CHDataType()), value
|
||||
}
|
||||
|
||||
func (c *conditionBuilder) GetCondition(
|
||||
ctx context.Context,
|
||||
key *telemetrytypes.TelemetryFieldKey,
|
||||
@ -154,6 +238,10 @@ func (c *conditionBuilder) GetCondition(
|
||||
return "", err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(key.Name, BodyJSONStringSearchPrefix) {
|
||||
tblFieldName, value = GetBodyJSONKey(ctx, key, operator, value)
|
||||
}
|
||||
|
||||
tblFieldName, value = telemetrytypes.DataTypeCollisionHandledFieldName(key, value, tblFieldName)
|
||||
|
||||
// regular operators
|
||||
|
@ -618,3 +618,194 @@ func TestGetConditionMultiple(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetConditionJSONBodySearch(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conditionBuilder := NewConditionBuilder()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
key telemetrytypes.TelemetryFieldKey
|
||||
operator qbtypes.FilterOperator
|
||||
value any
|
||||
expectedSQL string
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "Equal operator - int64",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "body.http.status_code",
|
||||
},
|
||||
operator: qbtypes.FilterOperatorEqual,
|
||||
value: 200,
|
||||
expectedSQL: "JSONExtract(JSON_VALUE(body, '$.http.status_code'), 'Int64') = ?",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Equal operator - float64",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "body.duration_ms",
|
||||
},
|
||||
operator: qbtypes.FilterOperatorEqual,
|
||||
value: 405.5,
|
||||
expectedSQL: "JSONExtract(JSON_VALUE(body, '$.duration_ms'), 'Float64') = ?",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Equal operator - string",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "body.http.method",
|
||||
},
|
||||
operator: qbtypes.FilterOperatorEqual,
|
||||
value: "GET",
|
||||
expectedSQL: "JSONExtract(JSON_VALUE(body, '$.http.method'), 'String') = ?",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Equal operator - bool",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "body.http.success",
|
||||
},
|
||||
operator: qbtypes.FilterOperatorEqual,
|
||||
value: true,
|
||||
expectedSQL: "JSONExtract(JSON_VALUE(body, '$.http.success'), 'Bool') = ?",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Exists operator",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "body.http.status_code",
|
||||
},
|
||||
operator: qbtypes.FilterOperatorExists,
|
||||
value: nil,
|
||||
expectedSQL: "JSONExtract(JSON_VALUE(body, '$.http.status_code'), 'String') <> ?",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Not Exists operator",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "body.http.status_code",
|
||||
},
|
||||
operator: qbtypes.FilterOperatorNotExists,
|
||||
value: nil,
|
||||
expectedSQL: "JSONExtract(JSON_VALUE(body, '$.http.status_code'), 'String') = ?",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Greater than operator - string",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "body.http.status_code",
|
||||
},
|
||||
operator: qbtypes.FilterOperatorGreaterThan,
|
||||
value: "200",
|
||||
expectedSQL: "JSONExtract(JSON_VALUE(body, '$.http.status_code'), 'Int64') > ?",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Greater than operator - int64",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "body.http.status_code",
|
||||
},
|
||||
operator: qbtypes.FilterOperatorGreaterThan,
|
||||
value: 200,
|
||||
expectedSQL: "JSONExtract(JSON_VALUE(body, '$.http.status_code'), 'Int64') > ?",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Less than operator - string",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "body.http.status_code",
|
||||
},
|
||||
operator: qbtypes.FilterOperatorLessThan,
|
||||
value: "300",
|
||||
expectedSQL: "JSONExtract(JSON_VALUE(body, '$.http.status_code'), 'Int64') < ?",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Less than operator - int64",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "body.http.status_code",
|
||||
},
|
||||
operator: qbtypes.FilterOperatorLessThan,
|
||||
value: 300,
|
||||
expectedSQL: "JSONExtract(JSON_VALUE(body, '$.http.status_code'), 'Int64') < ?",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Contains operator - string",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "body.http.status_code",
|
||||
},
|
||||
operator: qbtypes.FilterOperatorContains,
|
||||
value: "200",
|
||||
expectedSQL: "LOWER(JSONExtract(JSON_VALUE(body, '$.http.status_code'), 'String')) LIKE LOWER(?)",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Not Contains operator - string",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "body.http.status_code",
|
||||
},
|
||||
operator: qbtypes.FilterOperatorNotContains,
|
||||
value: "200",
|
||||
expectedSQL: "LOWER(JSONExtract(JSON_VALUE(body, '$.http.status_code'), 'String')) NOT LIKE LOWER(?)",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Between operator - string",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "body.http.status_code",
|
||||
},
|
||||
operator: qbtypes.FilterOperatorBetween,
|
||||
value: []any{"200", "300"},
|
||||
expectedSQL: "JSONExtract(JSON_VALUE(body, '$.http.status_code'), 'Int64') BETWEEN ? AND ?",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Between operator - int64",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "body.http.status_code",
|
||||
},
|
||||
operator: qbtypes.FilterOperatorBetween,
|
||||
value: []any{400, 500},
|
||||
expectedSQL: "JSONExtract(JSON_VALUE(body, '$.http.status_code'), 'Int64') BETWEEN ? AND ?",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "In operator - string",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "body.http.status_code",
|
||||
},
|
||||
operator: qbtypes.FilterOperatorIn,
|
||||
value: []any{"200", "300"},
|
||||
expectedSQL: "JSONExtract(JSON_VALUE(body, '$.http.status_code'), 'Int64') IN (?, ?)",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "In operator - int64",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "body.http.status_code",
|
||||
},
|
||||
operator: qbtypes.FilterOperatorIn,
|
||||
value: []any{401, 404, 500},
|
||||
expectedSQL: "JSONExtract(JSON_VALUE(body, '$.http.status_code'), 'Int64') IN (?, ?, ?)",
|
||||
expectedError: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cond, err := conditionBuilder.GetCondition(ctx, &tc.key, tc.operator, tc.value, sb)
|
||||
sb.Where(cond)
|
||||
|
||||
if tc.expectedError != nil {
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
sql, _ := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
|
||||
assert.Contains(t, sql, tc.expectedSQL)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
5
pkg/telemetrylogs/const.go
Normal file
5
pkg/telemetrylogs/const.go
Normal file
@ -0,0 +1,5 @@
|
||||
package telemetrylogs
|
||||
|
||||
var (
|
||||
BodyJSONStringSearchPrefix = `body.`
|
||||
)
|
@ -27,6 +27,18 @@ type TelemetryFieldKey struct {
|
||||
Materialized bool `json:"materialized,omitempty"`
|
||||
}
|
||||
|
||||
func (f TelemetryFieldKey) String() string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(fmt.Sprintf("name=%s", f.Name))
|
||||
if f.FieldContext != FieldContextUnspecified {
|
||||
sb.WriteString(fmt.Sprintf(",context=%s", f.FieldContext.String))
|
||||
}
|
||||
if f.FieldDataType != FieldDataTypeUnspecified {
|
||||
sb.WriteString(fmt.Sprintf(",type=%s", f.FieldDataType.StringValue()))
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// GetFieldKeyFromKeyText returns a TelemetryFieldKey from a key text.
|
||||
// The key text is expected to be in the format of `fieldContext.fieldName:fieldDataType` in the search query.
|
||||
func GetFieldKeyFromKeyText(key string) TelemetryFieldKey {
|
||||
@ -87,11 +99,11 @@ func GetFieldKeyFromKeyText(key string) TelemetryFieldKey {
|
||||
}
|
||||
|
||||
func FieldKeyToMaterializedColumnName(key *TelemetryFieldKey) string {
|
||||
return fmt.Sprintf("%s_%s_%s", key.FieldContext, key.FieldDataType.String, strings.ReplaceAll(key.Name, ".", "$$"))
|
||||
return fmt.Sprintf("%s_%s_%s", key.FieldContext.String, fieldDataTypes[key.FieldDataType.StringValue()].StringValue(), strings.ReplaceAll(key.Name, ".", "$$"))
|
||||
}
|
||||
|
||||
func FieldKeyToMaterializedColumnNameForExists(key *TelemetryFieldKey) string {
|
||||
return fmt.Sprintf("%s_%s_%s_exists", key.FieldContext, key.FieldDataType.String, strings.ReplaceAll(key.Name, ".", "$$"))
|
||||
return fmt.Sprintf("%s_%s_%s_exists", key.FieldContext.String, fieldDataTypes[key.FieldDataType.StringValue()].StringValue(), strings.ReplaceAll(key.Name, ".", "$$"))
|
||||
}
|
||||
|
||||
type TelemetryFieldValues struct {
|
||||
|
@ -23,6 +23,14 @@ var (
|
||||
FieldDataTypeNumber = FieldDataType{valuer.NewString("number")}
|
||||
FieldDataTypeUnspecified = FieldDataType{valuer.NewString("")}
|
||||
|
||||
FieldDataTypeArrayString = FieldDataType{valuer.NewString("[]string")}
|
||||
FieldDataTypeArrayFloat64 = FieldDataType{valuer.NewString("[]float64")}
|
||||
FieldDataTypeArrayBool = FieldDataType{valuer.NewString("[]bool")}
|
||||
|
||||
// int64 and number are synonyms for float64
|
||||
FieldDataTypeArrayInt64 = FieldDataType{valuer.NewString("[]int64")}
|
||||
FieldDataTypeArrayNumber = FieldDataType{valuer.NewString("[]number")}
|
||||
|
||||
// Map string representations to FieldDataType values
|
||||
// We want to handle all the possible string representations of the data types.
|
||||
// Even if the user uses some non-standard representation, we want to be able to
|
||||
@ -53,9 +61,43 @@ var (
|
||||
"double": FieldDataTypeNumber,
|
||||
"decimal": FieldDataTypeNumber,
|
||||
"number": FieldDataTypeNumber,
|
||||
|
||||
// Array types
|
||||
"[]string": FieldDataTypeArrayString,
|
||||
"[]int64": FieldDataTypeArrayInt64,
|
||||
"[]float64": FieldDataTypeArrayFloat64,
|
||||
"[]number": FieldDataTypeArrayNumber,
|
||||
"[]bool": FieldDataTypeArrayBool,
|
||||
|
||||
// c-style array types
|
||||
"string[]": FieldDataTypeArrayString,
|
||||
"int64[]": FieldDataTypeArrayInt64,
|
||||
"float64[]": FieldDataTypeArrayFloat64,
|
||||
"number[]": FieldDataTypeArrayNumber,
|
||||
"bool[]": FieldDataTypeArrayBool,
|
||||
}
|
||||
|
||||
fieldDataTypeToCHDataType = map[FieldDataType]string{
|
||||
FieldDataTypeString: "String",
|
||||
FieldDataTypeBool: "Bool",
|
||||
FieldDataTypeNumber: "Float64",
|
||||
FieldDataTypeInt64: "Int64",
|
||||
FieldDataTypeFloat64: "Float64",
|
||||
|
||||
FieldDataTypeArrayString: "Array(String)",
|
||||
FieldDataTypeArrayInt64: "Array(Int64)",
|
||||
FieldDataTypeArrayFloat64: "Array(Float64)",
|
||||
FieldDataTypeArrayBool: "Array(Bool)",
|
||||
}
|
||||
)
|
||||
|
||||
func (f FieldDataType) CHDataType() string {
|
||||
if chDataType, ok := fieldDataTypeToCHDataType[f]; ok {
|
||||
return chDataType
|
||||
}
|
||||
return "String"
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface
|
||||
func (f *FieldDataType) UnmarshalJSON(data []byte) error {
|
||||
var str string
|
||||
|
Loading…
x
Reference in New Issue
Block a user