mirror of
https://github.com/PostHog/posthog.git
synced 2024-11-21 13:39:22 +01:00
feat: add "INTERSECT" and "EXCEPT" to HogQL (#25737)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
parent
e6877d3436
commit
447a18930f
File diff suppressed because it is too large
Load Diff
@ -16,32 +16,33 @@ public:
|
|||||||
ASOF = 8, BETWEEN = 9, BOTH = 10, BY = 11, CASE = 12, CAST = 13, CATCH = 14,
|
ASOF = 8, BETWEEN = 9, BOTH = 10, BY = 11, CASE = 12, CAST = 13, CATCH = 14,
|
||||||
COHORT = 15, COLLATE = 16, CROSS = 17, CUBE = 18, CURRENT = 19, DATE = 20,
|
COHORT = 15, COLLATE = 16, CROSS = 17, CUBE = 18, CURRENT = 19, DATE = 20,
|
||||||
DAY = 21, DESC = 22, DESCENDING = 23, DISTINCT = 24, ELSE = 25, END = 26,
|
DAY = 21, DESC = 22, DESCENDING = 23, DISTINCT = 24, ELSE = 25, END = 26,
|
||||||
EXTRACT = 27, FINAL = 28, FINALLY = 29, FIRST = 30, FN = 31, FOLLOWING = 32,
|
EXCEPT = 27, EXTRACT = 28, FINAL = 29, FINALLY = 30, FIRST = 31, FN = 32,
|
||||||
FOR = 33, FROM = 34, FULL = 35, FUN = 36, GROUP = 37, HAVING = 38, HOUR = 39,
|
FOLLOWING = 33, FOR = 34, FROM = 35, FULL = 36, FUN = 37, GROUP = 38,
|
||||||
ID = 40, IF = 41, ILIKE = 42, IN = 43, INF = 44, INNER = 45, INTERVAL = 46,
|
HAVING = 39, HOUR = 40, ID = 41, IF = 42, ILIKE = 43, IN = 44, INF = 45,
|
||||||
IS = 47, JOIN = 48, KEY = 49, LAST = 50, LEADING = 51, LEFT = 52, LET = 53,
|
INNER = 46, INTERSECT = 47, INTERVAL = 48, IS = 49, JOIN = 50, KEY = 51,
|
||||||
LIKE = 54, LIMIT = 55, MINUTE = 56, MONTH = 57, NAN_SQL = 58, NOT = 59,
|
LAST = 52, LEADING = 53, LEFT = 54, LET = 55, LIKE = 56, LIMIT = 57,
|
||||||
NULL_SQL = 60, NULLS = 61, OFFSET = 62, ON = 63, OR = 64, ORDER = 65,
|
MINUTE = 58, MONTH = 59, NAN_SQL = 60, NOT = 61, NULL_SQL = 62, NULLS = 63,
|
||||||
OUTER = 66, OVER = 67, PARTITION = 68, PRECEDING = 69, PREWHERE = 70,
|
OFFSET = 64, ON = 65, OR = 66, ORDER = 67, OUTER = 68, OVER = 69, PARTITION = 70,
|
||||||
QUARTER = 71, RANGE = 72, RETURN = 73, RIGHT = 74, ROLLUP = 75, ROW = 76,
|
PRECEDING = 71, PREWHERE = 72, QUARTER = 73, RANGE = 74, RETURN = 75,
|
||||||
ROWS = 77, SAMPLE = 78, SECOND = 79, SELECT = 80, SEMI = 81, SETTINGS = 82,
|
RIGHT = 76, ROLLUP = 77, ROW = 78, ROWS = 79, SAMPLE = 80, SECOND = 81,
|
||||||
SUBSTRING = 83, THEN = 84, THROW = 85, TIES = 86, TIMESTAMP = 87, TO = 88,
|
SELECT = 82, SEMI = 83, SETTINGS = 84, SUBSTRING = 85, THEN = 86, THROW = 87,
|
||||||
TOP = 89, TOTALS = 90, TRAILING = 91, TRIM = 92, TRUNCATE = 93, TRY = 94,
|
TIES = 88, TIMESTAMP = 89, TO = 90, TOP = 91, TOTALS = 92, TRAILING = 93,
|
||||||
UNBOUNDED = 95, UNION = 96, USING = 97, WEEK = 98, WHEN = 99, WHERE = 100,
|
TRIM = 94, TRUNCATE = 95, TRY = 96, UNBOUNDED = 97, UNION = 98, USING = 99,
|
||||||
WHILE = 101, WINDOW = 102, WITH = 103, YEAR = 104, ESCAPE_CHAR_COMMON = 105,
|
WEEK = 100, WHEN = 101, WHERE = 102, WHILE = 103, WINDOW = 104, WITH = 105,
|
||||||
IDENTIFIER = 106, FLOATING_LITERAL = 107, OCTAL_LITERAL = 108, DECIMAL_LITERAL = 109,
|
YEAR = 106, ESCAPE_CHAR_COMMON = 107, IDENTIFIER = 108, FLOATING_LITERAL = 109,
|
||||||
HEXADECIMAL_LITERAL = 110, STRING_LITERAL = 111, ARROW = 112, ASTERISK = 113,
|
OCTAL_LITERAL = 110, DECIMAL_LITERAL = 111, HEXADECIMAL_LITERAL = 112,
|
||||||
BACKQUOTE = 114, BACKSLASH = 115, COLON = 116, COMMA = 117, CONCAT = 118,
|
STRING_LITERAL = 113, ARROW = 114, ASTERISK = 115, BACKQUOTE = 116,
|
||||||
DASH = 119, DOLLAR = 120, DOT = 121, EQ_DOUBLE = 122, EQ_SINGLE = 123,
|
BACKSLASH = 117, COLON = 118, COMMA = 119, CONCAT = 120, DASH = 121,
|
||||||
GT_EQ = 124, GT = 125, HASH = 126, IREGEX_SINGLE = 127, IREGEX_DOUBLE = 128,
|
DOLLAR = 122, DOT = 123, EQ_DOUBLE = 124, EQ_SINGLE = 125, GT_EQ = 126,
|
||||||
LBRACE = 129, LBRACKET = 130, LPAREN = 131, LT_EQ = 132, LT = 133, NOT_EQ = 134,
|
GT = 127, HASH = 128, IREGEX_SINGLE = 129, IREGEX_DOUBLE = 130, LBRACE = 131,
|
||||||
NOT_IREGEX = 135, NOT_REGEX = 136, NULL_PROPERTY = 137, NULLISH = 138,
|
LBRACKET = 132, LPAREN = 133, LT_EQ = 134, LT = 135, NOT_EQ = 136, NOT_IREGEX = 137,
|
||||||
PERCENT = 139, PLUS = 140, QUERY = 141, QUOTE_DOUBLE = 142, QUOTE_SINGLE_TEMPLATE = 143,
|
NOT_REGEX = 138, NULL_PROPERTY = 139, NULLISH = 140, PERCENT = 141,
|
||||||
QUOTE_SINGLE_TEMPLATE_FULL = 144, QUOTE_SINGLE = 145, REGEX_SINGLE = 146,
|
PLUS = 142, QUERY = 143, QUOTE_DOUBLE = 144, QUOTE_SINGLE_TEMPLATE = 145,
|
||||||
REGEX_DOUBLE = 147, RBRACE = 148, RBRACKET = 149, RPAREN = 150, SEMICOLON = 151,
|
QUOTE_SINGLE_TEMPLATE_FULL = 146, QUOTE_SINGLE = 147, REGEX_SINGLE = 148,
|
||||||
SLASH = 152, UNDERSCORE = 153, MULTI_LINE_COMMENT = 154, SINGLE_LINE_COMMENT = 155,
|
REGEX_DOUBLE = 149, RBRACE = 150, RBRACKET = 151, RPAREN = 152, SEMICOLON = 153,
|
||||||
WHITESPACE = 156, STRING_TEXT = 157, STRING_ESCAPE_TRIGGER = 158, FULL_STRING_TEXT = 159,
|
SLASH = 154, UNDERSCORE = 155, MULTI_LINE_COMMENT = 156, SINGLE_LINE_COMMENT = 157,
|
||||||
FULL_STRING_ESCAPE_TRIGGER = 160
|
WHITESPACE = 158, STRING_TEXT = 159, STRING_ESCAPE_TRIGGER = 160, FULL_STRING_TEXT = 161,
|
||||||
|
FULL_STRING_ESCAPE_TRIGGER = 162
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
|
File diff suppressed because one or more lines are too long
@ -24,178 +24,180 @@ DESCENDING=23
|
|||||||
DISTINCT=24
|
DISTINCT=24
|
||||||
ELSE=25
|
ELSE=25
|
||||||
END=26
|
END=26
|
||||||
EXTRACT=27
|
EXCEPT=27
|
||||||
FINAL=28
|
EXTRACT=28
|
||||||
FINALLY=29
|
FINAL=29
|
||||||
FIRST=30
|
FINALLY=30
|
||||||
FN=31
|
FIRST=31
|
||||||
FOLLOWING=32
|
FN=32
|
||||||
FOR=33
|
FOLLOWING=33
|
||||||
FROM=34
|
FOR=34
|
||||||
FULL=35
|
FROM=35
|
||||||
FUN=36
|
FULL=36
|
||||||
GROUP=37
|
FUN=37
|
||||||
HAVING=38
|
GROUP=38
|
||||||
HOUR=39
|
HAVING=39
|
||||||
ID=40
|
HOUR=40
|
||||||
IF=41
|
ID=41
|
||||||
ILIKE=42
|
IF=42
|
||||||
IN=43
|
ILIKE=43
|
||||||
INF=44
|
IN=44
|
||||||
INNER=45
|
INF=45
|
||||||
INTERVAL=46
|
INNER=46
|
||||||
IS=47
|
INTERSECT=47
|
||||||
JOIN=48
|
INTERVAL=48
|
||||||
KEY=49
|
IS=49
|
||||||
LAST=50
|
JOIN=50
|
||||||
LEADING=51
|
KEY=51
|
||||||
LEFT=52
|
LAST=52
|
||||||
LET=53
|
LEADING=53
|
||||||
LIKE=54
|
LEFT=54
|
||||||
LIMIT=55
|
LET=55
|
||||||
MINUTE=56
|
LIKE=56
|
||||||
MONTH=57
|
LIMIT=57
|
||||||
NAN_SQL=58
|
MINUTE=58
|
||||||
NOT=59
|
MONTH=59
|
||||||
NULL_SQL=60
|
NAN_SQL=60
|
||||||
NULLS=61
|
NOT=61
|
||||||
OFFSET=62
|
NULL_SQL=62
|
||||||
ON=63
|
NULLS=63
|
||||||
OR=64
|
OFFSET=64
|
||||||
ORDER=65
|
ON=65
|
||||||
OUTER=66
|
OR=66
|
||||||
OVER=67
|
ORDER=67
|
||||||
PARTITION=68
|
OUTER=68
|
||||||
PRECEDING=69
|
OVER=69
|
||||||
PREWHERE=70
|
PARTITION=70
|
||||||
QUARTER=71
|
PRECEDING=71
|
||||||
RANGE=72
|
PREWHERE=72
|
||||||
RETURN=73
|
QUARTER=73
|
||||||
RIGHT=74
|
RANGE=74
|
||||||
ROLLUP=75
|
RETURN=75
|
||||||
ROW=76
|
RIGHT=76
|
||||||
ROWS=77
|
ROLLUP=77
|
||||||
SAMPLE=78
|
ROW=78
|
||||||
SECOND=79
|
ROWS=79
|
||||||
SELECT=80
|
SAMPLE=80
|
||||||
SEMI=81
|
SECOND=81
|
||||||
SETTINGS=82
|
SELECT=82
|
||||||
SUBSTRING=83
|
SEMI=83
|
||||||
THEN=84
|
SETTINGS=84
|
||||||
THROW=85
|
SUBSTRING=85
|
||||||
TIES=86
|
THEN=86
|
||||||
TIMESTAMP=87
|
THROW=87
|
||||||
TO=88
|
TIES=88
|
||||||
TOP=89
|
TIMESTAMP=89
|
||||||
TOTALS=90
|
TO=90
|
||||||
TRAILING=91
|
TOP=91
|
||||||
TRIM=92
|
TOTALS=92
|
||||||
TRUNCATE=93
|
TRAILING=93
|
||||||
TRY=94
|
TRIM=94
|
||||||
UNBOUNDED=95
|
TRUNCATE=95
|
||||||
UNION=96
|
TRY=96
|
||||||
USING=97
|
UNBOUNDED=97
|
||||||
WEEK=98
|
UNION=98
|
||||||
WHEN=99
|
USING=99
|
||||||
WHERE=100
|
WEEK=100
|
||||||
WHILE=101
|
WHEN=101
|
||||||
WINDOW=102
|
WHERE=102
|
||||||
WITH=103
|
WHILE=103
|
||||||
YEAR=104
|
WINDOW=104
|
||||||
ESCAPE_CHAR_COMMON=105
|
WITH=105
|
||||||
IDENTIFIER=106
|
YEAR=106
|
||||||
FLOATING_LITERAL=107
|
ESCAPE_CHAR_COMMON=107
|
||||||
OCTAL_LITERAL=108
|
IDENTIFIER=108
|
||||||
DECIMAL_LITERAL=109
|
FLOATING_LITERAL=109
|
||||||
HEXADECIMAL_LITERAL=110
|
OCTAL_LITERAL=110
|
||||||
STRING_LITERAL=111
|
DECIMAL_LITERAL=111
|
||||||
ARROW=112
|
HEXADECIMAL_LITERAL=112
|
||||||
ASTERISK=113
|
STRING_LITERAL=113
|
||||||
BACKQUOTE=114
|
ARROW=114
|
||||||
BACKSLASH=115
|
ASTERISK=115
|
||||||
COLON=116
|
BACKQUOTE=116
|
||||||
COMMA=117
|
BACKSLASH=117
|
||||||
CONCAT=118
|
COLON=118
|
||||||
DASH=119
|
COMMA=119
|
||||||
DOLLAR=120
|
CONCAT=120
|
||||||
DOT=121
|
DASH=121
|
||||||
EQ_DOUBLE=122
|
DOLLAR=122
|
||||||
EQ_SINGLE=123
|
DOT=123
|
||||||
GT_EQ=124
|
EQ_DOUBLE=124
|
||||||
GT=125
|
EQ_SINGLE=125
|
||||||
HASH=126
|
GT_EQ=126
|
||||||
IREGEX_SINGLE=127
|
GT=127
|
||||||
IREGEX_DOUBLE=128
|
HASH=128
|
||||||
LBRACE=129
|
IREGEX_SINGLE=129
|
||||||
LBRACKET=130
|
IREGEX_DOUBLE=130
|
||||||
LPAREN=131
|
LBRACE=131
|
||||||
LT_EQ=132
|
LBRACKET=132
|
||||||
LT=133
|
LPAREN=133
|
||||||
NOT_EQ=134
|
LT_EQ=134
|
||||||
NOT_IREGEX=135
|
LT=135
|
||||||
NOT_REGEX=136
|
NOT_EQ=136
|
||||||
NULL_PROPERTY=137
|
NOT_IREGEX=137
|
||||||
NULLISH=138
|
NOT_REGEX=138
|
||||||
PERCENT=139
|
NULL_PROPERTY=139
|
||||||
PLUS=140
|
NULLISH=140
|
||||||
QUERY=141
|
PERCENT=141
|
||||||
QUOTE_DOUBLE=142
|
PLUS=142
|
||||||
QUOTE_SINGLE_TEMPLATE=143
|
QUERY=143
|
||||||
QUOTE_SINGLE_TEMPLATE_FULL=144
|
QUOTE_DOUBLE=144
|
||||||
QUOTE_SINGLE=145
|
QUOTE_SINGLE_TEMPLATE=145
|
||||||
REGEX_SINGLE=146
|
QUOTE_SINGLE_TEMPLATE_FULL=146
|
||||||
REGEX_DOUBLE=147
|
QUOTE_SINGLE=147
|
||||||
RBRACE=148
|
REGEX_SINGLE=148
|
||||||
RBRACKET=149
|
REGEX_DOUBLE=149
|
||||||
RPAREN=150
|
RBRACE=150
|
||||||
SEMICOLON=151
|
RBRACKET=151
|
||||||
SLASH=152
|
RPAREN=152
|
||||||
UNDERSCORE=153
|
SEMICOLON=153
|
||||||
MULTI_LINE_COMMENT=154
|
SLASH=154
|
||||||
SINGLE_LINE_COMMENT=155
|
UNDERSCORE=155
|
||||||
WHITESPACE=156
|
MULTI_LINE_COMMENT=156
|
||||||
STRING_TEXT=157
|
SINGLE_LINE_COMMENT=157
|
||||||
STRING_ESCAPE_TRIGGER=158
|
WHITESPACE=158
|
||||||
FULL_STRING_TEXT=159
|
STRING_TEXT=159
|
||||||
FULL_STRING_ESCAPE_TRIGGER=160
|
STRING_ESCAPE_TRIGGER=160
|
||||||
'->'=112
|
FULL_STRING_TEXT=161
|
||||||
'*'=113
|
FULL_STRING_ESCAPE_TRIGGER=162
|
||||||
'`'=114
|
'->'=114
|
||||||
'\\'=115
|
'*'=115
|
||||||
':'=116
|
'`'=116
|
||||||
','=117
|
'\\'=117
|
||||||
'||'=118
|
':'=118
|
||||||
'-'=119
|
','=119
|
||||||
'$'=120
|
'||'=120
|
||||||
'.'=121
|
'-'=121
|
||||||
'=='=122
|
'$'=122
|
||||||
'='=123
|
'.'=123
|
||||||
'>='=124
|
'=='=124
|
||||||
'>'=125
|
'='=125
|
||||||
'#'=126
|
'>='=126
|
||||||
'~*'=127
|
'>'=127
|
||||||
'=~*'=128
|
'#'=128
|
||||||
'{'=129
|
'~*'=129
|
||||||
'['=130
|
'=~*'=130
|
||||||
'('=131
|
'{'=131
|
||||||
'<='=132
|
'['=132
|
||||||
'<'=133
|
'('=133
|
||||||
'!~*'=135
|
'<='=134
|
||||||
'!~'=136
|
'<'=135
|
||||||
'?.'=137
|
'!~*'=137
|
||||||
'??'=138
|
'!~'=138
|
||||||
'%'=139
|
'?.'=139
|
||||||
'+'=140
|
'??'=140
|
||||||
'?'=141
|
'%'=141
|
||||||
'"'=142
|
'+'=142
|
||||||
'f\''=143
|
'?'=143
|
||||||
'F\''=144
|
'"'=144
|
||||||
'\''=145
|
'f\''=145
|
||||||
'~'=146
|
'F\''=146
|
||||||
'=~'=147
|
'\''=147
|
||||||
'}'=148
|
'~'=148
|
||||||
']'=149
|
'=~'=149
|
||||||
')'=150
|
'}'=150
|
||||||
';'=151
|
']'=151
|
||||||
'/'=152
|
')'=152
|
||||||
'_'=153
|
';'=153
|
||||||
|
'/'=154
|
||||||
|
'_'=155
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -16,32 +16,33 @@ public:
|
|||||||
ASOF = 8, BETWEEN = 9, BOTH = 10, BY = 11, CASE = 12, CAST = 13, CATCH = 14,
|
ASOF = 8, BETWEEN = 9, BOTH = 10, BY = 11, CASE = 12, CAST = 13, CATCH = 14,
|
||||||
COHORT = 15, COLLATE = 16, CROSS = 17, CUBE = 18, CURRENT = 19, DATE = 20,
|
COHORT = 15, COLLATE = 16, CROSS = 17, CUBE = 18, CURRENT = 19, DATE = 20,
|
||||||
DAY = 21, DESC = 22, DESCENDING = 23, DISTINCT = 24, ELSE = 25, END = 26,
|
DAY = 21, DESC = 22, DESCENDING = 23, DISTINCT = 24, ELSE = 25, END = 26,
|
||||||
EXTRACT = 27, FINAL = 28, FINALLY = 29, FIRST = 30, FN = 31, FOLLOWING = 32,
|
EXCEPT = 27, EXTRACT = 28, FINAL = 29, FINALLY = 30, FIRST = 31, FN = 32,
|
||||||
FOR = 33, FROM = 34, FULL = 35, FUN = 36, GROUP = 37, HAVING = 38, HOUR = 39,
|
FOLLOWING = 33, FOR = 34, FROM = 35, FULL = 36, FUN = 37, GROUP = 38,
|
||||||
ID = 40, IF = 41, ILIKE = 42, IN = 43, INF = 44, INNER = 45, INTERVAL = 46,
|
HAVING = 39, HOUR = 40, ID = 41, IF = 42, ILIKE = 43, IN = 44, INF = 45,
|
||||||
IS = 47, JOIN = 48, KEY = 49, LAST = 50, LEADING = 51, LEFT = 52, LET = 53,
|
INNER = 46, INTERSECT = 47, INTERVAL = 48, IS = 49, JOIN = 50, KEY = 51,
|
||||||
LIKE = 54, LIMIT = 55, MINUTE = 56, MONTH = 57, NAN_SQL = 58, NOT = 59,
|
LAST = 52, LEADING = 53, LEFT = 54, LET = 55, LIKE = 56, LIMIT = 57,
|
||||||
NULL_SQL = 60, NULLS = 61, OFFSET = 62, ON = 63, OR = 64, ORDER = 65,
|
MINUTE = 58, MONTH = 59, NAN_SQL = 60, NOT = 61, NULL_SQL = 62, NULLS = 63,
|
||||||
OUTER = 66, OVER = 67, PARTITION = 68, PRECEDING = 69, PREWHERE = 70,
|
OFFSET = 64, ON = 65, OR = 66, ORDER = 67, OUTER = 68, OVER = 69, PARTITION = 70,
|
||||||
QUARTER = 71, RANGE = 72, RETURN = 73, RIGHT = 74, ROLLUP = 75, ROW = 76,
|
PRECEDING = 71, PREWHERE = 72, QUARTER = 73, RANGE = 74, RETURN = 75,
|
||||||
ROWS = 77, SAMPLE = 78, SECOND = 79, SELECT = 80, SEMI = 81, SETTINGS = 82,
|
RIGHT = 76, ROLLUP = 77, ROW = 78, ROWS = 79, SAMPLE = 80, SECOND = 81,
|
||||||
SUBSTRING = 83, THEN = 84, THROW = 85, TIES = 86, TIMESTAMP = 87, TO = 88,
|
SELECT = 82, SEMI = 83, SETTINGS = 84, SUBSTRING = 85, THEN = 86, THROW = 87,
|
||||||
TOP = 89, TOTALS = 90, TRAILING = 91, TRIM = 92, TRUNCATE = 93, TRY = 94,
|
TIES = 88, TIMESTAMP = 89, TO = 90, TOP = 91, TOTALS = 92, TRAILING = 93,
|
||||||
UNBOUNDED = 95, UNION = 96, USING = 97, WEEK = 98, WHEN = 99, WHERE = 100,
|
TRIM = 94, TRUNCATE = 95, TRY = 96, UNBOUNDED = 97, UNION = 98, USING = 99,
|
||||||
WHILE = 101, WINDOW = 102, WITH = 103, YEAR = 104, ESCAPE_CHAR_COMMON = 105,
|
WEEK = 100, WHEN = 101, WHERE = 102, WHILE = 103, WINDOW = 104, WITH = 105,
|
||||||
IDENTIFIER = 106, FLOATING_LITERAL = 107, OCTAL_LITERAL = 108, DECIMAL_LITERAL = 109,
|
YEAR = 106, ESCAPE_CHAR_COMMON = 107, IDENTIFIER = 108, FLOATING_LITERAL = 109,
|
||||||
HEXADECIMAL_LITERAL = 110, STRING_LITERAL = 111, ARROW = 112, ASTERISK = 113,
|
OCTAL_LITERAL = 110, DECIMAL_LITERAL = 111, HEXADECIMAL_LITERAL = 112,
|
||||||
BACKQUOTE = 114, BACKSLASH = 115, COLON = 116, COMMA = 117, CONCAT = 118,
|
STRING_LITERAL = 113, ARROW = 114, ASTERISK = 115, BACKQUOTE = 116,
|
||||||
DASH = 119, DOLLAR = 120, DOT = 121, EQ_DOUBLE = 122, EQ_SINGLE = 123,
|
BACKSLASH = 117, COLON = 118, COMMA = 119, CONCAT = 120, DASH = 121,
|
||||||
GT_EQ = 124, GT = 125, HASH = 126, IREGEX_SINGLE = 127, IREGEX_DOUBLE = 128,
|
DOLLAR = 122, DOT = 123, EQ_DOUBLE = 124, EQ_SINGLE = 125, GT_EQ = 126,
|
||||||
LBRACE = 129, LBRACKET = 130, LPAREN = 131, LT_EQ = 132, LT = 133, NOT_EQ = 134,
|
GT = 127, HASH = 128, IREGEX_SINGLE = 129, IREGEX_DOUBLE = 130, LBRACE = 131,
|
||||||
NOT_IREGEX = 135, NOT_REGEX = 136, NULL_PROPERTY = 137, NULLISH = 138,
|
LBRACKET = 132, LPAREN = 133, LT_EQ = 134, LT = 135, NOT_EQ = 136, NOT_IREGEX = 137,
|
||||||
PERCENT = 139, PLUS = 140, QUERY = 141, QUOTE_DOUBLE = 142, QUOTE_SINGLE_TEMPLATE = 143,
|
NOT_REGEX = 138, NULL_PROPERTY = 139, NULLISH = 140, PERCENT = 141,
|
||||||
QUOTE_SINGLE_TEMPLATE_FULL = 144, QUOTE_SINGLE = 145, REGEX_SINGLE = 146,
|
PLUS = 142, QUERY = 143, QUOTE_DOUBLE = 144, QUOTE_SINGLE_TEMPLATE = 145,
|
||||||
REGEX_DOUBLE = 147, RBRACE = 148, RBRACKET = 149, RPAREN = 150, SEMICOLON = 151,
|
QUOTE_SINGLE_TEMPLATE_FULL = 146, QUOTE_SINGLE = 147, REGEX_SINGLE = 148,
|
||||||
SLASH = 152, UNDERSCORE = 153, MULTI_LINE_COMMENT = 154, SINGLE_LINE_COMMENT = 155,
|
REGEX_DOUBLE = 149, RBRACE = 150, RBRACKET = 151, RPAREN = 152, SEMICOLON = 153,
|
||||||
WHITESPACE = 156, STRING_TEXT = 157, STRING_ESCAPE_TRIGGER = 158, FULL_STRING_TEXT = 159,
|
SLASH = 154, UNDERSCORE = 155, MULTI_LINE_COMMENT = 156, SINGLE_LINE_COMMENT = 157,
|
||||||
FULL_STRING_ESCAPE_TRIGGER = 160
|
WHITESPACE = 158, STRING_TEXT = 159, STRING_ESCAPE_TRIGGER = 160, FULL_STRING_TEXT = 161,
|
||||||
|
FULL_STRING_ESCAPE_TRIGGER = 162
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
@ -50,26 +51,27 @@ public:
|
|||||||
RuleCatchBlock = 8, RuleTryCatchStmt = 9, RuleIfStmt = 10, RuleWhileStmt = 11,
|
RuleCatchBlock = 8, RuleTryCatchStmt = 9, RuleIfStmt = 10, RuleWhileStmt = 11,
|
||||||
RuleForStmt = 12, RuleForInStmt = 13, RuleFuncStmt = 14, RuleVarAssignment = 15,
|
RuleForStmt = 12, RuleForInStmt = 13, RuleFuncStmt = 14, RuleVarAssignment = 15,
|
||||||
RuleExprStmt = 16, RuleEmptyStmt = 17, RuleBlock = 18, RuleKvPair = 19,
|
RuleExprStmt = 16, RuleEmptyStmt = 17, RuleBlock = 18, RuleKvPair = 19,
|
||||||
RuleKvPairList = 20, RuleSelect = 21, RuleSelectUnionStmt = 22, RuleSelectStmtWithParens = 23,
|
RuleKvPairList = 20, RuleSelect = 21, RuleSelectStmtWithParens = 22,
|
||||||
RuleSelectStmt = 24, RuleWithClause = 25, RuleTopClause = 26, RuleFromClause = 27,
|
RuleSubsequentSelectSetClause = 23, RuleSelectSetStmt = 24, RuleSelectStmt = 25,
|
||||||
RuleArrayJoinClause = 28, RuleWindowClause = 29, RulePrewhereClause = 30,
|
RuleWithClause = 26, RuleTopClause = 27, RuleFromClause = 28, RuleArrayJoinClause = 29,
|
||||||
RuleWhereClause = 31, RuleGroupByClause = 32, RuleHavingClause = 33,
|
RuleWindowClause = 30, RulePrewhereClause = 31, RuleWhereClause = 32,
|
||||||
RuleOrderByClause = 34, RuleProjectionOrderByClause = 35, RuleLimitAndOffsetClause = 36,
|
RuleGroupByClause = 33, RuleHavingClause = 34, RuleOrderByClause = 35,
|
||||||
RuleOffsetOnlyClause = 37, RuleSettingsClause = 38, RuleJoinExpr = 39,
|
RuleProjectionOrderByClause = 36, RuleLimitAndOffsetClause = 37, RuleOffsetOnlyClause = 38,
|
||||||
RuleJoinOp = 40, RuleJoinOpCross = 41, RuleJoinConstraintClause = 42,
|
RuleSettingsClause = 39, RuleJoinExpr = 40, RuleJoinOp = 41, RuleJoinOpCross = 42,
|
||||||
RuleSampleClause = 43, RuleOrderExprList = 44, RuleOrderExpr = 45, RuleRatioExpr = 46,
|
RuleJoinConstraintClause = 43, RuleSampleClause = 44, RuleOrderExprList = 45,
|
||||||
RuleSettingExprList = 47, RuleSettingExpr = 48, RuleWindowExpr = 49,
|
RuleOrderExpr = 46, RuleRatioExpr = 47, RuleSettingExprList = 48, RuleSettingExpr = 49,
|
||||||
RuleWinPartitionByClause = 50, RuleWinOrderByClause = 51, RuleWinFrameClause = 52,
|
RuleWindowExpr = 50, RuleWinPartitionByClause = 51, RuleWinOrderByClause = 52,
|
||||||
RuleWinFrameExtend = 53, RuleWinFrameBound = 54, RuleExpr = 55, RuleColumnTypeExpr = 56,
|
RuleWinFrameClause = 53, RuleWinFrameExtend = 54, RuleWinFrameBound = 55,
|
||||||
RuleColumnExprList = 57, RuleColumnExpr = 58, RuleColumnLambdaExpr = 59,
|
RuleExpr = 56, RuleColumnTypeExpr = 57, RuleColumnExprList = 58, RuleColumnExpr = 59,
|
||||||
RuleHogqlxTagElement = 60, RuleHogqlxTagAttribute = 61, RuleWithExprList = 62,
|
RuleColumnLambdaExpr = 60, RuleHogqlxTagElement = 61, RuleHogqlxTagAttribute = 62,
|
||||||
RuleWithExpr = 63, RuleColumnIdentifier = 64, RuleNestedIdentifier = 65,
|
RuleWithExprList = 63, RuleWithExpr = 64, RuleColumnIdentifier = 65,
|
||||||
RuleTableExpr = 66, RuleTableFunctionExpr = 67, RuleTableIdentifier = 68,
|
RuleNestedIdentifier = 66, RuleTableExpr = 67, RuleTableFunctionExpr = 68,
|
||||||
RuleTableArgList = 69, RuleDatabaseIdentifier = 70, RuleFloatingLiteral = 71,
|
RuleTableIdentifier = 69, RuleTableArgList = 70, RuleDatabaseIdentifier = 71,
|
||||||
RuleNumberLiteral = 72, RuleLiteral = 73, RuleInterval = 74, RuleKeyword = 75,
|
RuleFloatingLiteral = 72, RuleNumberLiteral = 73, RuleLiteral = 74,
|
||||||
RuleKeywordForAlias = 76, RuleAlias = 77, RuleIdentifier = 78, RuleEnumValue = 79,
|
RuleInterval = 75, RuleKeyword = 76, RuleKeywordForAlias = 77, RuleAlias = 78,
|
||||||
RulePlaceholder = 80, RuleString = 81, RuleTemplateString = 82, RuleStringContents = 83,
|
RuleIdentifier = 79, RuleEnumValue = 80, RulePlaceholder = 81, RuleString = 82,
|
||||||
RuleFullTemplateString = 84, RuleStringContentsFull = 85
|
RuleTemplateString = 83, RuleStringContents = 84, RuleFullTemplateString = 85,
|
||||||
|
RuleStringContentsFull = 86
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit HogQLParser(antlr4::TokenStream *input);
|
explicit HogQLParser(antlr4::TokenStream *input);
|
||||||
@ -111,8 +113,9 @@ public:
|
|||||||
class KvPairContext;
|
class KvPairContext;
|
||||||
class KvPairListContext;
|
class KvPairListContext;
|
||||||
class SelectContext;
|
class SelectContext;
|
||||||
class SelectUnionStmtContext;
|
|
||||||
class SelectStmtWithParensContext;
|
class SelectStmtWithParensContext;
|
||||||
|
class SubsequentSelectSetClauseContext;
|
||||||
|
class SelectSetStmtContext;
|
||||||
class SelectStmtContext;
|
class SelectStmtContext;
|
||||||
class WithClauseContext;
|
class WithClauseContext;
|
||||||
class TopClauseContext;
|
class TopClauseContext;
|
||||||
@ -552,7 +555,7 @@ public:
|
|||||||
SelectContext(antlr4::ParserRuleContext *parent, size_t invokingState);
|
SelectContext(antlr4::ParserRuleContext *parent, size_t invokingState);
|
||||||
virtual size_t getRuleIndex() const override;
|
virtual size_t getRuleIndex() const override;
|
||||||
antlr4::tree::TerminalNode *EOF();
|
antlr4::tree::TerminalNode *EOF();
|
||||||
SelectUnionStmtContext *selectUnionStmt();
|
SelectSetStmtContext *selectSetStmt();
|
||||||
SelectStmtContext *selectStmt();
|
SelectStmtContext *selectStmt();
|
||||||
HogqlxTagElementContext *hogqlxTagElement();
|
HogqlxTagElementContext *hogqlxTagElement();
|
||||||
|
|
||||||
@ -563,31 +566,13 @@ public:
|
|||||||
|
|
||||||
SelectContext* select();
|
SelectContext* select();
|
||||||
|
|
||||||
class SelectUnionStmtContext : public antlr4::ParserRuleContext {
|
|
||||||
public:
|
|
||||||
SelectUnionStmtContext(antlr4::ParserRuleContext *parent, size_t invokingState);
|
|
||||||
virtual size_t getRuleIndex() const override;
|
|
||||||
std::vector<SelectStmtWithParensContext *> selectStmtWithParens();
|
|
||||||
SelectStmtWithParensContext* selectStmtWithParens(size_t i);
|
|
||||||
std::vector<antlr4::tree::TerminalNode *> UNION();
|
|
||||||
antlr4::tree::TerminalNode* UNION(size_t i);
|
|
||||||
std::vector<antlr4::tree::TerminalNode *> ALL();
|
|
||||||
antlr4::tree::TerminalNode* ALL(size_t i);
|
|
||||||
|
|
||||||
|
|
||||||
virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
SelectUnionStmtContext* selectUnionStmt();
|
|
||||||
|
|
||||||
class SelectStmtWithParensContext : public antlr4::ParserRuleContext {
|
class SelectStmtWithParensContext : public antlr4::ParserRuleContext {
|
||||||
public:
|
public:
|
||||||
SelectStmtWithParensContext(antlr4::ParserRuleContext *parent, size_t invokingState);
|
SelectStmtWithParensContext(antlr4::ParserRuleContext *parent, size_t invokingState);
|
||||||
virtual size_t getRuleIndex() const override;
|
virtual size_t getRuleIndex() const override;
|
||||||
SelectStmtContext *selectStmt();
|
SelectStmtContext *selectStmt();
|
||||||
antlr4::tree::TerminalNode *LPAREN();
|
antlr4::tree::TerminalNode *LPAREN();
|
||||||
SelectUnionStmtContext *selectUnionStmt();
|
SelectSetStmtContext *selectSetStmt();
|
||||||
antlr4::tree::TerminalNode *RPAREN();
|
antlr4::tree::TerminalNode *RPAREN();
|
||||||
PlaceholderContext *placeholder();
|
PlaceholderContext *placeholder();
|
||||||
|
|
||||||
@ -598,6 +583,38 @@ public:
|
|||||||
|
|
||||||
SelectStmtWithParensContext* selectStmtWithParens();
|
SelectStmtWithParensContext* selectStmtWithParens();
|
||||||
|
|
||||||
|
class SubsequentSelectSetClauseContext : public antlr4::ParserRuleContext {
|
||||||
|
public:
|
||||||
|
SubsequentSelectSetClauseContext(antlr4::ParserRuleContext *parent, size_t invokingState);
|
||||||
|
virtual size_t getRuleIndex() const override;
|
||||||
|
SelectStmtWithParensContext *selectStmtWithParens();
|
||||||
|
antlr4::tree::TerminalNode *EXCEPT();
|
||||||
|
antlr4::tree::TerminalNode *UNION();
|
||||||
|
antlr4::tree::TerminalNode *ALL();
|
||||||
|
antlr4::tree::TerminalNode *INTERSECT();
|
||||||
|
|
||||||
|
|
||||||
|
virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
SubsequentSelectSetClauseContext* subsequentSelectSetClause();
|
||||||
|
|
||||||
|
class SelectSetStmtContext : public antlr4::ParserRuleContext {
|
||||||
|
public:
|
||||||
|
SelectSetStmtContext(antlr4::ParserRuleContext *parent, size_t invokingState);
|
||||||
|
virtual size_t getRuleIndex() const override;
|
||||||
|
SelectStmtWithParensContext *selectStmtWithParens();
|
||||||
|
std::vector<SubsequentSelectSetClauseContext *> subsequentSelectSetClause();
|
||||||
|
SubsequentSelectSetClauseContext* subsequentSelectSetClause(size_t i);
|
||||||
|
|
||||||
|
|
||||||
|
virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
SelectSetStmtContext* selectSetStmt();
|
||||||
|
|
||||||
class SelectStmtContext : public antlr4::ParserRuleContext {
|
class SelectStmtContext : public antlr4::ParserRuleContext {
|
||||||
public:
|
public:
|
||||||
HogQLParser::WithClauseContext *with = nullptr;
|
HogQLParser::WithClauseContext *with = nullptr;
|
||||||
@ -1398,7 +1415,7 @@ public:
|
|||||||
ColumnExprSubqueryContext(ColumnExprContext *ctx);
|
ColumnExprSubqueryContext(ColumnExprContext *ctx);
|
||||||
|
|
||||||
antlr4::tree::TerminalNode *LPAREN();
|
antlr4::tree::TerminalNode *LPAREN();
|
||||||
SelectUnionStmtContext *selectUnionStmt();
|
SelectSetStmtContext *selectSetStmt();
|
||||||
antlr4::tree::TerminalNode *RPAREN();
|
antlr4::tree::TerminalNode *RPAREN();
|
||||||
|
|
||||||
virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
|
virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
|
||||||
@ -1999,7 +2016,7 @@ public:
|
|||||||
IdentifierContext *identifier();
|
IdentifierContext *identifier();
|
||||||
antlr4::tree::TerminalNode *AS();
|
antlr4::tree::TerminalNode *AS();
|
||||||
antlr4::tree::TerminalNode *LPAREN();
|
antlr4::tree::TerminalNode *LPAREN();
|
||||||
SelectUnionStmtContext *selectUnionStmt();
|
SelectSetStmtContext *selectSetStmt();
|
||||||
antlr4::tree::TerminalNode *RPAREN();
|
antlr4::tree::TerminalNode *RPAREN();
|
||||||
|
|
||||||
virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
|
virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
|
||||||
@ -2084,7 +2101,7 @@ public:
|
|||||||
TableExprSubqueryContext(TableExprContext *ctx);
|
TableExprSubqueryContext(TableExprContext *ctx);
|
||||||
|
|
||||||
antlr4::tree::TerminalNode *LPAREN();
|
antlr4::tree::TerminalNode *LPAREN();
|
||||||
SelectUnionStmtContext *selectUnionStmt();
|
SelectSetStmtContext *selectSetStmt();
|
||||||
antlr4::tree::TerminalNode *RPAREN();
|
antlr4::tree::TerminalNode *RPAREN();
|
||||||
|
|
||||||
virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
|
virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
|
||||||
|
File diff suppressed because one or more lines are too long
@ -24,178 +24,180 @@ DESCENDING=23
|
|||||||
DISTINCT=24
|
DISTINCT=24
|
||||||
ELSE=25
|
ELSE=25
|
||||||
END=26
|
END=26
|
||||||
EXTRACT=27
|
EXCEPT=27
|
||||||
FINAL=28
|
EXTRACT=28
|
||||||
FINALLY=29
|
FINAL=29
|
||||||
FIRST=30
|
FINALLY=30
|
||||||
FN=31
|
FIRST=31
|
||||||
FOLLOWING=32
|
FN=32
|
||||||
FOR=33
|
FOLLOWING=33
|
||||||
FROM=34
|
FOR=34
|
||||||
FULL=35
|
FROM=35
|
||||||
FUN=36
|
FULL=36
|
||||||
GROUP=37
|
FUN=37
|
||||||
HAVING=38
|
GROUP=38
|
||||||
HOUR=39
|
HAVING=39
|
||||||
ID=40
|
HOUR=40
|
||||||
IF=41
|
ID=41
|
||||||
ILIKE=42
|
IF=42
|
||||||
IN=43
|
ILIKE=43
|
||||||
INF=44
|
IN=44
|
||||||
INNER=45
|
INF=45
|
||||||
INTERVAL=46
|
INNER=46
|
||||||
IS=47
|
INTERSECT=47
|
||||||
JOIN=48
|
INTERVAL=48
|
||||||
KEY=49
|
IS=49
|
||||||
LAST=50
|
JOIN=50
|
||||||
LEADING=51
|
KEY=51
|
||||||
LEFT=52
|
LAST=52
|
||||||
LET=53
|
LEADING=53
|
||||||
LIKE=54
|
LEFT=54
|
||||||
LIMIT=55
|
LET=55
|
||||||
MINUTE=56
|
LIKE=56
|
||||||
MONTH=57
|
LIMIT=57
|
||||||
NAN_SQL=58
|
MINUTE=58
|
||||||
NOT=59
|
MONTH=59
|
||||||
NULL_SQL=60
|
NAN_SQL=60
|
||||||
NULLS=61
|
NOT=61
|
||||||
OFFSET=62
|
NULL_SQL=62
|
||||||
ON=63
|
NULLS=63
|
||||||
OR=64
|
OFFSET=64
|
||||||
ORDER=65
|
ON=65
|
||||||
OUTER=66
|
OR=66
|
||||||
OVER=67
|
ORDER=67
|
||||||
PARTITION=68
|
OUTER=68
|
||||||
PRECEDING=69
|
OVER=69
|
||||||
PREWHERE=70
|
PARTITION=70
|
||||||
QUARTER=71
|
PRECEDING=71
|
||||||
RANGE=72
|
PREWHERE=72
|
||||||
RETURN=73
|
QUARTER=73
|
||||||
RIGHT=74
|
RANGE=74
|
||||||
ROLLUP=75
|
RETURN=75
|
||||||
ROW=76
|
RIGHT=76
|
||||||
ROWS=77
|
ROLLUP=77
|
||||||
SAMPLE=78
|
ROW=78
|
||||||
SECOND=79
|
ROWS=79
|
||||||
SELECT=80
|
SAMPLE=80
|
||||||
SEMI=81
|
SECOND=81
|
||||||
SETTINGS=82
|
SELECT=82
|
||||||
SUBSTRING=83
|
SEMI=83
|
||||||
THEN=84
|
SETTINGS=84
|
||||||
THROW=85
|
SUBSTRING=85
|
||||||
TIES=86
|
THEN=86
|
||||||
TIMESTAMP=87
|
THROW=87
|
||||||
TO=88
|
TIES=88
|
||||||
TOP=89
|
TIMESTAMP=89
|
||||||
TOTALS=90
|
TO=90
|
||||||
TRAILING=91
|
TOP=91
|
||||||
TRIM=92
|
TOTALS=92
|
||||||
TRUNCATE=93
|
TRAILING=93
|
||||||
TRY=94
|
TRIM=94
|
||||||
UNBOUNDED=95
|
TRUNCATE=95
|
||||||
UNION=96
|
TRY=96
|
||||||
USING=97
|
UNBOUNDED=97
|
||||||
WEEK=98
|
UNION=98
|
||||||
WHEN=99
|
USING=99
|
||||||
WHERE=100
|
WEEK=100
|
||||||
WHILE=101
|
WHEN=101
|
||||||
WINDOW=102
|
WHERE=102
|
||||||
WITH=103
|
WHILE=103
|
||||||
YEAR=104
|
WINDOW=104
|
||||||
ESCAPE_CHAR_COMMON=105
|
WITH=105
|
||||||
IDENTIFIER=106
|
YEAR=106
|
||||||
FLOATING_LITERAL=107
|
ESCAPE_CHAR_COMMON=107
|
||||||
OCTAL_LITERAL=108
|
IDENTIFIER=108
|
||||||
DECIMAL_LITERAL=109
|
FLOATING_LITERAL=109
|
||||||
HEXADECIMAL_LITERAL=110
|
OCTAL_LITERAL=110
|
||||||
STRING_LITERAL=111
|
DECIMAL_LITERAL=111
|
||||||
ARROW=112
|
HEXADECIMAL_LITERAL=112
|
||||||
ASTERISK=113
|
STRING_LITERAL=113
|
||||||
BACKQUOTE=114
|
ARROW=114
|
||||||
BACKSLASH=115
|
ASTERISK=115
|
||||||
COLON=116
|
BACKQUOTE=116
|
||||||
COMMA=117
|
BACKSLASH=117
|
||||||
CONCAT=118
|
COLON=118
|
||||||
DASH=119
|
COMMA=119
|
||||||
DOLLAR=120
|
CONCAT=120
|
||||||
DOT=121
|
DASH=121
|
||||||
EQ_DOUBLE=122
|
DOLLAR=122
|
||||||
EQ_SINGLE=123
|
DOT=123
|
||||||
GT_EQ=124
|
EQ_DOUBLE=124
|
||||||
GT=125
|
EQ_SINGLE=125
|
||||||
HASH=126
|
GT_EQ=126
|
||||||
IREGEX_SINGLE=127
|
GT=127
|
||||||
IREGEX_DOUBLE=128
|
HASH=128
|
||||||
LBRACE=129
|
IREGEX_SINGLE=129
|
||||||
LBRACKET=130
|
IREGEX_DOUBLE=130
|
||||||
LPAREN=131
|
LBRACE=131
|
||||||
LT_EQ=132
|
LBRACKET=132
|
||||||
LT=133
|
LPAREN=133
|
||||||
NOT_EQ=134
|
LT_EQ=134
|
||||||
NOT_IREGEX=135
|
LT=135
|
||||||
NOT_REGEX=136
|
NOT_EQ=136
|
||||||
NULL_PROPERTY=137
|
NOT_IREGEX=137
|
||||||
NULLISH=138
|
NOT_REGEX=138
|
||||||
PERCENT=139
|
NULL_PROPERTY=139
|
||||||
PLUS=140
|
NULLISH=140
|
||||||
QUERY=141
|
PERCENT=141
|
||||||
QUOTE_DOUBLE=142
|
PLUS=142
|
||||||
QUOTE_SINGLE_TEMPLATE=143
|
QUERY=143
|
||||||
QUOTE_SINGLE_TEMPLATE_FULL=144
|
QUOTE_DOUBLE=144
|
||||||
QUOTE_SINGLE=145
|
QUOTE_SINGLE_TEMPLATE=145
|
||||||
REGEX_SINGLE=146
|
QUOTE_SINGLE_TEMPLATE_FULL=146
|
||||||
REGEX_DOUBLE=147
|
QUOTE_SINGLE=147
|
||||||
RBRACE=148
|
REGEX_SINGLE=148
|
||||||
RBRACKET=149
|
REGEX_DOUBLE=149
|
||||||
RPAREN=150
|
RBRACE=150
|
||||||
SEMICOLON=151
|
RBRACKET=151
|
||||||
SLASH=152
|
RPAREN=152
|
||||||
UNDERSCORE=153
|
SEMICOLON=153
|
||||||
MULTI_LINE_COMMENT=154
|
SLASH=154
|
||||||
SINGLE_LINE_COMMENT=155
|
UNDERSCORE=155
|
||||||
WHITESPACE=156
|
MULTI_LINE_COMMENT=156
|
||||||
STRING_TEXT=157
|
SINGLE_LINE_COMMENT=157
|
||||||
STRING_ESCAPE_TRIGGER=158
|
WHITESPACE=158
|
||||||
FULL_STRING_TEXT=159
|
STRING_TEXT=159
|
||||||
FULL_STRING_ESCAPE_TRIGGER=160
|
STRING_ESCAPE_TRIGGER=160
|
||||||
'->'=112
|
FULL_STRING_TEXT=161
|
||||||
'*'=113
|
FULL_STRING_ESCAPE_TRIGGER=162
|
||||||
'`'=114
|
'->'=114
|
||||||
'\\'=115
|
'*'=115
|
||||||
':'=116
|
'`'=116
|
||||||
','=117
|
'\\'=117
|
||||||
'||'=118
|
':'=118
|
||||||
'-'=119
|
','=119
|
||||||
'$'=120
|
'||'=120
|
||||||
'.'=121
|
'-'=121
|
||||||
'=='=122
|
'$'=122
|
||||||
'='=123
|
'.'=123
|
||||||
'>='=124
|
'=='=124
|
||||||
'>'=125
|
'='=125
|
||||||
'#'=126
|
'>='=126
|
||||||
'~*'=127
|
'>'=127
|
||||||
'=~*'=128
|
'#'=128
|
||||||
'{'=129
|
'~*'=129
|
||||||
'['=130
|
'=~*'=130
|
||||||
'('=131
|
'{'=131
|
||||||
'<='=132
|
'['=132
|
||||||
'<'=133
|
'('=133
|
||||||
'!~*'=135
|
'<='=134
|
||||||
'!~'=136
|
'<'=135
|
||||||
'?.'=137
|
'!~*'=137
|
||||||
'??'=138
|
'!~'=138
|
||||||
'%'=139
|
'?.'=139
|
||||||
'+'=140
|
'??'=140
|
||||||
'?'=141
|
'%'=141
|
||||||
'"'=142
|
'+'=142
|
||||||
'f\''=143
|
'?'=143
|
||||||
'F\''=144
|
'"'=144
|
||||||
'\''=145
|
'f\''=145
|
||||||
'~'=146
|
'F\''=146
|
||||||
'=~'=147
|
'\''=147
|
||||||
'}'=148
|
'~'=148
|
||||||
']'=149
|
'=~'=149
|
||||||
')'=150
|
'}'=150
|
||||||
';'=151
|
']'=151
|
||||||
'/'=152
|
')'=152
|
||||||
'_'=153
|
';'=153
|
||||||
|
'/'=154
|
||||||
|
'_'=155
|
||||||
|
@ -103,11 +103,15 @@ public:
|
|||||||
return visitChildren(ctx);
|
return visitChildren(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual std::any visitSelectUnionStmt(HogQLParser::SelectUnionStmtContext *ctx) override {
|
virtual std::any visitSelectStmtWithParens(HogQLParser::SelectStmtWithParensContext *ctx) override {
|
||||||
return visitChildren(ctx);
|
return visitChildren(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual std::any visitSelectStmtWithParens(HogQLParser::SelectStmtWithParensContext *ctx) override {
|
virtual std::any visitSubsequentSelectSetClause(HogQLParser::SubsequentSelectSetClauseContext *ctx) override {
|
||||||
|
return visitChildren(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::any visitSelectSetStmt(HogQLParser::SelectSetStmtContext *ctx) override {
|
||||||
return visitChildren(ctx);
|
return visitChildren(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,10 +63,12 @@ public:
|
|||||||
|
|
||||||
virtual std::any visitSelect(HogQLParser::SelectContext *context) = 0;
|
virtual std::any visitSelect(HogQLParser::SelectContext *context) = 0;
|
||||||
|
|
||||||
virtual std::any visitSelectUnionStmt(HogQLParser::SelectUnionStmtContext *context) = 0;
|
|
||||||
|
|
||||||
virtual std::any visitSelectStmtWithParens(HogQLParser::SelectStmtWithParensContext *context) = 0;
|
virtual std::any visitSelectStmtWithParens(HogQLParser::SelectStmtWithParensContext *context) = 0;
|
||||||
|
|
||||||
|
virtual std::any visitSubsequentSelectSetClause(HogQLParser::SubsequentSelectSetClauseContext *context) = 0;
|
||||||
|
|
||||||
|
virtual std::any visitSelectSetStmt(HogQLParser::SelectSetStmtContext *context) = 0;
|
||||||
|
|
||||||
virtual std::any visitSelectStmt(HogQLParser::SelectStmtContext *context) = 0;
|
virtual std::any visitSelectStmt(HogQLParser::SelectStmtContext *context) = 0;
|
||||||
|
|
||||||
virtual std::any visitWithClause(HogQLParser::WithClauseContext *context) = 0;
|
virtual std::any visitWithClause(HogQLParser::WithClauseContext *context) = 0;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from posthog.hogql.ast import SelectQuery, SelectUnionQuery, Program
|
from posthog.hogql.ast import SelectQuery, SelectSetQuery, Program
|
||||||
from posthog.hogql.base import AST
|
from posthog.hogql.base import AST
|
||||||
|
|
||||||
def parse_expr(expr: str, /, *, is_internal: bool = False) -> AST:
|
def parse_expr(expr: str, /, *, is_internal: bool = False) -> AST:
|
||||||
@ -15,7 +15,7 @@ def parse_order_expr(expr: str, /, *, is_internal: bool = False) -> AST:
|
|||||||
"""
|
"""
|
||||||
...
|
...
|
||||||
|
|
||||||
def parse_select(expr: str, /, *, is_internal: bool = False) -> SelectQuery | SelectUnionQuery:
|
def parse_select(expr: str, /, *, is_internal: bool = False) -> SelectQuery | SelectSetQuery:
|
||||||
"""Parse the HogQL SELECT statement string into an AST.
|
"""Parse the HogQL SELECT statement string into an AST.
|
||||||
|
|
||||||
If the expr `is_internal`, spans and notices won't be included in the AST.
|
If the expr `is_internal`, spans and notices won't be included in the AST.
|
||||||
|
@ -827,9 +827,9 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
|
|||||||
// HogQL rules
|
// HogQL rules
|
||||||
|
|
||||||
VISIT(Select) {
|
VISIT(Select) {
|
||||||
auto select_union_stmt_ctx = ctx->selectUnionStmt();
|
auto select_set_stmt_ctx = ctx->selectSetStmt();
|
||||||
if (select_union_stmt_ctx) {
|
if (select_set_stmt_ctx) {
|
||||||
return visit(select_union_stmt_ctx);
|
return visit(select_set_stmt_ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto select_stmt_ctx = ctx->selectStmt();
|
auto select_stmt_ctx = ctx->selectStmt();
|
||||||
@ -851,68 +851,48 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
|
|||||||
return visitAsPyObject(placeholder_ctx);
|
return visitAsPyObject(placeholder_ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return visit(ctx->selectUnionStmt());
|
return visit(ctx->selectSetStmt());
|
||||||
}
|
}
|
||||||
|
|
||||||
VISIT(SelectUnionStmt) {
|
VISIT(SelectSetStmt) {
|
||||||
// Using a vector of PyObjects atypically here, because this is a precursor of flattened_queries
|
PyObject* initial_query = visitAsPyObject(ctx->selectStmtWithParens());
|
||||||
vector<PyObject*> select_queries;
|
PyObject* select_query = NULL;
|
||||||
auto select_stmt_with_parens_ctxs = ctx->selectStmtWithParens();
|
PyObject* select_queries = PyList_New(0);
|
||||||
select_queries.reserve(select_stmt_with_parens_ctxs.size());
|
if (!select_queries) {
|
||||||
for (auto select_stmt_with_parens_ctx : select_stmt_with_parens_ctxs) {
|
throw PyInternalError();
|
||||||
try {
|
}
|
||||||
select_queries.push_back(visitAsPyObject(select_stmt_with_parens_ctx));
|
|
||||||
} catch (...) {
|
try {
|
||||||
X_Py_DECREF_ALL(select_queries);
|
for (auto subsequent : ctx->subsequentSelectSetClause()) {
|
||||||
|
char* set_operator;
|
||||||
|
if (subsequent->UNION() && subsequent->ALL()) {
|
||||||
|
set_operator = "UNION ALL";
|
||||||
|
} else if (subsequent->INTERSECT()) {
|
||||||
|
set_operator = "INTERSECT";
|
||||||
|
} else if (subsequent->EXCEPT()) {
|
||||||
|
set_operator = "EXCEPT";
|
||||||
|
} else {
|
||||||
|
throw SyntaxError("Set operator must be one of UNION ALL, INTERSECT, and EXCEPT");
|
||||||
|
}
|
||||||
|
select_query = visitAsPyObject(subsequent->selectStmtWithParens());
|
||||||
|
PyObject* query = build_ast_node("SelectSetNode", "{s:N,s:N}", "select_query", select_query, "set_operator", PyUnicode_FromString(set_operator));
|
||||||
|
if (!query) {
|
||||||
|
throw PyInternalError();
|
||||||
|
}
|
||||||
|
PyList_Append(select_queries, query);
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
Py_DECREF(select_queries);
|
||||||
|
Py_DECREF(initial_query);
|
||||||
throw;
|
throw;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
PyObject* flattened_queries = PyList_New(0);
|
|
||||||
if (!flattened_queries) {
|
if (PyList_Size(select_queries) == 0) {
|
||||||
X_Py_DECREF_ALL(select_queries);
|
Py_DECREF(select_queries);
|
||||||
throw PyInternalError();
|
return initial_query;
|
||||||
}
|
}
|
||||||
for (auto query : select_queries) {
|
|
||||||
int is_select_query = is_ast_node_instance(query, "SelectQuery");
|
RETURN_NEW_AST_NODE("SelectSetQuery", "{s:N, s:N}", "initial_select_query", initial_query, "subsequent_select_queries", select_queries);
|
||||||
if (is_select_query == -1) goto select_queries_loop_py_error;
|
|
||||||
if (is_ast_node_instance(query, "SelectQuery")) {
|
|
||||||
int append_code = PyList_Append(flattened_queries, query);
|
|
||||||
if (append_code == -1) goto select_queries_loop_py_error;
|
|
||||||
} else if (is_ast_node_instance(query, "SelectUnionQuery")) {
|
|
||||||
// Extend flattened_queries with sub_select_queries
|
|
||||||
PyObject* sub_select_queries = PyObject_GetAttrString(query, "select_queries");
|
|
||||||
if (!sub_select_queries) goto select_queries_loop_py_error;
|
|
||||||
int extend_code = X_PyList_Extend(flattened_queries, sub_select_queries);
|
|
||||||
if (extend_code == -1) goto select_queries_loop_py_error;
|
|
||||||
Py_DECREF(sub_select_queries);
|
|
||||||
} else if (is_ast_node_instance(query, "Placeholder")) {
|
|
||||||
int append_code = PyList_Append(flattened_queries, query);
|
|
||||||
if (append_code == -1) goto select_queries_loop_py_error;
|
|
||||||
} else {
|
|
||||||
Py_DECREF(flattened_queries);
|
|
||||||
X_Py_DECREF_ALL(select_queries);
|
|
||||||
throw ParsingError("Unexpected query node type: " + string(Py_TYPE(query)->tp_name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
goto select_queries_loop_success;
|
|
||||||
select_queries_loop_py_error:
|
|
||||||
X_Py_DECREF_ALL(select_queries);
|
|
||||||
Py_DECREF(flattened_queries);
|
|
||||||
throw PyInternalError();
|
|
||||||
select_queries_loop_success:
|
|
||||||
X_Py_DECREF_ALL(select_queries);
|
|
||||||
Py_ssize_t flattened_queries_size = PyList_Size(flattened_queries);
|
|
||||||
if (flattened_queries_size == -1) {
|
|
||||||
Py_DECREF(flattened_queries);
|
|
||||||
throw PyInternalError();
|
|
||||||
}
|
|
||||||
if (flattened_queries_size == 1) {
|
|
||||||
PyObject* query = PyList_GET_ITEM(flattened_queries, 0);
|
|
||||||
Py_INCREF(query);
|
|
||||||
Py_DECREF(flattened_queries);
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
RETURN_NEW_AST_NODE("SelectUnionQuery", "{s:N}", "select_queries", flattened_queries);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VISIT(SelectStmt) {
|
VISIT(SelectStmt) {
|
||||||
@ -1632,7 +1612,7 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
|
|||||||
RETURN_NEW_AST_NODE("ArithmeticOperation", "{s:N,s:N,s:N}", "left", left, "right", right, "op", op);
|
RETURN_NEW_AST_NODE("ArithmeticOperation", "{s:N,s:N,s:N}", "left", left, "right", right, "op", op);
|
||||||
}
|
}
|
||||||
|
|
||||||
VISIT(ColumnExprSubquery) { return visit(ctx->selectUnionStmt()); }
|
VISIT(ColumnExprSubquery) { return visit(ctx->selectSetStmt()); }
|
||||||
|
|
||||||
VISIT(ColumnExprArray) {
|
VISIT(ColumnExprArray) {
|
||||||
RETURN_NEW_AST_NODE("Array", "{s:N}", "exprs", visitAsPyObjectOrEmptyList(ctx->columnExprList()));
|
RETURN_NEW_AST_NODE("Array", "{s:N}", "exprs", visitAsPyObjectOrEmptyList(ctx->columnExprList()));
|
||||||
@ -2286,7 +2266,7 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
|
|||||||
VISIT(WithExprSubquery) {
|
VISIT(WithExprSubquery) {
|
||||||
string name = visitAsString(ctx->identifier());
|
string name = visitAsString(ctx->identifier());
|
||||||
RETURN_NEW_AST_NODE(
|
RETURN_NEW_AST_NODE(
|
||||||
"CTE", "{s:s#,s:N,s:s}", "name", name.data(), name.size(), "expr", visitAsPyObject(ctx->selectUnionStmt()),
|
"CTE", "{s:s#,s:N,s:s}", "name", name.data(), name.size(), "expr", visitAsPyObject(ctx->selectSetStmt()),
|
||||||
"cte_type", "subquery"
|
"cte_type", "subquery"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2332,7 +2312,7 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
|
|||||||
RETURN_NEW_AST_NODE("Field", "{s:N}", "chain", X_PyList_FromStrings(chain));
|
RETURN_NEW_AST_NODE("Field", "{s:N}", "chain", X_PyList_FromStrings(chain));
|
||||||
}
|
}
|
||||||
|
|
||||||
VISIT(TableExprSubquery) { return visit(ctx->selectUnionStmt()); }
|
VISIT(TableExprSubquery) { return visit(ctx->selectSetStmt()); }
|
||||||
|
|
||||||
VISIT(TableExprPlaceholder) { return visitAsPyObject(ctx->placeholder()); }
|
VISIT(TableExprPlaceholder) { return visitAsPyObject(ctx->placeholder()); }
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ module = Extension(
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="hogql_parser",
|
name="hogql_parser",
|
||||||
version="1.0.45",
|
version="1.0.46",
|
||||||
url="https://github.com/PostHog/posthog/tree/master/hogql_parser",
|
url="https://github.com/PostHog/posthog/tree/master/hogql_parser",
|
||||||
author="PostHog Inc.",
|
author="PostHog Inc.",
|
||||||
author_email="hey@posthog.com",
|
author_email="hey@posthog.com",
|
||||||
|
@ -88,10 +88,10 @@ posthog/hogql/visitor.py:0: error: Incompatible types in assignment (expression
|
|||||||
posthog/hogql/visitor.py:0: error: Incompatible types in assignment (expression has type "OrderExpr", variable has type "CTE") [assignment]
|
posthog/hogql/visitor.py:0: error: Incompatible types in assignment (expression has type "OrderExpr", variable has type "CTE") [assignment]
|
||||||
posthog/hogql/visitor.py:0: error: Incompatible types in assignment (expression has type "Expr", variable has type "CTE") [assignment]
|
posthog/hogql/visitor.py:0: error: Incompatible types in assignment (expression has type "Expr", variable has type "CTE") [assignment]
|
||||||
posthog/hogql/visitor.py:0: error: Incompatible types in assignment (expression has type "WindowExpr", variable has type "CTE") [assignment]
|
posthog/hogql/visitor.py:0: error: Incompatible types in assignment (expression has type "WindowExpr", variable has type "CTE") [assignment]
|
||||||
posthog/hogql/visitor.py:0: error: Incompatible types in assignment (expression has type "FieldAliasType", variable has type "BaseTableType | SelectUnionQueryType | SelectQueryType | SelectQueryAliasType | SelectViewType") [assignment]
|
posthog/hogql/visitor.py:0: error: Incompatible types in assignment (expression has type "FieldAliasType", variable has type "BaseTableType | SelectSetQueryType | SelectQueryType | SelectQueryAliasType | SelectViewType") [assignment]
|
||||||
posthog/hogql/visitor.py:0: error: Incompatible types in assignment (expression has type "Type", variable has type "BaseTableType | SelectUnionQueryType | SelectQueryType | SelectQueryAliasType | SelectViewType") [assignment]
|
posthog/hogql/visitor.py:0: error: Incompatible types in assignment (expression has type "Type", variable has type "BaseTableType | SelectSetQueryType | SelectQueryType | SelectQueryAliasType | SelectViewType") [assignment]
|
||||||
posthog/models/filters/mixins/simplify.py:0: error: Incompatible type for lookup 'pk': (got "str | int | list[str]", expected "str | int") [misc]
|
posthog/models/filters/mixins/simplify.py:0: error: Incompatible type for lookup 'pk': (got "str | int | list[str]", expected "str | int") [misc]
|
||||||
posthog/hogql/resolver_utils.py:0: error: Argument 1 to "lookup_field_by_name" has incompatible type "SelectQueryType | SelectUnionQueryType"; expected "SelectQueryType" [arg-type]
|
posthog/hogql/resolver_utils.py:0: error: Argument 1 to "lookup_field_by_name" has incompatible type "SelectQueryType | SelectSetQueryType"; expected "SelectQueryType" [arg-type]
|
||||||
posthog/helpers/dashboard_templates.py:0: error: Incompatible types in assignment (expression has type "str | None", variable has type "str | Combinable") [assignment]
|
posthog/helpers/dashboard_templates.py:0: error: Incompatible types in assignment (expression has type "str | None", variable has type "str | Combinable") [assignment]
|
||||||
posthog/hogql/parser.py:0: error: Item "None" of "list[Expr] | None" has no attribute "__iter__" (not iterable) [union-attr]
|
posthog/hogql/parser.py:0: error: Item "None" of "list[Expr] | None" has no attribute "__iter__" (not iterable) [union-attr]
|
||||||
posthog/hogql/parser.py:0: error: "None" has no attribute "text" [attr-defined]
|
posthog/hogql/parser.py:0: error: "None" has no attribute "text" [attr-defined]
|
||||||
@ -161,23 +161,22 @@ posthog/hogql_queries/utils/query_date_range.py:0: error: Incompatible default f
|
|||||||
posthog/hogql_queries/utils/query_date_range.py:0: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
|
posthog/hogql_queries/utils/query_date_range.py:0: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
|
||||||
posthog/hogql_queries/utils/query_date_range.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
|
posthog/hogql_queries/utils/query_date_range.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
|
||||||
posthog/hogql_queries/utils/query_date_range.py:0: error: Item "None" of "IntervalType | None" has no attribute "name" [union-attr]
|
posthog/hogql_queries/utils/query_date_range.py:0: error: Item "None" of "IntervalType | None" has no attribute "name" [union-attr]
|
||||||
posthog/hogql/resolver.py:0: error: List comprehension has incompatible type List[SelectQueryType | None]; expected List[SelectQueryType] [misc]
|
|
||||||
posthog/hogql/resolver.py:0: error: Need type annotation for "columns_with_visible_alias" (hint: "columns_with_visible_alias: dict[<type>, <type>] = ...") [var-annotated]
|
posthog/hogql/resolver.py:0: error: Need type annotation for "columns_with_visible_alias" (hint: "columns_with_visible_alias: dict[<type>, <type>] = ...") [var-annotated]
|
||||||
posthog/hogql/resolver.py:0: error: Statement is unreachable [unreachable]
|
posthog/hogql/resolver.py:0: error: Statement is unreachable [unreachable]
|
||||||
posthog/hogql/resolver.py:0: error: Incompatible types in assignment (expression has type "Expr", variable has type "SelectQuery | SelectUnionQuery | Field | None") [assignment]
|
posthog/hogql/resolver.py:0: error: Incompatible types in assignment (expression has type "Expr", variable has type "SelectQuery | SelectSetQuery | Field | None") [assignment]
|
||||||
posthog/hogql/resolver.py:0: error: Item "None" of "Database | None" has no attribute "get_table" [union-attr]
|
posthog/hogql/resolver.py:0: error: Item "None" of "Database | None" has no attribute "get_table" [union-attr]
|
||||||
posthog/hogql/resolver.py:0: error: Incompatible types in assignment (expression has type "TableType", variable has type "LazyTableType") [assignment]
|
posthog/hogql/resolver.py:0: error: Incompatible types in assignment (expression has type "TableType", variable has type "LazyTableType") [assignment]
|
||||||
posthog/hogql/resolver.py:0: error: Argument "table_type" to "TableAliasType" has incompatible type "LazyTableType"; expected "TableType" [arg-type]
|
posthog/hogql/resolver.py:0: error: Argument "table_type" to "TableAliasType" has incompatible type "LazyTableType"; expected "TableType" [arg-type]
|
||||||
posthog/hogql/resolver.py:0: error: Incompatible types in assignment (expression has type "LazyTableType", variable has type "TableAliasType") [assignment]
|
posthog/hogql/resolver.py:0: error: Incompatible types in assignment (expression has type "LazyTableType", variable has type "TableAliasType") [assignment]
|
||||||
posthog/hogql/resolver.py:0: error: Argument 1 to "clone_expr" has incompatible type "SelectQuery | SelectUnionQuery | Field | None"; expected "Expr" [arg-type]
|
posthog/hogql/resolver.py:0: error: Argument 1 to "clone_expr" has incompatible type "SelectQuery | SelectSetQuery | Field | None"; expected "Expr" [arg-type]
|
||||||
posthog/hogql/resolver.py:0: error: Statement is unreachable [unreachable]
|
posthog/hogql/resolver.py:0: error: Statement is unreachable [unreachable]
|
||||||
posthog/hogql/resolver.py:0: error: Item "None" of "JoinExpr | None" has no attribute "join_type" [union-attr]
|
posthog/hogql/resolver.py:0: error: Item "None" of "JoinExpr | None" has no attribute "join_type" [union-attr]
|
||||||
posthog/hogql/resolver.py:0: error: Argument "select_query_type" to "SelectViewType" has incompatible type "SelectQueryType | None"; expected "SelectQueryType | SelectUnionQueryType" [arg-type]
|
posthog/hogql/resolver.py:0: error: Argument "select_query_type" to "SelectViewType" has incompatible type "SelectQueryType | None"; expected "SelectQueryType | SelectSetQueryType" [arg-type]
|
||||||
posthog/hogql/resolver.py:0: error: Item "None" of "SelectQuery | SelectUnionQuery | Field | None" has no attribute "type" [union-attr]
|
posthog/hogql/resolver.py:0: error: Item "None" of "SelectQuery | SelectSetQuery | Field | None" has no attribute "type" [union-attr]
|
||||||
posthog/hogql/resolver.py:0: error: Argument "select_query_type" to "SelectQueryAliasType" has incompatible type "Type | Any | None"; expected "SelectQueryType | SelectUnionQueryType" [arg-type]
|
posthog/hogql/resolver.py:0: error: Argument "select_query_type" to "SelectQueryAliasType" has incompatible type "Type | Any | None"; expected "SelectQueryType | SelectSetQueryType" [arg-type]
|
||||||
posthog/hogql/resolver.py:0: error: Item "None" of "SelectQuery | SelectUnionQuery | Field | None" has no attribute "type" [union-attr]
|
posthog/hogql/resolver.py:0: error: Item "None" of "SelectQuery | SelectSetQuery | Field | None" has no attribute "type" [union-attr]
|
||||||
posthog/hogql/resolver.py:0: error: Incompatible types in assignment (expression has type "Type | Any | None", variable has type "BaseTableType | SelectUnionQueryType | SelectQueryType | SelectQueryAliasType | SelectViewType | None") [assignment]
|
posthog/hogql/resolver.py:0: error: Incompatible types in assignment (expression has type "Type | Any | None", variable has type "BaseTableType | SelectSetQueryType | SelectQueryType | SelectQueryAliasType | SelectViewType | None") [assignment]
|
||||||
posthog/hogql/resolver.py:0: error: Argument 1 to "append" of "list" has incompatible type "BaseTableType | SelectUnionQueryType | SelectQueryType | SelectQueryAliasType | SelectViewType | None"; expected "SelectQueryType | SelectUnionQueryType" [arg-type]
|
posthog/hogql/resolver.py:0: error: Argument 1 to "append" of "list" has incompatible type "BaseTableType | SelectSetQueryType | SelectQueryType | SelectQueryAliasType | SelectViewType | None"; expected "SelectQueryType | SelectSetQueryType" [arg-type]
|
||||||
posthog/hogql/resolver.py:0: error: Statement is unreachable [unreachable]
|
posthog/hogql/resolver.py:0: error: Statement is unreachable [unreachable]
|
||||||
posthog/hogql/resolver.py:0: error: Statement is unreachable [unreachable]
|
posthog/hogql/resolver.py:0: error: Statement is unreachable [unreachable]
|
||||||
posthog/hogql/resolver.py:0: error: Item "None" of "Type | None" has no attribute "resolve_constant_type" [union-attr]
|
posthog/hogql/resolver.py:0: error: Item "None" of "Type | None" has no attribute "resolve_constant_type" [union-attr]
|
||||||
@ -193,12 +192,12 @@ posthog/hogql_queries/insights/trends/aggregation_operations.py:0: note: "List"
|
|||||||
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: note: Consider using "Sequence" instead, which is covariant
|
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: note: Consider using "Sequence" instead, which is covariant
|
||||||
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: List item 1 has incompatible type "str | None"; expected "str" [list-item]
|
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: List item 1 has incompatible type "str | None"; expected "str" [list-item]
|
||||||
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: Argument "chain" to "Field" has incompatible type "list[str | int] | list[str]"; expected "list[str | int]" [arg-type]
|
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: Argument "chain" to "Field" has incompatible type "list[str | int] | list[str]"; expected "list[str | int]" [arg-type]
|
||||||
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: Item "SelectUnionQuery" of "SelectQuery | SelectUnionQuery" has no attribute "select" [union-attr]
|
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: Item "SelectSetQuery" of "SelectQuery | SelectSetQuery" has no attribute "select" [union-attr]
|
||||||
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: Item "SelectUnionQuery" of "SelectQuery | SelectUnionQuery" has no attribute "select" [union-attr]
|
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: Item "SelectSetQuery" of "SelectQuery | SelectSetQuery" has no attribute "select" [union-attr]
|
||||||
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: Item "SelectUnionQuery" of "SelectQuery | SelectUnionQuery" has no attribute "group_by" [union-attr]
|
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: Item "SelectSetQuery" of "SelectQuery | SelectSetQuery" has no attribute "group_by" [union-attr]
|
||||||
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: Item "None" of "list[Expr] | None" has no attribute "append" [union-attr]
|
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: Item "None" of "list[Expr] | None" has no attribute "append" [union-attr]
|
||||||
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: Item "SelectUnionQuery" of "SelectQuery | SelectUnionQuery" has no attribute "select" [union-attr]
|
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: Item "SelectSetQuery" of "SelectQuery | SelectSetQuery" has no attribute "select" [union-attr]
|
||||||
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: Item "SelectUnionQuery" of "SelectQuery | SelectUnionQuery" has no attribute "group_by" [union-attr]
|
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: Item "SelectSetQuery" of "SelectQuery | SelectSetQuery" has no attribute "group_by" [union-attr]
|
||||||
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: Item "None" of "list[Expr] | Any | None" has no attribute "append" [union-attr]
|
posthog/hogql_queries/insights/trends/aggregation_operations.py:0: error: Item "None" of "list[Expr] | Any | None" has no attribute "append" [union-attr]
|
||||||
posthog/hogql/transforms/lazy_tables.py:0: error: Incompatible types in assignment (expression has type "dict[Never, Never]", variable has type "list[ConstraintOverride]") [assignment]
|
posthog/hogql/transforms/lazy_tables.py:0: error: Incompatible types in assignment (expression has type "dict[Never, Never]", variable has type "list[ConstraintOverride]") [assignment]
|
||||||
posthog/hogql/transforms/lazy_tables.py:0: error: Non-overlapping equality check (left operand type: "TableType", right operand type: "LazyTableType") [comparison-overlap]
|
posthog/hogql/transforms/lazy_tables.py:0: error: Non-overlapping equality check (left operand type: "TableType", right operand type: "LazyTableType") [comparison-overlap]
|
||||||
@ -206,7 +205,7 @@ posthog/hogql/transforms/lazy_tables.py:0: error: Non-overlapping equality check
|
|||||||
posthog/hogql/transforms/lazy_tables.py:0: error: Name "chain" already defined on line 0 [no-redef]
|
posthog/hogql/transforms/lazy_tables.py:0: error: Name "chain" already defined on line 0 [no-redef]
|
||||||
posthog/hogql/transforms/lazy_tables.py:0: error: Subclass of "TableType" and "LazyTableType" cannot exist: would have incompatible method signatures [unreachable]
|
posthog/hogql/transforms/lazy_tables.py:0: error: Subclass of "TableType" and "LazyTableType" cannot exist: would have incompatible method signatures [unreachable]
|
||||||
posthog/hogql/transforms/lazy_tables.py:0: error: Statement is unreachable [unreachable]
|
posthog/hogql/transforms/lazy_tables.py:0: error: Statement is unreachable [unreachable]
|
||||||
posthog/hogql/transforms/lazy_tables.py:0: error: Incompatible types in assignment (expression has type "BaseTableType | SelectUnionQueryType | SelectQueryType | SelectQueryAliasType | SelectViewType", variable has type "SelectQueryAliasType | None") [assignment]
|
posthog/hogql/transforms/lazy_tables.py:0: error: Incompatible types in assignment (expression has type "BaseTableType | SelectSetQueryType | SelectQueryType | SelectQueryAliasType | SelectViewType", variable has type "SelectQueryAliasType | None") [assignment]
|
||||||
posthog/hogql/transforms/in_cohort.py:0: error: Incompatible default for argument "context" (default has type "None", argument has type "HogQLContext") [assignment]
|
posthog/hogql/transforms/in_cohort.py:0: error: Incompatible default for argument "context" (default has type "None", argument has type "HogQLContext") [assignment]
|
||||||
posthog/hogql/transforms/in_cohort.py:0: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
|
posthog/hogql/transforms/in_cohort.py:0: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
|
||||||
posthog/hogql/transforms/in_cohort.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
|
posthog/hogql/transforms/in_cohort.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
|
||||||
@ -217,7 +216,7 @@ posthog/hogql/transforms/in_cohort.py:0: note: PEP 484 prohibits implicit Option
|
|||||||
posthog/hogql/transforms/in_cohort.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
|
posthog/hogql/transforms/in_cohort.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
|
||||||
posthog/hogql/transforms/in_cohort.py:0: error: Incompatible type for lookup 'team_id': (got "int | None", expected "str | int") [misc]
|
posthog/hogql/transforms/in_cohort.py:0: error: Incompatible type for lookup 'team_id': (got "int | None", expected "str | int") [misc]
|
||||||
posthog/hogql/transforms/in_cohort.py:0: error: Incompatible type for lookup 'team_id': (got "int | None", expected "str | int") [misc]
|
posthog/hogql/transforms/in_cohort.py:0: error: Incompatible type for lookup 'team_id': (got "int | None", expected "str | int") [misc]
|
||||||
posthog/hogql/transforms/in_cohort.py:0: error: Argument "table" to "JoinExpr" has incompatible type "Expr"; expected "SelectQuery | SelectUnionQuery | Field | None" [arg-type]
|
posthog/hogql/transforms/in_cohort.py:0: error: Argument "table" to "JoinExpr" has incompatible type "Expr"; expected "SelectQuery | SelectSetQuery | Field | None" [arg-type]
|
||||||
posthog/hogql/transforms/in_cohort.py:0: error: List item 0 has incompatible type "SelectQueryType | None"; expected "SelectQueryType" [list-item]
|
posthog/hogql/transforms/in_cohort.py:0: error: List item 0 has incompatible type "SelectQueryType | None"; expected "SelectQueryType" [list-item]
|
||||||
posthog/hogql/transforms/in_cohort.py:0: error: Item "None" of "JoinConstraint | None" has no attribute "expr" [union-attr]
|
posthog/hogql/transforms/in_cohort.py:0: error: Item "None" of "JoinConstraint | None" has no attribute "expr" [union-attr]
|
||||||
posthog/hogql/transforms/in_cohort.py:0: error: Item "Expr" of "Expr | Any" has no attribute "left" [union-attr]
|
posthog/hogql/transforms/in_cohort.py:0: error: Item "Expr" of "Expr | Any" has no attribute "left" [union-attr]
|
||||||
@ -332,17 +331,16 @@ posthog/hogql/filters.py:0: error: Incompatible default for argument "team" (def
|
|||||||
posthog/hogql/filters.py:0: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
|
posthog/hogql/filters.py:0: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
|
||||||
posthog/hogql/filters.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
|
posthog/hogql/filters.py:0: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
|
||||||
posthog/api/organization.py:0: error: Incompatible return value type (got "int | None", expected "Level | None") [return-value]
|
posthog/api/organization.py:0: error: Incompatible return value type (got "int | None", expected "Level | None") [return-value]
|
||||||
posthog/hogql/query.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "str | SelectQuery | SelectUnionQuery") [assignment]
|
posthog/hogql/query.py:0: error: Incompatible types in assignment (expression has type "None", variable has type "str | SelectQuery | SelectSetQuery") [assignment]
|
||||||
posthog/hogql/query.py:0: error: Incompatible types in assignment (expression has type "Expr", variable has type "SelectQuery | SelectUnionQuery") [assignment]
|
posthog/hogql/query.py:0: error: Incompatible types in assignment (expression has type "Expr", variable has type "SelectQuery | SelectSetQuery") [assignment]
|
||||||
posthog/hogql/query.py:0: error: Argument 1 to "get_default_limit_for_context" has incompatible type "LimitContext | None"; expected "LimitContext" [arg-type]
|
posthog/hogql/query.py:0: error: Argument 1 to "get_default_limit_for_context" has incompatible type "LimitContext | None"; expected "LimitContext" [arg-type]
|
||||||
posthog/hogql/query.py:0: error: "SelectQuery" has no attribute "select_queries" [attr-defined]
|
posthog/hogql/query.py:0: error: Subclass of "SelectQuery" and "SelectSetQuery" cannot exist: would have incompatible method signatures [unreachable]
|
||||||
posthog/hogql/query.py:0: error: Subclass of "SelectQuery" and "SelectUnionQuery" cannot exist: would have incompatible method signatures [unreachable]
|
|
||||||
posthog/queries/person_query.py:0: error: Incompatible type for lookup 'pk': (got "str | int | list[str]", expected "str | int") [misc]
|
posthog/queries/person_query.py:0: error: Incompatible type for lookup 'pk': (got "str | int | list[str]", expected "str | int") [misc]
|
||||||
posthog/api/action.py:0: error: Argument 1 to <tuple> has incompatible type "*tuple[str, ...]"; expected "type[BaseRenderer]" [arg-type]
|
posthog/api/action.py:0: error: Argument 1 to <tuple> has incompatible type "*tuple[str, ...]"; expected "type[BaseRenderer]" [arg-type]
|
||||||
posthog/queries/event_query/event_query.py:0: error: Incompatible type for lookup 'pk': (got "str | int | list[str]", expected "str | int") [misc]
|
posthog/queries/event_query/event_query.py:0: error: Incompatible type for lookup 'pk': (got "str | int | list[str]", expected "str | int") [misc]
|
||||||
posthog/hogql_queries/sessions_timeline_query_runner.py:0: error: Statement is unreachable [unreachable]
|
posthog/hogql_queries/sessions_timeline_query_runner.py:0: error: Statement is unreachable [unreachable]
|
||||||
posthog/hogql_queries/hogql_query_runner.py:0: error: Statement is unreachable [unreachable]
|
posthog/hogql_queries/hogql_query_runner.py:0: error: Statement is unreachable [unreachable]
|
||||||
posthog/hogql_queries/hogql_query_runner.py:0: error: Incompatible return value type (got "SelectQuery | SelectUnionQuery", expected "SelectQuery") [return-value]
|
posthog/hogql_queries/hogql_query_runner.py:0: error: Incompatible return value type (got "SelectQuery | SelectSetQuery", expected "SelectQuery") [return-value]
|
||||||
posthog/hogql_queries/events_query_runner.py:0: error: Statement is unreachable [unreachable]
|
posthog/hogql_queries/events_query_runner.py:0: error: Statement is unreachable [unreachable]
|
||||||
posthog/queries/breakdown_props.py:0: error: Argument 1 to "translate_hogql" has incompatible type "str | int"; expected "str" [arg-type]
|
posthog/queries/breakdown_props.py:0: error: Argument 1 to "translate_hogql" has incompatible type "str | int"; expected "str" [arg-type]
|
||||||
posthog/queries/breakdown_props.py:0: error: Incompatible type for lookup 'pk': (got "str | None", expected "str | int") [misc]
|
posthog/queries/breakdown_props.py:0: error: Incompatible type for lookup 'pk': (got "str | None", expected "str | int") [misc]
|
||||||
@ -386,15 +384,15 @@ posthog/hogql_queries/insights/stickiness_query_runner.py:0: error: Module "djan
|
|||||||
posthog/hogql_queries/insights/retention_query_runner.py:0: error: Item "None" of "JoinExpr | None" has no attribute "sample" [union-attr]
|
posthog/hogql_queries/insights/retention_query_runner.py:0: error: Item "None" of "JoinExpr | None" has no attribute "sample" [union-attr]
|
||||||
posthog/hogql_queries/insights/retention_query_runner.py:0: error: Unsupported operand types for - ("int" and "None") [operator]
|
posthog/hogql_queries/insights/retention_query_runner.py:0: error: Unsupported operand types for - ("int" and "None") [operator]
|
||||||
posthog/hogql_queries/insights/retention_query_runner.py:0: note: Right operand is of type "int | None"
|
posthog/hogql_queries/insights/retention_query_runner.py:0: note: Right operand is of type "int | None"
|
||||||
posthog/hogql_queries/insights/retention_query_runner.py:0: error: Item "SelectUnionQuery" of "SelectQuery | SelectUnionQuery" has no attribute "select" [union-attr]
|
posthog/hogql_queries/insights/retention_query_runner.py:0: error: Item "SelectSetQuery" of "SelectQuery | SelectSetQuery" has no attribute "select" [union-attr]
|
||||||
posthog/hogql_queries/insights/retention_query_runner.py:0: error: Incompatible return value type (got "SelectQuery | SelectUnionQuery", expected "SelectQuery") [return-value]
|
posthog/hogql_queries/insights/retention_query_runner.py:0: error: Incompatible return value type (got "SelectQuery | SelectSetQuery", expected "SelectQuery") [return-value]
|
||||||
posthog/hogql_queries/insights/lifecycle_query_runner.py:0: error: Module "django.utils.timezone" does not explicitly export attribute "datetime" [attr-defined]
|
posthog/hogql_queries/insights/lifecycle_query_runner.py:0: error: Module "django.utils.timezone" does not explicitly export attribute "datetime" [attr-defined]
|
||||||
posthog/hogql_queries/insights/lifecycle_query_runner.py:0: error: Incompatible types in assignment (expression has type "Expr", variable has type "Constant") [assignment]
|
posthog/hogql_queries/insights/lifecycle_query_runner.py:0: error: Incompatible types in assignment (expression has type "Expr", variable has type "Constant") [assignment]
|
||||||
posthog/hogql_queries/insights/lifecycle_query_runner.py:0: error: Incompatible types in assignment (expression has type "Expr", variable has type "Constant") [assignment]
|
posthog/hogql_queries/insights/lifecycle_query_runner.py:0: error: Incompatible types in assignment (expression has type "Expr", variable has type "Constant") [assignment]
|
||||||
posthog/hogql_queries/insights/lifecycle_query_runner.py:0: error: Argument "exprs" to "And" has incompatible type "list[CompareOperation]"; expected "list[Expr]" [arg-type]
|
posthog/hogql_queries/insights/lifecycle_query_runner.py:0: error: Argument "exprs" to "And" has incompatible type "list[CompareOperation]"; expected "list[Expr]" [arg-type]
|
||||||
posthog/hogql_queries/insights/lifecycle_query_runner.py:0: note: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance
|
posthog/hogql_queries/insights/lifecycle_query_runner.py:0: note: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance
|
||||||
posthog/hogql_queries/insights/lifecycle_query_runner.py:0: note: Consider using "Sequence" instead, which is covariant
|
posthog/hogql_queries/insights/lifecycle_query_runner.py:0: note: Consider using "Sequence" instead, which is covariant
|
||||||
posthog/hogql_queries/insights/lifecycle_query_runner.py:0: error: Item "SelectUnionQuery" of "SelectQuery | SelectUnionQuery" has no attribute "select_from" [union-attr]
|
posthog/hogql_queries/insights/lifecycle_query_runner.py:0: error: Item "SelectSetQuery" of "SelectQuery | SelectSetQuery" has no attribute "select_from" [union-attr]
|
||||||
posthog/hogql_queries/insights/lifecycle_query_runner.py:0: error: Item "None" of "JoinExpr | Any | None" has no attribute "sample" [union-attr]
|
posthog/hogql_queries/insights/lifecycle_query_runner.py:0: error: Item "None" of "JoinExpr | Any | None" has no attribute "sample" [union-attr]
|
||||||
posthog/hogql_queries/insights/funnels/funnels_query_runner.py:0: error: Module "django.utils.timezone" does not explicitly export attribute "datetime" [attr-defined]
|
posthog/hogql_queries/insights/funnels/funnels_query_runner.py:0: error: Module "django.utils.timezone" does not explicitly export attribute "datetime" [attr-defined]
|
||||||
posthog/api/survey.py:0: error: Incompatible types in assignment (expression has type "Any | Sequence[Any] | None", variable has type "Survey | None") [assignment]
|
posthog/api/survey.py:0: error: Incompatible types in assignment (expression has type "Any | Sequence[Any] | None", variable has type "Survey | None") [assignment]
|
||||||
@ -503,19 +501,19 @@ posthog/hogql/test/test_resolver.py:0: error: Item "None" of "JoinConstraint | A
|
|||||||
posthog/hogql/test/test_resolver.py:0: error: Item "None" of "JoinExpr | None" has no attribute "next_join" [union-attr]
|
posthog/hogql/test/test_resolver.py:0: error: Item "None" of "JoinExpr | None" has no attribute "next_join" [union-attr]
|
||||||
posthog/hogql/test/test_resolver.py:0: error: Item "None" of "JoinExpr | Any | None" has no attribute "constraint" [union-attr]
|
posthog/hogql/test/test_resolver.py:0: error: Item "None" of "JoinExpr | Any | None" has no attribute "constraint" [union-attr]
|
||||||
posthog/hogql/test/test_resolver.py:0: error: Item "None" of "JoinConstraint | Any | None" has no attribute "constraint_type" [union-attr]
|
posthog/hogql/test/test_resolver.py:0: error: Item "None" of "JoinConstraint | Any | None" has no attribute "constraint_type" [union-attr]
|
||||||
posthog/hogql/test/test_resolver.py:0: error: Item "SelectUnionQueryType" of "SelectQueryType | SelectUnionQueryType | None" has no attribute "columns" [union-attr]
|
posthog/hogql/test/test_resolver.py:0: error: Item "SelectSetQueryType" of "SelectQueryType | SelectSetQueryType | None" has no attribute "columns" [union-attr]
|
||||||
posthog/hogql/test/test_resolver.py:0: error: Item "None" of "SelectQueryType | SelectUnionQueryType | None" has no attribute "columns" [union-attr]
|
posthog/hogql/test/test_resolver.py:0: error: Item "None" of "SelectQueryType | SelectSetQueryType | None" has no attribute "columns" [union-attr]
|
||||||
posthog/hogql/test/test_resolver.py:0: error: "FieldOrTable" has no attribute "fields" [attr-defined]
|
posthog/hogql/test/test_resolver.py:0: error: "FieldOrTable" has no attribute "fields" [attr-defined]
|
||||||
posthog/hogql/test/test_resolver.py:0: error: "FieldOrTable" has no attribute "fields" [attr-defined]
|
posthog/hogql/test/test_resolver.py:0: error: "FieldOrTable" has no attribute "fields" [attr-defined]
|
||||||
posthog/hogql/test/test_resolver.py:0: error: "FieldOrTable" has no attribute "fields" [attr-defined]
|
posthog/hogql/test/test_resolver.py:0: error: "FieldOrTable" has no attribute "fields" [attr-defined]
|
||||||
posthog/hogql/test/test_resolver.py:0: error: Item "None" of "JoinExpr | None" has no attribute "table" [union-attr]
|
posthog/hogql/test/test_resolver.py:0: error: Item "None" of "JoinExpr | None" has no attribute "table" [union-attr]
|
||||||
posthog/hogql/test/test_resolver.py:0: error: Argument 1 to "clone_expr" has incompatible type "SelectQuery | SelectUnionQuery | Field | Any | None"; expected "Expr" [arg-type]
|
posthog/hogql/test/test_resolver.py:0: error: Argument 1 to "clone_expr" has incompatible type "SelectQuery | SelectSetQuery | Field | Any | None"; expected "Expr" [arg-type]
|
||||||
posthog/hogql/test/test_resolver.py:0: error: Item "None" of "JoinExpr | None" has no attribute "alias" [union-attr]
|
posthog/hogql/test/test_resolver.py:0: error: Item "None" of "JoinExpr | None" has no attribute "alias" [union-attr]
|
||||||
posthog/hogql/test/test_property.py:0: error: Argument 1 to "_property_to_expr" of "TestProperty" has incompatible type "HogQLPropertyFilter"; expected "PropertyGroup | Property | dict[Any, Any] | list[Any]" [arg-type]
|
posthog/hogql/test/test_property.py:0: error: Argument 1 to "_property_to_expr" of "TestProperty" has incompatible type "HogQLPropertyFilter"; expected "PropertyGroup | Property | dict[Any, Any] | list[Any]" [arg-type]
|
||||||
posthog/hogql/test/test_printer.py:0: error: Argument 2 to "Database" has incompatible type "int"; expected "WeekStartDay | None" [arg-type]
|
posthog/hogql/test/test_printer.py:0: error: Argument 2 to "Database" has incompatible type "int"; expected "WeekStartDay | None" [arg-type]
|
||||||
posthog/hogql/test/test_printer.py:0: error: Argument 2 to "Database" has incompatible type "int"; expected "WeekStartDay | None" [arg-type]
|
posthog/hogql/test/test_printer.py:0: error: Argument 2 to "Database" has incompatible type "int"; expected "WeekStartDay | None" [arg-type]
|
||||||
posthog/hogql/test/test_printer.py:0: error: Item "SelectUnionQuery" of "SelectQuery | SelectUnionQuery" has no attribute "settings" [union-attr]
|
posthog/hogql/test/test_printer.py:0: error: Item "SelectSetQuery" of "SelectQuery | SelectSetQuery" has no attribute "settings" [union-attr]
|
||||||
posthog/hogql/test/test_printer.py:0: error: Item "SelectUnionQuery" of "SelectQuery | SelectUnionQuery" has no attribute "settings" [union-attr]
|
posthog/hogql/test/test_printer.py:0: error: Item "SelectSetQuery" of "SelectQuery | SelectSetQuery" has no attribute "settings" [union-attr]
|
||||||
posthog/hogql/test/test_printer.py:0: error: "TestPrinter" has no attribute "snapshot" [attr-defined]
|
posthog/hogql/test/test_printer.py:0: error: "TestPrinter" has no attribute "snapshot" [attr-defined]
|
||||||
posthog/hogql/test/test_modifiers.py:0: error: Unsupported right operand type for in ("str | None") [operator]
|
posthog/hogql/test/test_modifiers.py:0: error: Unsupported right operand type for in ("str | None") [operator]
|
||||||
posthog/hogql/test/test_modifiers.py:0: error: Unsupported right operand type for in ("str | None") [operator]
|
posthog/hogql/test/test_modifiers.py:0: error: Unsupported right operand type for in ("str | None") [operator]
|
||||||
@ -531,7 +529,7 @@ posthog/hogql/test/test_modifiers.py:0: error: Unsupported right operand type fo
|
|||||||
posthog/hogql/test/test_modifiers.py:0: error: Unsupported right operand type for in ("str | None") [operator]
|
posthog/hogql/test/test_modifiers.py:0: error: Unsupported right operand type for in ("str | None") [operator]
|
||||||
posthog/hogql/test/test_modifiers.py:0: error: Unsupported right operand type for in ("str | None") [operator]
|
posthog/hogql/test/test_modifiers.py:0: error: Unsupported right operand type for in ("str | None") [operator]
|
||||||
posthog/hogql/test/_test_parser.py:0: error: Invalid base class [misc]
|
posthog/hogql/test/_test_parser.py:0: error: Invalid base class [misc]
|
||||||
posthog/hogql/test/_test_parser.py:0: error: Argument "table" to "JoinExpr" has incompatible type "Placeholder"; expected "SelectQuery | SelectUnionQuery | Field | None" [arg-type]
|
posthog/hogql/test/_test_parser.py:0: error: Argument "table" to "JoinExpr" has incompatible type "Placeholder"; expected "SelectQuery | SelectSetQuery | Field | None" [arg-type]
|
||||||
posthog/hogql/test/_test_parser.py:0: error: Item "None" of "JoinExpr | None" has no attribute "table" [union-attr]
|
posthog/hogql/test/_test_parser.py:0: error: Item "None" of "JoinExpr | None" has no attribute "table" [union-attr]
|
||||||
posthog/hogql/test/_test_parser.py:0: error: Item "None" of "JoinExpr | None" has no attribute "table" [union-attr]
|
posthog/hogql/test/_test_parser.py:0: error: Item "None" of "JoinExpr | None" has no attribute "table" [union-attr]
|
||||||
posthog/hogql/test/_test_parser.py:0: error: Item "None" of "JoinExpr | None" has no attribute "table" [union-attr]
|
posthog/hogql/test/_test_parser.py:0: error: Item "None" of "JoinExpr | None" has no attribute "table" [union-attr]
|
||||||
|
@ -371,35 +371,36 @@ class TestPluginAPI(APIBaseTest, QueryMatchingTest):
|
|||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
self.assertEqual(mock_sync_from_plugin_archive.call_count, 2) # Not extracted on auth failure
|
self.assertEqual(mock_sync_from_plugin_archive.call_count, 2) # Not extracted on auth failure
|
||||||
|
|
||||||
@freeze_time("2021-08-25T22:09:14.252Z")
|
|
||||||
def test_delete_plugin_auth(self, mock_get, mock_reload):
|
def test_delete_plugin_auth(self, mock_get, mock_reload):
|
||||||
repo_url = "https://github.com/PostHog/helloworldplugin"
|
with freeze_time("2021-08-25T22:09:14.252Z"):
|
||||||
response = self.client.post("/api/organizations/@current/plugins/", {"url": repo_url})
|
repo_url = "https://github.com/PostHog/helloworldplugin"
|
||||||
self.assertEqual(response.status_code, 201)
|
response = self.client.post("/api/organizations/@current/plugins/", {"url": repo_url})
|
||||||
|
self.assertEqual(response.status_code, 201)
|
||||||
|
|
||||||
plugin_id = response.json()["id"]
|
with freeze_time("2021-08-25T22:09:14.253Z"):
|
||||||
|
plugin_id = response.json()["id"]
|
||||||
|
|
||||||
api_url = "/api/organizations/@current/plugins/{}".format(response.json()["id"])
|
api_url = "/api/organizations/@current/plugins/{}".format(response.json()["id"])
|
||||||
|
|
||||||
for level in (
|
for level in (
|
||||||
Organization.PluginsAccessLevel.NONE,
|
Organization.PluginsAccessLevel.NONE,
|
||||||
Organization.PluginsAccessLevel.CONFIG,
|
Organization.PluginsAccessLevel.CONFIG,
|
||||||
):
|
):
|
||||||
self.organization.plugins_access_level = level
|
self.organization.plugins_access_level = level
|
||||||
|
self.organization.save()
|
||||||
|
response = self.client.delete(api_url)
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
|
self.organization.plugins_access_level = Organization.PluginsAccessLevel.INSTALL
|
||||||
self.organization.save()
|
self.organization.save()
|
||||||
response = self.client.delete(api_url)
|
response = self.client.delete(api_url)
|
||||||
self.assertEqual(response.status_code, 403)
|
|
||||||
|
|
||||||
self.organization.plugins_access_level = Organization.PluginsAccessLevel.INSTALL
|
|
||||||
self.organization.save()
|
|
||||||
response = self.client.delete(api_url)
|
|
||||||
self.assertEqual(response.status_code, 204)
|
self.assertEqual(response.status_code, 204)
|
||||||
self.assert_plugin_activity(
|
self.assert_plugin_activity(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"user": {"first_name": "", "email": "user1@posthog.com"},
|
"user": {"first_name": "", "email": "user1@posthog.com"},
|
||||||
"activity": "installed",
|
"activity": "uninstalled",
|
||||||
"created_at": "2021-08-25T22:09:14.252000Z",
|
"created_at": "2021-08-25T22:09:14.253000Z",
|
||||||
"scope": "Plugin",
|
"scope": "Plugin",
|
||||||
"item_id": str(plugin_id),
|
"item_id": str(plugin_id),
|
||||||
"detail": {
|
"detail": {
|
||||||
@ -412,7 +413,7 @@ class TestPluginAPI(APIBaseTest, QueryMatchingTest):
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"user": {"first_name": "", "email": "user1@posthog.com"},
|
"user": {"first_name": "", "email": "user1@posthog.com"},
|
||||||
"activity": "uninstalled",
|
"activity": "installed",
|
||||||
"created_at": "2021-08-25T22:09:14.252000Z",
|
"created_at": "2021-08-25T22:09:14.252000Z",
|
||||||
"scope": "Plugin",
|
"scope": "Plugin",
|
||||||
"item_id": str(plugin_id),
|
"item_id": str(plugin_id),
|
||||||
|
@ -183,7 +183,7 @@ class BatchExportDestinationSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
|
|
||||||
class HogQLSelectQueryField(serializers.Field):
|
class HogQLSelectQueryField(serializers.Field):
|
||||||
def to_internal_value(self, data: str) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def to_internal_value(self, data: str) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
"""Parse a HogQL SelectQuery from a string query."""
|
"""Parse a HogQL SelectQuery from a string query."""
|
||||||
try:
|
try:
|
||||||
parsed_query = parse_select(data)
|
parsed_query = parse_select(data)
|
||||||
@ -329,7 +329,7 @@ class BatchExportSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
return batch_export_schema
|
return batch_export_schema
|
||||||
|
|
||||||
def validate_hogql_query(self, hogql_query: ast.SelectQuery | ast.SelectUnionQuery) -> ast.SelectQuery:
|
def validate_hogql_query(self, hogql_query: ast.SelectQuery | ast.SelectSetQuery) -> ast.SelectQuery:
|
||||||
"""Validate a HogQLQuery being used for batch exports.
|
"""Validate a HogQLQuery being used for batch exports.
|
||||||
|
|
||||||
This method essentially checks that a query is supported by batch exports:
|
This method essentially checks that a query is supported by batch exports:
|
||||||
@ -338,7 +338,7 @@ class BatchExportSerializer(serializers.ModelSerializer):
|
|||||||
3. Query must SELECT FROM events, and only from events.
|
3. Query must SELECT FROM events, and only from events.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if isinstance(hogql_query, ast.SelectUnionQuery):
|
if isinstance(hogql_query, ast.SelectSetQuery):
|
||||||
raise serializers.ValidationError("UNIONs are not supported")
|
raise serializers.ValidationError("UNIONs are not supported")
|
||||||
|
|
||||||
parsed = cast(ast.SelectQuery, hogql_query)
|
parsed = cast(ast.SelectQuery, hogql_query)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from enum import StrEnum
|
from enum import StrEnum
|
||||||
from typing import Any, Literal, Optional, Union
|
from typing import Any, Literal, Optional, Union, get_args
|
||||||
|
from collections.abc import Sequence
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
from posthog.hogql.base import Type, Expr, CTE, ConstantType, UnknownType, AST
|
from posthog.hogql.base import Type, Expr, CTE, ConstantType, UnknownType, AST
|
||||||
@ -173,7 +174,7 @@ class BaseTableType(Type):
|
|||||||
|
|
||||||
|
|
||||||
TableOrSelectType = Union[
|
TableOrSelectType = Union[
|
||||||
BaseTableType, "SelectUnionQueryType", "SelectQueryType", "SelectQueryAliasType", "SelectViewType"
|
BaseTableType, "SelectSetQueryType", "SelectQueryType", "SelectQueryAliasType", "SelectViewType"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -243,9 +244,9 @@ class SelectQueryType(Type):
|
|||||||
tables: dict[str, TableOrSelectType] = field(default_factory=dict)
|
tables: dict[str, TableOrSelectType] = field(default_factory=dict)
|
||||||
ctes: dict[str, CTE] = field(default_factory=dict)
|
ctes: dict[str, CTE] = field(default_factory=dict)
|
||||||
# all from and join subqueries without aliases
|
# all from and join subqueries without aliases
|
||||||
anonymous_tables: list[Union["SelectQueryType", "SelectUnionQueryType"]] = field(default_factory=list)
|
anonymous_tables: list[Union["SelectQueryType", "SelectSetQueryType"]] = field(default_factory=list)
|
||||||
# the parent select query, if this is a lambda
|
# the parent select query, if this is a lambda
|
||||||
parent: Optional[Union["SelectQueryType", "SelectUnionQueryType"]] = None
|
parent: Optional[Union["SelectQueryType", "SelectSetQueryType"]] = None
|
||||||
|
|
||||||
def get_alias_for_table_type(self, table_type: TableOrSelectType) -> Optional[str]:
|
def get_alias_for_table_type(self, table_type: TableOrSelectType) -> Optional[str]:
|
||||||
for key, value in self.tables.items():
|
for key, value in self.tables.items():
|
||||||
@ -276,8 +277,8 @@ class SelectQueryType(Type):
|
|||||||
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
@dataclass(kw_only=True)
|
||||||
class SelectUnionQueryType(Type):
|
class SelectSetQueryType(Type):
|
||||||
types: list[SelectQueryType]
|
types: list[Union[SelectQueryType, "SelectSetQueryType"]]
|
||||||
|
|
||||||
def get_alias_for_table_type(self, table_type: TableOrSelectType) -> Optional[str]:
|
def get_alias_for_table_type(self, table_type: TableOrSelectType) -> Optional[str]:
|
||||||
return self.types[0].get_alias_for_table_type(table_type)
|
return self.types[0].get_alias_for_table_type(table_type)
|
||||||
@ -296,7 +297,7 @@ class SelectUnionQueryType(Type):
|
|||||||
class SelectViewType(Type):
|
class SelectViewType(Type):
|
||||||
view_name: str
|
view_name: str
|
||||||
alias: str
|
alias: str
|
||||||
select_query_type: SelectQueryType | SelectUnionQueryType
|
select_query_type: SelectQueryType | SelectSetQueryType
|
||||||
|
|
||||||
def get_child(self, name: str, context: HogQLContext) -> Type:
|
def get_child(self, name: str, context: HogQLContext) -> Type:
|
||||||
if name == "*":
|
if name == "*":
|
||||||
@ -343,7 +344,7 @@ class SelectViewType(Type):
|
|||||||
@dataclass(kw_only=True)
|
@dataclass(kw_only=True)
|
||||||
class SelectQueryAliasType(Type):
|
class SelectQueryAliasType(Type):
|
||||||
alias: str
|
alias: str
|
||||||
select_query_type: SelectQueryType | SelectUnionQueryType
|
select_query_type: SelectQueryType | SelectSetQueryType
|
||||||
|
|
||||||
def get_child(self, name: str, context: HogQLContext) -> Type:
|
def get_child(self, name: str, context: HogQLContext) -> Type:
|
||||||
if name == "*":
|
if name == "*":
|
||||||
@ -763,7 +764,7 @@ class JoinExpr(Expr):
|
|||||||
type: Optional[TableOrSelectType] = None
|
type: Optional[TableOrSelectType] = None
|
||||||
|
|
||||||
join_type: Optional[str] = None
|
join_type: Optional[str] = None
|
||||||
table: Optional[Union["SelectQuery", "SelectUnionQuery", Field]] = None
|
table: Optional[Union["SelectQuery", "SelectSetQuery", Field]] = None
|
||||||
table_args: Optional[list[Expr]] = None
|
table_args: Optional[list[Expr]] = None
|
||||||
alias: Optional[str] = None
|
alias: Optional[str] = None
|
||||||
table_final: Optional[bool] = None
|
table_final: Optional[bool] = None
|
||||||
@ -820,10 +821,35 @@ class SelectQuery(Expr):
|
|||||||
view_name: Optional[str] = None
|
view_name: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
SetOperator = Literal["UNION ALL", "INTERSECT", "EXCEPT"]
|
||||||
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
@dataclass(kw_only=True)
|
||||||
class SelectUnionQuery(Expr):
|
class SelectSetNode:
|
||||||
type: Optional[SelectUnionQueryType] = None
|
select_query: Union[SelectQuery, "SelectSetQuery"]
|
||||||
select_queries: list[SelectQuery]
|
set_operator: SetOperator
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
if self.set_operator not in get_args(SetOperator):
|
||||||
|
raise ValueError("Invalid Set Operator")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(kw_only=True)
|
||||||
|
class SelectSetQuery(Expr):
|
||||||
|
type: Optional[SelectSetQueryType] = None
|
||||||
|
initial_select_query: Union[SelectQuery, "SelectSetQuery"]
|
||||||
|
subsequent_select_queries: list[SelectSetNode]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_from_queries(
|
||||||
|
cls, queries: Sequence[Union[SelectQuery, "SelectSetQuery"]], set_operator: SetOperator
|
||||||
|
) -> "SelectSetQuery":
|
||||||
|
return SelectSetQuery(
|
||||||
|
initial_select_query=queries[0],
|
||||||
|
subsequent_select_queries=[
|
||||||
|
SelectSetNode(select_query=query, set_operator=set_operator) for query in queries[1:]
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(kw_only=True)
|
@dataclass(kw_only=True)
|
||||||
|
@ -28,6 +28,7 @@ from posthog.hogql.resolver import resolve_types
|
|||||||
from posthog.hogql.timings import HogQLTimings
|
from posthog.hogql.timings import HogQLTimings
|
||||||
from posthog.hogql.visitor import TraversingVisitor, clone_expr
|
from posthog.hogql.visitor import TraversingVisitor, clone_expr
|
||||||
from posthog.hogql_queries.query_runner import get_query_runner
|
from posthog.hogql_queries.query_runner import get_query_runner
|
||||||
|
from posthog.hogql.resolver_utils import extract_select_queries
|
||||||
from posthog.models.insight_variable import InsightVariable
|
from posthog.models.insight_variable import InsightVariable
|
||||||
from posthog.models.property_definition import PropertyDefinition
|
from posthog.models.property_definition import PropertyDefinition
|
||||||
from posthog.models.team.team import Team
|
from posthog.models.team.team import Team
|
||||||
@ -475,8 +476,8 @@ def get_hogql_autocomplete(
|
|||||||
|
|
||||||
if isinstance(select_ast, ast.SelectQuery):
|
if isinstance(select_ast, ast.SelectQuery):
|
||||||
ctes = select_ast.ctes
|
ctes = select_ast.ctes
|
||||||
elif isinstance(select_ast, ast.SelectUnionQuery):
|
elif isinstance(select_ast, ast.SelectSetQuery):
|
||||||
ctes = select_ast.select_queries[0].ctes
|
ctes = next(extract_select_queries(select_ast)).ctes
|
||||||
nearest_select = find_node.nearest_select_query or select_ast
|
nearest_select = find_node.nearest_select_query or select_ast
|
||||||
|
|
||||||
table_has_alias = (
|
table_has_alias = (
|
||||||
|
@ -24,7 +24,7 @@ def _expr(s: Union[str, ast.Expr, None], placeholders: Optional[dict[str, ast.Ex
|
|||||||
def _select(
|
def _select(
|
||||||
s: str,
|
s: str,
|
||||||
placeholders: Optional[dict[str, ast.Expr]] = None,
|
placeholders: Optional[dict[str, ast.Expr]] = None,
|
||||||
) -> ast.SelectQuery | ast.SelectUnionQuery:
|
) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
parsed = parse_select(s, placeholders=placeholders)
|
parsed = parse_select(s, placeholders=placeholders)
|
||||||
return parsed
|
return parsed
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ def f(s: Union[str, ast.Expr, None], placeholders: Optional[dict[str, ast.Expr]]
|
|||||||
def parse(
|
def parse(
|
||||||
s: str,
|
s: str,
|
||||||
placeholders: Optional[dict[str, ast.Expr]] = None,
|
placeholders: Optional[dict[str, ast.Expr]] = None,
|
||||||
) -> ast.SelectQuery | ast.SelectUnionQuery:
|
) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
parsed = parse_select(s, placeholders=placeholders)
|
parsed = parse_select(s, placeholders=placeholders)
|
||||||
return parsed
|
return parsed
|
||||||
|
|
||||||
@ -363,7 +363,8 @@ class TestSessionsV2QueriesHogQLToClickhouse(ClickhouseTestMixin, APIBaseTest):
|
|||||||
|
|
||||||
def test_select_with_timestamp(self):
|
def test_select_with_timestamp(self):
|
||||||
actual = self.print_query("SELECT session_id FROM sessions WHERE $start_timestamp > '2021-01-01'")
|
actual = self.print_query("SELECT session_id FROM sessions WHERE $start_timestamp > '2021-01-01'")
|
||||||
assert self.generalize_sql(actual) == snapshot("""\
|
assert self.generalize_sql(actual) == snapshot(
|
||||||
|
"""\
|
||||||
SELECT
|
SELECT
|
||||||
sessions.session_id AS session_id
|
sessions.session_id AS session_id
|
||||||
FROM
|
FROM
|
||||||
@ -381,7 +382,8 @@ FROM
|
|||||||
WHERE
|
WHERE
|
||||||
ifNull(greater(sessions.`$start_timestamp`, %(hogql_val_2)s), 0)
|
ifNull(greater(sessions.`$start_timestamp`, %(hogql_val_2)s), 0)
|
||||||
LIMIT 50000\
|
LIMIT 50000\
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
def test_join_with_events(self):
|
def test_join_with_events(self):
|
||||||
actual = self.print_query(
|
actual = self.print_query(
|
||||||
@ -396,7 +398,8 @@ WHERE events.timestamp > '2021-01-01'
|
|||||||
GROUP BY sessions.session_id
|
GROUP BY sessions.session_id
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
assert self.generalize_sql(actual) == snapshot("""\
|
assert self.generalize_sql(actual) == snapshot(
|
||||||
|
"""\
|
||||||
SELECT
|
SELECT
|
||||||
sessions.session_id AS session_id,
|
sessions.session_id AS session_id,
|
||||||
uniq(events.uuid)
|
uniq(events.uuid)
|
||||||
@ -417,7 +420,8 @@ WHERE
|
|||||||
GROUP BY
|
GROUP BY
|
||||||
sessions.session_id
|
sessions.session_id
|
||||||
LIMIT 50000\
|
LIMIT 50000\
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
def test_union(self):
|
def test_union(self):
|
||||||
actual = self.print_query(
|
actual = self.print_query(
|
||||||
@ -429,7 +433,8 @@ FROM events
|
|||||||
WHERE events.timestamp < today()
|
WHERE events.timestamp < today()
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
assert self.generalize_sql(actual) == snapshot("""\
|
assert self.generalize_sql(actual) == snapshot(
|
||||||
|
"""\
|
||||||
SELECT
|
SELECT
|
||||||
0 AS duration
|
0 AS duration
|
||||||
LIMIT 50000
|
LIMIT 50000
|
||||||
@ -451,7 +456,8 @@ FROM
|
|||||||
WHERE
|
WHERE
|
||||||
and(equals(events.team_id, <TEAM_ID>), less(toTimeZone(events.timestamp, %(hogql_val_4)s), today()))
|
and(equals(events.team_id, <TEAM_ID>), less(toTimeZone(events.timestamp, %(hogql_val_4)s), today()))
|
||||||
LIMIT 50000\
|
LIMIT 50000\
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
def test_session_breakdown(self):
|
def test_session_breakdown(self):
|
||||||
actual = self.print_query(
|
actual = self.print_query(
|
||||||
@ -495,7 +501,8 @@ WHERE and(greaterOrEquals(timestamp, toStartOfDay(assumeNotNull(toDateTime('2024
|
|||||||
GROUP BY day_start,
|
GROUP BY day_start,
|
||||||
breakdown_value"""
|
breakdown_value"""
|
||||||
)
|
)
|
||||||
assert self.generalize_sql(actual) == snapshot("""\
|
assert self.generalize_sql(actual) == snapshot(
|
||||||
|
"""\
|
||||||
SELECT
|
SELECT
|
||||||
count(DISTINCT e.`$session_id`) AS total,
|
count(DISTINCT e.`$session_id`) AS total,
|
||||||
toStartOfDay(toTimeZone(e.timestamp, %(hogql_val_8)s)) AS day_start,
|
toStartOfDay(toTimeZone(e.timestamp, %(hogql_val_8)s)) AS day_start,
|
||||||
@ -535,7 +542,8 @@ GROUP BY
|
|||||||
day_start,
|
day_start,
|
||||||
breakdown_value
|
breakdown_value
|
||||||
LIMIT 50000\
|
LIMIT 50000\
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
def test_session_replay_query(self):
|
def test_session_replay_query(self):
|
||||||
actual = self.print_query(
|
actual = self.print_query(
|
||||||
@ -548,7 +556,8 @@ WHERE s.session.$entry_pathname = '/home' AND min_first_timestamp >= '2021-01-01
|
|||||||
GROUP BY session_id
|
GROUP BY session_id
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
assert self.generalize_sql(actual) == snapshot("""\
|
assert self.generalize_sql(actual) == snapshot(
|
||||||
|
"""\
|
||||||
SELECT
|
SELECT
|
||||||
s.session_id AS session_id,
|
s.session_id AS session_id,
|
||||||
min(toTimeZone(s.min_first_timestamp, %(hogql_val_5)s)) AS start_time
|
min(toTimeZone(s.min_first_timestamp, %(hogql_val_5)s)) AS start_time
|
||||||
@ -569,7 +578,8 @@ WHERE
|
|||||||
GROUP BY
|
GROUP BY
|
||||||
s.session_id
|
s.session_id
|
||||||
LIMIT 50000\
|
LIMIT 50000\
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
def test_urls_in_sessions_in_timestamp_query(self):
|
def test_urls_in_sessions_in_timestamp_query(self):
|
||||||
actual = self.print_query(
|
actual = self.print_query(
|
||||||
@ -582,7 +592,8 @@ from sessions
|
|||||||
where `$start_timestamp` >= now() - toIntervalDay(7)
|
where `$start_timestamp` >= now() - toIntervalDay(7)
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
assert self.generalize_sql(actual) == snapshot("""\
|
assert self.generalize_sql(actual) == snapshot(
|
||||||
|
"""\
|
||||||
SELECT
|
SELECT
|
||||||
sessions.session_id AS session_id,
|
sessions.session_id AS session_id,
|
||||||
sessions.`$urls` AS `$urls`,
|
sessions.`$urls` AS `$urls`,
|
||||||
@ -603,4 +614,5 @@ FROM
|
|||||||
WHERE
|
WHERE
|
||||||
ifNull(greaterOrEquals(sessions.`$start_timestamp`, minus(now64(6, %(hogql_val_2)s), toIntervalDay(7))), 0)
|
ifNull(greaterOrEquals(sessions.`$start_timestamp`, minus(now64(6, %(hogql_val_2)s), toIntervalDay(7))), 0)
|
||||||
LIMIT 50000\
|
LIMIT 50000\
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
@ -26,7 +26,7 @@ def f(s: Union[str, ast.Expr, None], placeholders: Optional[dict[str, ast.Expr]]
|
|||||||
def parse(
|
def parse(
|
||||||
s: str,
|
s: str,
|
||||||
placeholders: Optional[dict[str, ast.Expr]] = None,
|
placeholders: Optional[dict[str, ast.Expr]] = None,
|
||||||
) -> ast.SelectQuery | ast.SelectUnionQuery:
|
) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
parsed = parse_select(s, placeholders=placeholders)
|
parsed = parse_select(s, placeholders=placeholders)
|
||||||
return parsed
|
return parsed
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ DESCENDING: D E S C E N D I N G;
|
|||||||
DISTINCT: D I S T I N C T;
|
DISTINCT: D I S T I N C T;
|
||||||
ELSE: E L S E;
|
ELSE: E L S E;
|
||||||
END: E N D;
|
END: E N D;
|
||||||
|
EXCEPT: E X C E P T;
|
||||||
EXTRACT: E X T R A C T;
|
EXTRACT: E X T R A C T;
|
||||||
FINAL: F I N A L;
|
FINAL: F I N A L;
|
||||||
FINALLY: F I N A L L Y;
|
FINALLY: F I N A L L Y;
|
||||||
@ -49,6 +50,7 @@ ILIKE: I L I K E;
|
|||||||
IN: I N;
|
IN: I N;
|
||||||
INF: I N F | I N F I N I T Y;
|
INF: I N F | I N F I N I T Y;
|
||||||
INNER: I N N E R;
|
INNER: I N N E R;
|
||||||
|
INTERSECT: I N T E R S E C T;
|
||||||
INTERVAL: I N T E R V A L;
|
INTERVAL: I N T E R V A L;
|
||||||
IS: I S;
|
IS: I S;
|
||||||
JOIN: J O I N;
|
JOIN: J O I N;
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -24,178 +24,180 @@ DESCENDING=23
|
|||||||
DISTINCT=24
|
DISTINCT=24
|
||||||
ELSE=25
|
ELSE=25
|
||||||
END=26
|
END=26
|
||||||
EXTRACT=27
|
EXCEPT=27
|
||||||
FINAL=28
|
EXTRACT=28
|
||||||
FINALLY=29
|
FINAL=29
|
||||||
FIRST=30
|
FINALLY=30
|
||||||
FN=31
|
FIRST=31
|
||||||
FOLLOWING=32
|
FN=32
|
||||||
FOR=33
|
FOLLOWING=33
|
||||||
FROM=34
|
FOR=34
|
||||||
FULL=35
|
FROM=35
|
||||||
FUN=36
|
FULL=36
|
||||||
GROUP=37
|
FUN=37
|
||||||
HAVING=38
|
GROUP=38
|
||||||
HOUR=39
|
HAVING=39
|
||||||
ID=40
|
HOUR=40
|
||||||
IF=41
|
ID=41
|
||||||
ILIKE=42
|
IF=42
|
||||||
IN=43
|
ILIKE=43
|
||||||
INF=44
|
IN=44
|
||||||
INNER=45
|
INF=45
|
||||||
INTERVAL=46
|
INNER=46
|
||||||
IS=47
|
INTERSECT=47
|
||||||
JOIN=48
|
INTERVAL=48
|
||||||
KEY=49
|
IS=49
|
||||||
LAST=50
|
JOIN=50
|
||||||
LEADING=51
|
KEY=51
|
||||||
LEFT=52
|
LAST=52
|
||||||
LET=53
|
LEADING=53
|
||||||
LIKE=54
|
LEFT=54
|
||||||
LIMIT=55
|
LET=55
|
||||||
MINUTE=56
|
LIKE=56
|
||||||
MONTH=57
|
LIMIT=57
|
||||||
NAN_SQL=58
|
MINUTE=58
|
||||||
NOT=59
|
MONTH=59
|
||||||
NULL_SQL=60
|
NAN_SQL=60
|
||||||
NULLS=61
|
NOT=61
|
||||||
OFFSET=62
|
NULL_SQL=62
|
||||||
ON=63
|
NULLS=63
|
||||||
OR=64
|
OFFSET=64
|
||||||
ORDER=65
|
ON=65
|
||||||
OUTER=66
|
OR=66
|
||||||
OVER=67
|
ORDER=67
|
||||||
PARTITION=68
|
OUTER=68
|
||||||
PRECEDING=69
|
OVER=69
|
||||||
PREWHERE=70
|
PARTITION=70
|
||||||
QUARTER=71
|
PRECEDING=71
|
||||||
RANGE=72
|
PREWHERE=72
|
||||||
RETURN=73
|
QUARTER=73
|
||||||
RIGHT=74
|
RANGE=74
|
||||||
ROLLUP=75
|
RETURN=75
|
||||||
ROW=76
|
RIGHT=76
|
||||||
ROWS=77
|
ROLLUP=77
|
||||||
SAMPLE=78
|
ROW=78
|
||||||
SECOND=79
|
ROWS=79
|
||||||
SELECT=80
|
SAMPLE=80
|
||||||
SEMI=81
|
SECOND=81
|
||||||
SETTINGS=82
|
SELECT=82
|
||||||
SUBSTRING=83
|
SEMI=83
|
||||||
THEN=84
|
SETTINGS=84
|
||||||
THROW=85
|
SUBSTRING=85
|
||||||
TIES=86
|
THEN=86
|
||||||
TIMESTAMP=87
|
THROW=87
|
||||||
TO=88
|
TIES=88
|
||||||
TOP=89
|
TIMESTAMP=89
|
||||||
TOTALS=90
|
TO=90
|
||||||
TRAILING=91
|
TOP=91
|
||||||
TRIM=92
|
TOTALS=92
|
||||||
TRUNCATE=93
|
TRAILING=93
|
||||||
TRY=94
|
TRIM=94
|
||||||
UNBOUNDED=95
|
TRUNCATE=95
|
||||||
UNION=96
|
TRY=96
|
||||||
USING=97
|
UNBOUNDED=97
|
||||||
WEEK=98
|
UNION=98
|
||||||
WHEN=99
|
USING=99
|
||||||
WHERE=100
|
WEEK=100
|
||||||
WHILE=101
|
WHEN=101
|
||||||
WINDOW=102
|
WHERE=102
|
||||||
WITH=103
|
WHILE=103
|
||||||
YEAR=104
|
WINDOW=104
|
||||||
ESCAPE_CHAR_COMMON=105
|
WITH=105
|
||||||
IDENTIFIER=106
|
YEAR=106
|
||||||
FLOATING_LITERAL=107
|
ESCAPE_CHAR_COMMON=107
|
||||||
OCTAL_LITERAL=108
|
IDENTIFIER=108
|
||||||
DECIMAL_LITERAL=109
|
FLOATING_LITERAL=109
|
||||||
HEXADECIMAL_LITERAL=110
|
OCTAL_LITERAL=110
|
||||||
STRING_LITERAL=111
|
DECIMAL_LITERAL=111
|
||||||
ARROW=112
|
HEXADECIMAL_LITERAL=112
|
||||||
ASTERISK=113
|
STRING_LITERAL=113
|
||||||
BACKQUOTE=114
|
ARROW=114
|
||||||
BACKSLASH=115
|
ASTERISK=115
|
||||||
COLON=116
|
BACKQUOTE=116
|
||||||
COMMA=117
|
BACKSLASH=117
|
||||||
CONCAT=118
|
COLON=118
|
||||||
DASH=119
|
COMMA=119
|
||||||
DOLLAR=120
|
CONCAT=120
|
||||||
DOT=121
|
DASH=121
|
||||||
EQ_DOUBLE=122
|
DOLLAR=122
|
||||||
EQ_SINGLE=123
|
DOT=123
|
||||||
GT_EQ=124
|
EQ_DOUBLE=124
|
||||||
GT=125
|
EQ_SINGLE=125
|
||||||
HASH=126
|
GT_EQ=126
|
||||||
IREGEX_SINGLE=127
|
GT=127
|
||||||
IREGEX_DOUBLE=128
|
HASH=128
|
||||||
LBRACE=129
|
IREGEX_SINGLE=129
|
||||||
LBRACKET=130
|
IREGEX_DOUBLE=130
|
||||||
LPAREN=131
|
LBRACE=131
|
||||||
LT_EQ=132
|
LBRACKET=132
|
||||||
LT=133
|
LPAREN=133
|
||||||
NOT_EQ=134
|
LT_EQ=134
|
||||||
NOT_IREGEX=135
|
LT=135
|
||||||
NOT_REGEX=136
|
NOT_EQ=136
|
||||||
NULL_PROPERTY=137
|
NOT_IREGEX=137
|
||||||
NULLISH=138
|
NOT_REGEX=138
|
||||||
PERCENT=139
|
NULL_PROPERTY=139
|
||||||
PLUS=140
|
NULLISH=140
|
||||||
QUERY=141
|
PERCENT=141
|
||||||
QUOTE_DOUBLE=142
|
PLUS=142
|
||||||
QUOTE_SINGLE_TEMPLATE=143
|
QUERY=143
|
||||||
QUOTE_SINGLE_TEMPLATE_FULL=144
|
QUOTE_DOUBLE=144
|
||||||
QUOTE_SINGLE=145
|
QUOTE_SINGLE_TEMPLATE=145
|
||||||
REGEX_SINGLE=146
|
QUOTE_SINGLE_TEMPLATE_FULL=146
|
||||||
REGEX_DOUBLE=147
|
QUOTE_SINGLE=147
|
||||||
RBRACE=148
|
REGEX_SINGLE=148
|
||||||
RBRACKET=149
|
REGEX_DOUBLE=149
|
||||||
RPAREN=150
|
RBRACE=150
|
||||||
SEMICOLON=151
|
RBRACKET=151
|
||||||
SLASH=152
|
RPAREN=152
|
||||||
UNDERSCORE=153
|
SEMICOLON=153
|
||||||
MULTI_LINE_COMMENT=154
|
SLASH=154
|
||||||
SINGLE_LINE_COMMENT=155
|
UNDERSCORE=155
|
||||||
WHITESPACE=156
|
MULTI_LINE_COMMENT=156
|
||||||
STRING_TEXT=157
|
SINGLE_LINE_COMMENT=157
|
||||||
STRING_ESCAPE_TRIGGER=158
|
WHITESPACE=158
|
||||||
FULL_STRING_TEXT=159
|
STRING_TEXT=159
|
||||||
FULL_STRING_ESCAPE_TRIGGER=160
|
STRING_ESCAPE_TRIGGER=160
|
||||||
'->'=112
|
FULL_STRING_TEXT=161
|
||||||
'*'=113
|
FULL_STRING_ESCAPE_TRIGGER=162
|
||||||
'`'=114
|
'->'=114
|
||||||
'\\'=115
|
'*'=115
|
||||||
':'=116
|
'`'=116
|
||||||
','=117
|
'\\'=117
|
||||||
'||'=118
|
':'=118
|
||||||
'-'=119
|
','=119
|
||||||
'$'=120
|
'||'=120
|
||||||
'.'=121
|
'-'=121
|
||||||
'=='=122
|
'$'=122
|
||||||
'='=123
|
'.'=123
|
||||||
'>='=124
|
'=='=124
|
||||||
'>'=125
|
'='=125
|
||||||
'#'=126
|
'>='=126
|
||||||
'~*'=127
|
'>'=127
|
||||||
'=~*'=128
|
'#'=128
|
||||||
'{'=129
|
'~*'=129
|
||||||
'['=130
|
'=~*'=130
|
||||||
'('=131
|
'{'=131
|
||||||
'<='=132
|
'['=132
|
||||||
'<'=133
|
'('=133
|
||||||
'!~*'=135
|
'<='=134
|
||||||
'!~'=136
|
'<'=135
|
||||||
'?.'=137
|
'!~*'=137
|
||||||
'??'=138
|
'!~'=138
|
||||||
'%'=139
|
'?.'=139
|
||||||
'+'=140
|
'??'=140
|
||||||
'?'=141
|
'%'=141
|
||||||
'"'=142
|
'+'=142
|
||||||
'f\''=143
|
'?'=143
|
||||||
'F\''=144
|
'"'=144
|
||||||
'\''=145
|
'f\''=145
|
||||||
'~'=146
|
'F\''=146
|
||||||
'=~'=147
|
'\''=147
|
||||||
'}'=148
|
'~'=148
|
||||||
']'=149
|
'=~'=149
|
||||||
')'=150
|
'}'=150
|
||||||
';'=151
|
']'=151
|
||||||
'/'=152
|
')'=152
|
||||||
'_'=153
|
';'=153
|
||||||
|
'/'=154
|
||||||
|
'_'=155
|
||||||
|
@ -51,10 +51,12 @@ kvPairList: kvPair (COMMA kvPair)* COMMA?;
|
|||||||
|
|
||||||
|
|
||||||
// SELECT statement
|
// SELECT statement
|
||||||
select: (selectUnionStmt | selectStmt | hogqlxTagElement) EOF;
|
select: (selectSetStmt | selectStmt | hogqlxTagElement) EOF;
|
||||||
|
|
||||||
selectUnionStmt: selectStmtWithParens (UNION ALL selectStmtWithParens)*;
|
selectStmtWithParens: selectStmt | LPAREN selectSetStmt RPAREN | placeholder;
|
||||||
selectStmtWithParens: selectStmt | LPAREN selectUnionStmt RPAREN | placeholder;
|
|
||||||
|
subsequentSelectSetClause: (EXCEPT | UNION ALL | INTERSECT) selectStmtWithParens;
|
||||||
|
selectSetStmt: selectStmtWithParens (subsequentSelectSetClause)*;
|
||||||
|
|
||||||
selectStmt:
|
selectStmt:
|
||||||
with=withClause?
|
with=withClause?
|
||||||
@ -201,7 +203,7 @@ columnExpr
|
|||||||
| <assoc=right> columnExpr QUERY columnExpr COLON columnExpr # ColumnExprTernaryOp
|
| <assoc=right> columnExpr QUERY columnExpr COLON columnExpr # ColumnExprTernaryOp
|
||||||
| columnExpr (AS identifier | AS STRING_LITERAL) # ColumnExprAlias
|
| columnExpr (AS identifier | AS STRING_LITERAL) # ColumnExprAlias
|
||||||
| (tableIdentifier DOT)? ASTERISK # ColumnExprAsterisk // single-column only
|
| (tableIdentifier DOT)? ASTERISK # ColumnExprAsterisk // single-column only
|
||||||
| LPAREN selectUnionStmt RPAREN # ColumnExprSubquery // single-column only
|
| LPAREN selectSetStmt RPAREN # ColumnExprSubquery // single-column only
|
||||||
| LPAREN columnExpr RPAREN # ColumnExprParens // single-column only
|
| LPAREN columnExpr RPAREN # ColumnExprParens // single-column only
|
||||||
| LPAREN columnExprList RPAREN # ColumnExprTuple
|
| LPAREN columnExprList RPAREN # ColumnExprTuple
|
||||||
| LBRACKET columnExprList? RBRACKET # ColumnExprArray
|
| LBRACKET columnExprList? RBRACKET # ColumnExprArray
|
||||||
@ -231,7 +233,7 @@ hogqlxTagAttribute
|
|||||||
|
|
||||||
withExprList: withExpr (COMMA withExpr)* COMMA?;
|
withExprList: withExpr (COMMA withExpr)* COMMA?;
|
||||||
withExpr
|
withExpr
|
||||||
: identifier AS LPAREN selectUnionStmt RPAREN # WithExprSubquery
|
: identifier AS LPAREN selectSetStmt RPAREN # WithExprSubquery
|
||||||
// NOTE: asterisk and subquery goes before |columnExpr| so that we can mark them as multi-column expressions.
|
// NOTE: asterisk and subquery goes before |columnExpr| so that we can mark them as multi-column expressions.
|
||||||
| columnExpr AS identifier # WithExprColumn
|
| columnExpr AS identifier # WithExprColumn
|
||||||
;
|
;
|
||||||
@ -246,7 +248,7 @@ nestedIdentifier: identifier (DOT identifier)*;
|
|||||||
tableExpr
|
tableExpr
|
||||||
: tableIdentifier # TableExprIdentifier
|
: tableIdentifier # TableExprIdentifier
|
||||||
| tableFunctionExpr # TableExprFunction
|
| tableFunctionExpr # TableExprFunction
|
||||||
| LPAREN selectUnionStmt RPAREN # TableExprSubquery
|
| LPAREN selectSetStmt RPAREN # TableExprSubquery
|
||||||
| tableExpr (alias | AS identifier) # TableExprAlias
|
| tableExpr (alias | AS identifier) # TableExprAlias
|
||||||
| hogqlxTagElement # TableExprTag
|
| hogqlxTagElement # TableExprTag
|
||||||
| placeholder # TableExprPlaceholder
|
| placeholder # TableExprPlaceholder
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -24,178 +24,180 @@ DESCENDING=23
|
|||||||
DISTINCT=24
|
DISTINCT=24
|
||||||
ELSE=25
|
ELSE=25
|
||||||
END=26
|
END=26
|
||||||
EXTRACT=27
|
EXCEPT=27
|
||||||
FINAL=28
|
EXTRACT=28
|
||||||
FINALLY=29
|
FINAL=29
|
||||||
FIRST=30
|
FINALLY=30
|
||||||
FN=31
|
FIRST=31
|
||||||
FOLLOWING=32
|
FN=32
|
||||||
FOR=33
|
FOLLOWING=33
|
||||||
FROM=34
|
FOR=34
|
||||||
FULL=35
|
FROM=35
|
||||||
FUN=36
|
FULL=36
|
||||||
GROUP=37
|
FUN=37
|
||||||
HAVING=38
|
GROUP=38
|
||||||
HOUR=39
|
HAVING=39
|
||||||
ID=40
|
HOUR=40
|
||||||
IF=41
|
ID=41
|
||||||
ILIKE=42
|
IF=42
|
||||||
IN=43
|
ILIKE=43
|
||||||
INF=44
|
IN=44
|
||||||
INNER=45
|
INF=45
|
||||||
INTERVAL=46
|
INNER=46
|
||||||
IS=47
|
INTERSECT=47
|
||||||
JOIN=48
|
INTERVAL=48
|
||||||
KEY=49
|
IS=49
|
||||||
LAST=50
|
JOIN=50
|
||||||
LEADING=51
|
KEY=51
|
||||||
LEFT=52
|
LAST=52
|
||||||
LET=53
|
LEADING=53
|
||||||
LIKE=54
|
LEFT=54
|
||||||
LIMIT=55
|
LET=55
|
||||||
MINUTE=56
|
LIKE=56
|
||||||
MONTH=57
|
LIMIT=57
|
||||||
NAN_SQL=58
|
MINUTE=58
|
||||||
NOT=59
|
MONTH=59
|
||||||
NULL_SQL=60
|
NAN_SQL=60
|
||||||
NULLS=61
|
NOT=61
|
||||||
OFFSET=62
|
NULL_SQL=62
|
||||||
ON=63
|
NULLS=63
|
||||||
OR=64
|
OFFSET=64
|
||||||
ORDER=65
|
ON=65
|
||||||
OUTER=66
|
OR=66
|
||||||
OVER=67
|
ORDER=67
|
||||||
PARTITION=68
|
OUTER=68
|
||||||
PRECEDING=69
|
OVER=69
|
||||||
PREWHERE=70
|
PARTITION=70
|
||||||
QUARTER=71
|
PRECEDING=71
|
||||||
RANGE=72
|
PREWHERE=72
|
||||||
RETURN=73
|
QUARTER=73
|
||||||
RIGHT=74
|
RANGE=74
|
||||||
ROLLUP=75
|
RETURN=75
|
||||||
ROW=76
|
RIGHT=76
|
||||||
ROWS=77
|
ROLLUP=77
|
||||||
SAMPLE=78
|
ROW=78
|
||||||
SECOND=79
|
ROWS=79
|
||||||
SELECT=80
|
SAMPLE=80
|
||||||
SEMI=81
|
SECOND=81
|
||||||
SETTINGS=82
|
SELECT=82
|
||||||
SUBSTRING=83
|
SEMI=83
|
||||||
THEN=84
|
SETTINGS=84
|
||||||
THROW=85
|
SUBSTRING=85
|
||||||
TIES=86
|
THEN=86
|
||||||
TIMESTAMP=87
|
THROW=87
|
||||||
TO=88
|
TIES=88
|
||||||
TOP=89
|
TIMESTAMP=89
|
||||||
TOTALS=90
|
TO=90
|
||||||
TRAILING=91
|
TOP=91
|
||||||
TRIM=92
|
TOTALS=92
|
||||||
TRUNCATE=93
|
TRAILING=93
|
||||||
TRY=94
|
TRIM=94
|
||||||
UNBOUNDED=95
|
TRUNCATE=95
|
||||||
UNION=96
|
TRY=96
|
||||||
USING=97
|
UNBOUNDED=97
|
||||||
WEEK=98
|
UNION=98
|
||||||
WHEN=99
|
USING=99
|
||||||
WHERE=100
|
WEEK=100
|
||||||
WHILE=101
|
WHEN=101
|
||||||
WINDOW=102
|
WHERE=102
|
||||||
WITH=103
|
WHILE=103
|
||||||
YEAR=104
|
WINDOW=104
|
||||||
ESCAPE_CHAR_COMMON=105
|
WITH=105
|
||||||
IDENTIFIER=106
|
YEAR=106
|
||||||
FLOATING_LITERAL=107
|
ESCAPE_CHAR_COMMON=107
|
||||||
OCTAL_LITERAL=108
|
IDENTIFIER=108
|
||||||
DECIMAL_LITERAL=109
|
FLOATING_LITERAL=109
|
||||||
HEXADECIMAL_LITERAL=110
|
OCTAL_LITERAL=110
|
||||||
STRING_LITERAL=111
|
DECIMAL_LITERAL=111
|
||||||
ARROW=112
|
HEXADECIMAL_LITERAL=112
|
||||||
ASTERISK=113
|
STRING_LITERAL=113
|
||||||
BACKQUOTE=114
|
ARROW=114
|
||||||
BACKSLASH=115
|
ASTERISK=115
|
||||||
COLON=116
|
BACKQUOTE=116
|
||||||
COMMA=117
|
BACKSLASH=117
|
||||||
CONCAT=118
|
COLON=118
|
||||||
DASH=119
|
COMMA=119
|
||||||
DOLLAR=120
|
CONCAT=120
|
||||||
DOT=121
|
DASH=121
|
||||||
EQ_DOUBLE=122
|
DOLLAR=122
|
||||||
EQ_SINGLE=123
|
DOT=123
|
||||||
GT_EQ=124
|
EQ_DOUBLE=124
|
||||||
GT=125
|
EQ_SINGLE=125
|
||||||
HASH=126
|
GT_EQ=126
|
||||||
IREGEX_SINGLE=127
|
GT=127
|
||||||
IREGEX_DOUBLE=128
|
HASH=128
|
||||||
LBRACE=129
|
IREGEX_SINGLE=129
|
||||||
LBRACKET=130
|
IREGEX_DOUBLE=130
|
||||||
LPAREN=131
|
LBRACE=131
|
||||||
LT_EQ=132
|
LBRACKET=132
|
||||||
LT=133
|
LPAREN=133
|
||||||
NOT_EQ=134
|
LT_EQ=134
|
||||||
NOT_IREGEX=135
|
LT=135
|
||||||
NOT_REGEX=136
|
NOT_EQ=136
|
||||||
NULL_PROPERTY=137
|
NOT_IREGEX=137
|
||||||
NULLISH=138
|
NOT_REGEX=138
|
||||||
PERCENT=139
|
NULL_PROPERTY=139
|
||||||
PLUS=140
|
NULLISH=140
|
||||||
QUERY=141
|
PERCENT=141
|
||||||
QUOTE_DOUBLE=142
|
PLUS=142
|
||||||
QUOTE_SINGLE_TEMPLATE=143
|
QUERY=143
|
||||||
QUOTE_SINGLE_TEMPLATE_FULL=144
|
QUOTE_DOUBLE=144
|
||||||
QUOTE_SINGLE=145
|
QUOTE_SINGLE_TEMPLATE=145
|
||||||
REGEX_SINGLE=146
|
QUOTE_SINGLE_TEMPLATE_FULL=146
|
||||||
REGEX_DOUBLE=147
|
QUOTE_SINGLE=147
|
||||||
RBRACE=148
|
REGEX_SINGLE=148
|
||||||
RBRACKET=149
|
REGEX_DOUBLE=149
|
||||||
RPAREN=150
|
RBRACE=150
|
||||||
SEMICOLON=151
|
RBRACKET=151
|
||||||
SLASH=152
|
RPAREN=152
|
||||||
UNDERSCORE=153
|
SEMICOLON=153
|
||||||
MULTI_LINE_COMMENT=154
|
SLASH=154
|
||||||
SINGLE_LINE_COMMENT=155
|
UNDERSCORE=155
|
||||||
WHITESPACE=156
|
MULTI_LINE_COMMENT=156
|
||||||
STRING_TEXT=157
|
SINGLE_LINE_COMMENT=157
|
||||||
STRING_ESCAPE_TRIGGER=158
|
WHITESPACE=158
|
||||||
FULL_STRING_TEXT=159
|
STRING_TEXT=159
|
||||||
FULL_STRING_ESCAPE_TRIGGER=160
|
STRING_ESCAPE_TRIGGER=160
|
||||||
'->'=112
|
FULL_STRING_TEXT=161
|
||||||
'*'=113
|
FULL_STRING_ESCAPE_TRIGGER=162
|
||||||
'`'=114
|
'->'=114
|
||||||
'\\'=115
|
'*'=115
|
||||||
':'=116
|
'`'=116
|
||||||
','=117
|
'\\'=117
|
||||||
'||'=118
|
':'=118
|
||||||
'-'=119
|
','=119
|
||||||
'$'=120
|
'||'=120
|
||||||
'.'=121
|
'-'=121
|
||||||
'=='=122
|
'$'=122
|
||||||
'='=123
|
'.'=123
|
||||||
'>='=124
|
'=='=124
|
||||||
'>'=125
|
'='=125
|
||||||
'#'=126
|
'>='=126
|
||||||
'~*'=127
|
'>'=127
|
||||||
'=~*'=128
|
'#'=128
|
||||||
'{'=129
|
'~*'=129
|
||||||
'['=130
|
'=~*'=130
|
||||||
'('=131
|
'{'=131
|
||||||
'<='=132
|
'['=132
|
||||||
'<'=133
|
'('=133
|
||||||
'!~*'=135
|
'<='=134
|
||||||
'!~'=136
|
'<'=135
|
||||||
'?.'=137
|
'!~*'=137
|
||||||
'??'=138
|
'!~'=138
|
||||||
'%'=139
|
'?.'=139
|
||||||
'+'=140
|
'??'=140
|
||||||
'?'=141
|
'%'=141
|
||||||
'"'=142
|
'+'=142
|
||||||
'f\''=143
|
'?'=143
|
||||||
'F\''=144
|
'"'=144
|
||||||
'\''=145
|
'f\''=145
|
||||||
'~'=146
|
'F\''=146
|
||||||
'=~'=147
|
'\''=147
|
||||||
'}'=148
|
'~'=148
|
||||||
']'=149
|
'=~'=149
|
||||||
')'=150
|
'}'=150
|
||||||
';'=151
|
']'=151
|
||||||
'/'=152
|
')'=152
|
||||||
'_'=153
|
';'=153
|
||||||
|
'/'=154
|
||||||
|
'_'=155
|
||||||
|
@ -119,13 +119,18 @@ class HogQLParserVisitor(ParseTreeVisitor):
|
|||||||
return self.visitChildren(ctx)
|
return self.visitChildren(ctx)
|
||||||
|
|
||||||
|
|
||||||
# Visit a parse tree produced by HogQLParser#selectUnionStmt.
|
# Visit a parse tree produced by HogQLParser#selectStmtWithParens.
|
||||||
def visitSelectUnionStmt(self, ctx:HogQLParser.SelectUnionStmtContext):
|
def visitSelectStmtWithParens(self, ctx:HogQLParser.SelectStmtWithParensContext):
|
||||||
return self.visitChildren(ctx)
|
return self.visitChildren(ctx)
|
||||||
|
|
||||||
|
|
||||||
# Visit a parse tree produced by HogQLParser#selectStmtWithParens.
|
# Visit a parse tree produced by HogQLParser#subsequentSelectSetClause.
|
||||||
def visitSelectStmtWithParens(self, ctx:HogQLParser.SelectStmtWithParensContext):
|
def visitSubsequentSelectSetClause(self, ctx:HogQLParser.SubsequentSelectSetClauseContext):
|
||||||
|
return self.visitChildren(ctx)
|
||||||
|
|
||||||
|
|
||||||
|
# Visit a parse tree produced by HogQLParser#selectSetStmt.
|
||||||
|
def visitSelectSetStmt(self, ctx:HogQLParser.SelectSetStmtContext):
|
||||||
return self.visitChildren(ctx)
|
return self.visitChildren(ctx)
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ from posthog.hogql.variables import replace_variables
|
|||||||
from posthog.hogql.visitor import clone_expr
|
from posthog.hogql.visitor import clone_expr
|
||||||
from posthog.hogql_queries.query_runner import get_query_runner
|
from posthog.hogql_queries.query_runner import get_query_runner
|
||||||
from posthog.models import Team
|
from posthog.models import Team
|
||||||
|
from posthog.hogql.resolver_utils import extract_select_queries
|
||||||
from posthog.schema import HogQLMetadataResponse, HogQLMetadata, HogQLNotice, HogLanguage
|
from posthog.schema import HogQLMetadataResponse, HogQLMetadata, HogQLNotice, HogLanguage
|
||||||
from posthog.hogql import ast
|
from posthog.hogql import ast
|
||||||
|
|
||||||
@ -99,7 +100,7 @@ def get_hogql_metadata(
|
|||||||
def process_expr_on_table(
|
def process_expr_on_table(
|
||||||
node: ast.Expr,
|
node: ast.Expr,
|
||||||
context: HogQLContext,
|
context: HogQLContext,
|
||||||
source_query: Optional[ast.SelectQuery | ast.SelectUnionQuery] = None,
|
source_query: Optional[ast.SelectQuery | ast.SelectSetQuery] = None,
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
if source_query is not None:
|
if source_query is not None:
|
||||||
@ -114,15 +115,9 @@ def process_expr_on_table(
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def is_valid_view(select_query: ast.SelectQuery | ast.SelectUnionQuery) -> bool:
|
def is_valid_view(select_query: ast.SelectQuery | ast.SelectSetQuery) -> bool:
|
||||||
if isinstance(select_query, ast.SelectQuery):
|
for query in extract_select_queries(select_query):
|
||||||
for field in select_query.select:
|
for field in query.select:
|
||||||
if not isinstance(field, ast.Alias):
|
if not isinstance(field, ast.Alias):
|
||||||
return False
|
return False
|
||||||
elif isinstance(select_query, ast.SelectUnionQuery):
|
|
||||||
for select in select_query.select_queries:
|
|
||||||
for field in select.select:
|
|
||||||
if not isinstance(field, ast.Alias):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -6,6 +6,7 @@ from antlr4.error.ErrorListener import ErrorListener
|
|||||||
from prometheus_client import Histogram
|
from prometheus_client import Histogram
|
||||||
|
|
||||||
from posthog.hogql import ast
|
from posthog.hogql import ast
|
||||||
|
from posthog.hogql.ast import SelectSetNode
|
||||||
from posthog.hogql.base import AST
|
from posthog.hogql.base import AST
|
||||||
from posthog.hogql.constants import RESERVED_KEYWORDS
|
from posthog.hogql.constants import RESERVED_KEYWORDS
|
||||||
from posthog.hogql.errors import BaseHogQLError, NotImplementedError, SyntaxError
|
from posthog.hogql.errors import BaseHogQLError, NotImplementedError, SyntaxError
|
||||||
@ -132,7 +133,7 @@ def parse_select(
|
|||||||
timings: Optional[HogQLTimings] = None,
|
timings: Optional[HogQLTimings] = None,
|
||||||
*,
|
*,
|
||||||
backend: Literal["python", "cpp"] = "cpp",
|
backend: Literal["python", "cpp"] = "cpp",
|
||||||
) -> ast.SelectQuery | ast.SelectUnionQuery:
|
) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
if timings is None:
|
if timings is None:
|
||||||
timings = HogQLTimings()
|
timings = HogQLTimings()
|
||||||
with timings.measure(f"parse_select_{backend}"):
|
with timings.measure(f"parse_select_{backend}"):
|
||||||
@ -327,28 +328,33 @@ class HogQLParseTreeConverter(ParseTreeVisitor):
|
|||||||
##### HogQL rules
|
##### HogQL rules
|
||||||
|
|
||||||
def visitSelect(self, ctx: HogQLParser.SelectContext):
|
def visitSelect(self, ctx: HogQLParser.SelectContext):
|
||||||
return self.visit(ctx.selectUnionStmt() or ctx.selectStmt() or ctx.hogqlxTagElement())
|
return self.visit(ctx.selectSetStmt() or ctx.selectStmt() or ctx.hogqlxTagElement())
|
||||||
|
|
||||||
def visitSelectUnionStmt(self, ctx: HogQLParser.SelectUnionStmtContext):
|
def visitSelectSetStmt(self, ctx: HogQLParser.SelectSetStmtContext):
|
||||||
select_queries: list[ast.SelectQuery | ast.SelectUnionQuery | ast.Placeholder] = [
|
select_queries: list[SelectSetNode] = []
|
||||||
self.visit(select) for select in ctx.selectStmtWithParens()
|
|
||||||
]
|
initial_query = self.visit(ctx.selectStmtWithParens())
|
||||||
flattened_queries: list[ast.SelectQuery] = []
|
|
||||||
for query in select_queries:
|
for subsequent in ctx.subsequentSelectSetClause():
|
||||||
if isinstance(query, ast.SelectQuery):
|
if subsequent.UNION() and subsequent.ALL():
|
||||||
flattened_queries.append(query)
|
union_type = "UNION ALL"
|
||||||
elif isinstance(query, ast.SelectUnionQuery):
|
elif subsequent.INTERSECT():
|
||||||
flattened_queries.extend(query.select_queries)
|
union_type = "INTERSECT"
|
||||||
elif isinstance(query, ast.Placeholder):
|
elif subsequent.EXCEPT():
|
||||||
flattened_queries.append(query) # type: ignore
|
union_type = "EXCEPT"
|
||||||
else:
|
else:
|
||||||
raise Exception(f"Unexpected query node type {type(query).__name__}")
|
raise SyntaxError("Set operator must be one of UNION ALL, INTERSECT, and EXCEPT")
|
||||||
if len(flattened_queries) == 1:
|
select_query = self.visit(subsequent.selectStmtWithParens())
|
||||||
return flattened_queries[0]
|
select_queries.append(
|
||||||
return ast.SelectUnionQuery(select_queries=flattened_queries)
|
SelectSetNode(select_query=select_query, set_operator=cast(ast.SetOperator, union_type))
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(select_queries) == 0:
|
||||||
|
return initial_query
|
||||||
|
return ast.SelectSetQuery(initial_select_query=initial_query, subsequent_select_queries=select_queries)
|
||||||
|
|
||||||
def visitSelectStmtWithParens(self, ctx: HogQLParser.SelectStmtWithParensContext):
|
def visitSelectStmtWithParens(self, ctx: HogQLParser.SelectStmtWithParensContext):
|
||||||
return self.visit(ctx.selectStmt() or ctx.selectUnionStmt() or ctx.placeholder())
|
return self.visit(ctx.selectStmt() or ctx.selectSetStmt() or ctx.placeholder())
|
||||||
|
|
||||||
def visitSelectStmt(self, ctx: HogQLParser.SelectStmtContext):
|
def visitSelectStmt(self, ctx: HogQLParser.SelectStmtContext):
|
||||||
select_query = ast.SelectQuery(
|
select_query = ast.SelectQuery(
|
||||||
@ -673,7 +679,7 @@ class HogQLParseTreeConverter(ParseTreeVisitor):
|
|||||||
return ast.Dict(items=self.visit(ctx.kvPairList()) if ctx.kvPairList() else [])
|
return ast.Dict(items=self.visit(ctx.kvPairList()) if ctx.kvPairList() else [])
|
||||||
|
|
||||||
def visitColumnExprSubquery(self, ctx: HogQLParser.ColumnExprSubqueryContext):
|
def visitColumnExprSubquery(self, ctx: HogQLParser.ColumnExprSubqueryContext):
|
||||||
return self.visit(ctx.selectUnionStmt())
|
return self.visit(ctx.selectSetStmt())
|
||||||
|
|
||||||
def visitColumnExprLiteral(self, ctx: HogQLParser.ColumnExprLiteralContext):
|
def visitColumnExprLiteral(self, ctx: HogQLParser.ColumnExprLiteralContext):
|
||||||
return self.visitChildren(ctx)
|
return self.visitChildren(ctx)
|
||||||
@ -958,7 +964,7 @@ class HogQLParseTreeConverter(ParseTreeVisitor):
|
|||||||
return ctes
|
return ctes
|
||||||
|
|
||||||
def visitWithExprSubquery(self, ctx: HogQLParser.WithExprSubqueryContext):
|
def visitWithExprSubquery(self, ctx: HogQLParser.WithExprSubqueryContext):
|
||||||
subquery = self.visit(ctx.selectUnionStmt())
|
subquery = self.visit(ctx.selectSetStmt())
|
||||||
name = self.visit(ctx.identifier())
|
name = self.visit(ctx.identifier())
|
||||||
return ast.CTE(name=name, expr=subquery, cte_type="subquery")
|
return ast.CTE(name=name, expr=subquery, cte_type="subquery")
|
||||||
|
|
||||||
@ -992,7 +998,7 @@ class HogQLParseTreeConverter(ParseTreeVisitor):
|
|||||||
return ast.Field(chain=chain)
|
return ast.Field(chain=chain)
|
||||||
|
|
||||||
def visitTableExprSubquery(self, ctx: HogQLParser.TableExprSubqueryContext):
|
def visitTableExprSubquery(self, ctx: HogQLParser.TableExprSubqueryContext):
|
||||||
return self.visit(ctx.selectUnionStmt())
|
return self.visit(ctx.selectSetStmt())
|
||||||
|
|
||||||
def visitTableExprPlaceholder(self, ctx: HogQLParser.TableExprPlaceholderContext):
|
def visitTableExprPlaceholder(self, ctx: HogQLParser.TableExprPlaceholderContext):
|
||||||
return self.visit(ctx.placeholder())
|
return self.visit(ctx.placeholder())
|
||||||
|
@ -266,7 +266,7 @@ class _Printer(Visitor):
|
|||||||
self.stack.pop()
|
self.stack.pop()
|
||||||
|
|
||||||
if len(self.stack) == 0 and self.dialect == "clickhouse" and self.settings:
|
if len(self.stack) == 0 and self.dialect == "clickhouse" and self.settings:
|
||||||
if not isinstance(node, ast.SelectQuery) and not isinstance(node, ast.SelectUnionQuery):
|
if not isinstance(node, ast.SelectQuery) and not isinstance(node, ast.SelectSetQuery):
|
||||||
raise QueryError("Settings can only be applied to SELECT queries")
|
raise QueryError("Settings can only be applied to SELECT queries")
|
||||||
settings = self._print_settings(self.settings)
|
settings = self._print_settings(self.settings)
|
||||||
if settings is not None:
|
if settings is not None:
|
||||||
@ -274,17 +274,25 @@ class _Printer(Visitor):
|
|||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def visit_select_union_query(self, node: ast.SelectUnionQuery):
|
def visit_select_set_query(self, node: ast.SelectSetQuery):
|
||||||
self._indent -= 1
|
self._indent -= 1
|
||||||
queries = [self.visit(expr) for expr in node.select_queries]
|
ret = self.visit(node.initial_select_query)
|
||||||
if self.pretty:
|
if self.pretty:
|
||||||
query = f"\n{self.indent(1)}UNION ALL\n{self.indent(1)}".join([query.strip() for query in queries])
|
ret = ret.strip()
|
||||||
else:
|
for expr in node.subsequent_select_queries:
|
||||||
query = " UNION ALL ".join(queries)
|
query = self.visit(expr.select_query)
|
||||||
|
if self.pretty:
|
||||||
|
query = query.strip()
|
||||||
|
if expr.set_operator is not None:
|
||||||
|
if self.pretty:
|
||||||
|
ret += f"\n{self.indent(1)}{expr.set_operator}\n{self.indent(1)}"
|
||||||
|
else:
|
||||||
|
ret += f" {expr.set_operator} "
|
||||||
|
ret += query
|
||||||
self._indent += 1
|
self._indent += 1
|
||||||
if len(self.stack) > 1:
|
if len(self.stack) > 1:
|
||||||
return f"({query.strip()})"
|
return f"({ret.strip()})"
|
||||||
return query
|
return ret
|
||||||
|
|
||||||
def visit_select_query(self, node: ast.SelectQuery):
|
def visit_select_query(self, node: ast.SelectQuery):
|
||||||
if self.dialect == "clickhouse":
|
if self.dialect == "clickhouse":
|
||||||
@ -293,8 +301,8 @@ class _Printer(Visitor):
|
|||||||
if not self.context.team_id:
|
if not self.context.team_id:
|
||||||
raise InternalHogQLError("Full SELECT queries are disabled if context.team_id is not set")
|
raise InternalHogQLError("Full SELECT queries are disabled if context.team_id is not set")
|
||||||
|
|
||||||
# if we are the first parsed node in the tree, or a child of a SelectUnionQuery, mark us as a top level query
|
# if we are the first parsed node in the tree, or a child of a SelectSetQuery, mark us as a top level query
|
||||||
part_of_select_union = len(self.stack) >= 2 and isinstance(self.stack[-2], ast.SelectUnionQuery)
|
part_of_select_union = len(self.stack) >= 2 and isinstance(self.stack[-2], ast.SelectSetQuery)
|
||||||
is_top_level_query = len(self.stack) <= 1 or (len(self.stack) == 2 and part_of_select_union)
|
is_top_level_query = len(self.stack) <= 1 or (len(self.stack) == 2 and part_of_select_union)
|
||||||
|
|
||||||
# We will add extra clauses onto this from the joined tables
|
# We will add extra clauses onto this from the joined tables
|
||||||
@ -507,7 +515,7 @@ class _Printer(Visitor):
|
|||||||
elif isinstance(node.type, ast.SelectQueryType):
|
elif isinstance(node.type, ast.SelectQueryType):
|
||||||
join_strings.append(self.visit(node.table))
|
join_strings.append(self.visit(node.table))
|
||||||
|
|
||||||
elif isinstance(node.type, ast.SelectUnionQueryType):
|
elif isinstance(node.type, ast.SelectSetQueryType):
|
||||||
join_strings.append(self.visit(node.table))
|
join_strings.append(self.visit(node.table))
|
||||||
|
|
||||||
elif isinstance(node.type, ast.SelectViewType) and node.alias is not None:
|
elif isinstance(node.type, ast.SelectViewType) and node.alias is not None:
|
||||||
@ -1251,7 +1259,7 @@ class _Printer(Visitor):
|
|||||||
isinstance(type.table_type, ast.SelectQueryType)
|
isinstance(type.table_type, ast.SelectQueryType)
|
||||||
or isinstance(type.table_type, ast.SelectQueryAliasType)
|
or isinstance(type.table_type, ast.SelectQueryAliasType)
|
||||||
or isinstance(type.table_type, ast.SelectViewType)
|
or isinstance(type.table_type, ast.SelectViewType)
|
||||||
or isinstance(type.table_type, ast.SelectUnionQueryType)
|
or isinstance(type.table_type, ast.SelectSetQueryType)
|
||||||
):
|
):
|
||||||
field_sql = self._print_identifier(type.name)
|
field_sql = self._print_identifier(type.name)
|
||||||
if isinstance(type.table_type, ast.SelectQueryAliasType) or isinstance(type.table_type, ast.SelectViewType):
|
if isinstance(type.table_type, ast.SelectQueryAliasType) or isinstance(type.table_type, ast.SelectViewType):
|
||||||
|
@ -19,6 +19,7 @@ from posthog.hogql.filters import replace_filters
|
|||||||
from posthog.hogql.timings import HogQLTimings
|
from posthog.hogql.timings import HogQLTimings
|
||||||
from posthog.hogql.variables import replace_variables
|
from posthog.hogql.variables import replace_variables
|
||||||
from posthog.hogql.visitor import clone_expr
|
from posthog.hogql.visitor import clone_expr
|
||||||
|
from posthog.hogql.resolver_utils import extract_select_queries
|
||||||
from posthog.models.team import Team
|
from posthog.models.team import Team
|
||||||
from posthog.clickhouse.query_tagging import tag_queries
|
from posthog.clickhouse.query_tagging import tag_queries
|
||||||
from posthog.client import sync_execute
|
from posthog.client import sync_execute
|
||||||
@ -35,7 +36,7 @@ from posthog.settings import HOGQL_INCREASED_MAX_EXECUTION_TIME
|
|||||||
|
|
||||||
|
|
||||||
def execute_hogql_query(
|
def execute_hogql_query(
|
||||||
query: Union[str, ast.SelectQuery, ast.SelectUnionQuery],
|
query: Union[str, ast.SelectQuery, ast.SelectSetQuery],
|
||||||
team: Team,
|
team: Team,
|
||||||
*,
|
*,
|
||||||
query_type: str = "hogql_query",
|
query_type: str = "hogql_query",
|
||||||
@ -65,7 +66,7 @@ def execute_hogql_query(
|
|||||||
metadata: Optional[HogQLMetadataResponse] = None
|
metadata: Optional[HogQLMetadataResponse] = None
|
||||||
|
|
||||||
with timings.measure("query"):
|
with timings.measure("query"):
|
||||||
if isinstance(query, ast.SelectQuery) or isinstance(query, ast.SelectUnionQuery):
|
if isinstance(query, ast.SelectQuery) or isinstance(query, ast.SelectSetQuery):
|
||||||
select_query = query
|
select_query = query
|
||||||
query = None
|
query = None
|
||||||
else:
|
else:
|
||||||
@ -105,10 +106,7 @@ def execute_hogql_query(
|
|||||||
select_query = replace_placeholders(select_query, placeholders)
|
select_query = replace_placeholders(select_query, placeholders)
|
||||||
|
|
||||||
with timings.measure("max_limit"):
|
with timings.measure("max_limit"):
|
||||||
select_queries = (
|
for one_query in extract_select_queries(select_query):
|
||||||
select_query.select_queries if isinstance(select_query, ast.SelectUnionQuery) else [select_query]
|
|
||||||
)
|
|
||||||
for one_query in select_queries:
|
|
||||||
if one_query.limit is None:
|
if one_query.limit is None:
|
||||||
one_query.limit = ast.Constant(value=get_default_limit_for_context(limit_context))
|
one_query.limit = ast.Constant(value=get_default_limit_for_context(limit_context))
|
||||||
|
|
||||||
@ -138,8 +136,8 @@ def execute_hogql_query(
|
|||||||
)
|
)
|
||||||
print_columns = []
|
print_columns = []
|
||||||
columns_query = (
|
columns_query = (
|
||||||
select_query_hogql.select_queries[0]
|
next(extract_select_queries(select_query_hogql))
|
||||||
if isinstance(select_query_hogql, ast.SelectUnionQuery)
|
if isinstance(select_query_hogql, ast.SelectSetQuery)
|
||||||
else select_query_hogql
|
else select_query_hogql
|
||||||
)
|
)
|
||||||
for node in columns_query.select:
|
for node in columns_query.select:
|
||||||
|
@ -24,7 +24,12 @@ from posthog.hogql.functions.recording_button import recording_button
|
|||||||
from posthog.hogql.functions.sparkline import sparkline
|
from posthog.hogql.functions.sparkline import sparkline
|
||||||
from posthog.hogql.hogqlx import HOGQLX_COMPONENTS, convert_to_hx
|
from posthog.hogql.hogqlx import HOGQLX_COMPONENTS, convert_to_hx
|
||||||
from posthog.hogql.parser import parse_select
|
from posthog.hogql.parser import parse_select
|
||||||
from posthog.hogql.resolver_utils import expand_hogqlx_query, lookup_cte_by_name, lookup_field_by_name
|
from posthog.hogql.resolver_utils import (
|
||||||
|
expand_hogqlx_query,
|
||||||
|
lookup_cte_by_name,
|
||||||
|
lookup_field_by_name,
|
||||||
|
extract_select_queries,
|
||||||
|
)
|
||||||
from posthog.hogql.visitor import CloningVisitor, TraversingVisitor, clone_expr
|
from posthog.hogql.visitor import CloningVisitor, TraversingVisitor, clone_expr
|
||||||
from posthog.models.utils import UUIDT
|
from posthog.models.utils import UUIDT
|
||||||
|
|
||||||
@ -123,15 +128,17 @@ class Resolver(CloningVisitor):
|
|||||||
raise QueryError("Too many CTE expansions (50+). Probably a CTE loop.")
|
raise QueryError("Too many CTE expansions (50+). Probably a CTE loop.")
|
||||||
return super().visit(node)
|
return super().visit(node)
|
||||||
|
|
||||||
def visit_select_union_query(self, node: ast.SelectUnionQuery):
|
def visit_select_set_query(self, node: ast.SelectSetQuery):
|
||||||
# all expressions combined by UNION ALL can use CTEs from the first expression
|
# all expressions combined by UNION ALL can use CTEs from the first expression
|
||||||
# so we put these CTEs to the scope
|
# so we put these CTEs to the scope
|
||||||
default_ctes = node.select_queries[0].ctes if node.select_queries else None
|
default_ctes = next(extract_select_queries(node)).ctes
|
||||||
if default_ctes:
|
if default_ctes:
|
||||||
self.scopes.append(ast.SelectQueryType(ctes=default_ctes))
|
self.scopes.append(ast.SelectQueryType(ctes=default_ctes))
|
||||||
|
|
||||||
node = super().visit_select_union_query(node)
|
node = super().visit_select_set_query(node)
|
||||||
node.type = ast.SelectUnionQueryType(types=[expr.type for expr in node.select_queries])
|
node.type = ast.SelectSetQueryType(
|
||||||
|
types=[node.initial_select_query.type, *(x.select_query.type for x in node.subsequent_select_queries)] # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
if default_ctes:
|
if default_ctes:
|
||||||
self.scopes.pop()
|
self.scopes.pop()
|
||||||
@ -254,7 +261,7 @@ class Resolver(CloningVisitor):
|
|||||||
database_fields = table.get_asterisk()
|
database_fields = table.get_asterisk()
|
||||||
return [ast.Field(chain=[key]) for key in database_fields.keys()]
|
return [ast.Field(chain=[key]) for key in database_fields.keys()]
|
||||||
elif (
|
elif (
|
||||||
isinstance(asterisk.table_type, ast.SelectUnionQueryType)
|
isinstance(asterisk.table_type, ast.SelectSetQueryType)
|
||||||
or isinstance(asterisk.table_type, ast.SelectQueryType)
|
or isinstance(asterisk.table_type, ast.SelectQueryType)
|
||||||
or isinstance(asterisk.table_type, ast.SelectQueryAliasType)
|
or isinstance(asterisk.table_type, ast.SelectQueryAliasType)
|
||||||
or isinstance(asterisk.table_type, ast.SelectViewType)
|
or isinstance(asterisk.table_type, ast.SelectViewType)
|
||||||
@ -262,7 +269,7 @@ class Resolver(CloningVisitor):
|
|||||||
select = asterisk.table_type
|
select = asterisk.table_type
|
||||||
while isinstance(select, ast.SelectQueryAliasType) or isinstance(select, ast.SelectViewType):
|
while isinstance(select, ast.SelectQueryAliasType) or isinstance(select, ast.SelectViewType):
|
||||||
select = select.select_query_type
|
select = select.select_query_type
|
||||||
if isinstance(select, ast.SelectUnionQueryType):
|
if isinstance(select, ast.SelectSetQueryType):
|
||||||
select = select.types[0]
|
select = select.types[0]
|
||||||
if isinstance(select, ast.SelectQueryType):
|
if isinstance(select, ast.SelectQueryType):
|
||||||
return [ast.Field(chain=[key]) for key in select.columns.keys()]
|
return [ast.Field(chain=[key]) for key in select.columns.keys()]
|
||||||
@ -369,7 +376,7 @@ class Resolver(CloningVisitor):
|
|||||||
|
|
||||||
return node
|
return node
|
||||||
|
|
||||||
elif isinstance(node.table, ast.SelectQuery) or isinstance(node.table, ast.SelectUnionQuery):
|
elif isinstance(node.table, ast.SelectQuery) or isinstance(node.table, ast.SelectSetQuery):
|
||||||
node = cast(ast.JoinExpr, clone_expr(node))
|
node = cast(ast.JoinExpr, clone_expr(node))
|
||||||
if node.constraint and node.constraint.constraint_type == "USING":
|
if node.constraint and node.constraint.constraint_type == "USING":
|
||||||
# visit USING constraint before adding the table to avoid ambiguous names
|
# visit USING constraint before adding the table to avoid ambiguous names
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from collections.abc import Generator
|
||||||
|
|
||||||
from posthog import schema
|
from posthog import schema
|
||||||
from posthog.hogql import ast
|
from posthog.hogql import ast
|
||||||
@ -86,3 +87,12 @@ def expand_hogqlx_query(node: ast.HogQLXTag, team_id: Optional[int]):
|
|||||||
return query
|
return query
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ResolutionError(f"Error parsing query tag: {e}", start=node.start, end=node.end)
|
raise ResolutionError(f"Error parsing query tag: {e}", start=node.start, end=node.end)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_select_queries(select: ast.SelectSetQuery | ast.SelectQuery) -> Generator[ast.SelectQuery, None, None]:
|
||||||
|
if isinstance(select, ast.SelectQuery):
|
||||||
|
yield select
|
||||||
|
else:
|
||||||
|
yield from extract_select_queries(select.initial_select_query)
|
||||||
|
for select_query in select.subsequent_select_queries:
|
||||||
|
yield from extract_select_queries(select_query.select_query)
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -21,6 +21,8 @@ from posthog.hogql.ast import (
|
|||||||
Array,
|
Array,
|
||||||
Dict,
|
Dict,
|
||||||
VariableDeclaration,
|
VariableDeclaration,
|
||||||
|
SelectSetNode,
|
||||||
|
SelectSetQuery,
|
||||||
)
|
)
|
||||||
|
|
||||||
from posthog.hogql.parser import parse_program
|
from posthog.hogql.parser import parse_program
|
||||||
@ -50,9 +52,9 @@ def parser_test_factory(backend: Literal["python", "cpp"]):
|
|||||||
|
|
||||||
def _select(
|
def _select(
|
||||||
self, query: str, placeholders: Optional[dict[str, ast.Expr]] = None
|
self, query: str, placeholders: Optional[dict[str, ast.Expr]] = None
|
||||||
) -> ast.SelectQuery | ast.SelectUnionQuery | ast.HogQLXTag:
|
) -> ast.SelectQuery | ast.SelectSetQuery | ast.HogQLXTag:
|
||||||
return cast(
|
return cast(
|
||||||
ast.SelectQuery | ast.SelectUnionQuery | ast.HogQLXTag,
|
ast.SelectQuery | ast.SelectSetQuery | ast.HogQLXTag,
|
||||||
clear_locations(parse_select(query, placeholders=placeholders, backend=backend)),
|
clear_locations(parse_select(query, placeholders=placeholders, backend=backend)),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1381,12 +1383,48 @@ def parser_test_factory(backend: Literal["python", "cpp"]):
|
|||||||
def test_select_union_all(self):
|
def test_select_union_all(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self._select("select 1 union all select 2 union all select 3"),
|
self._select("select 1 union all select 2 union all select 3"),
|
||||||
ast.SelectUnionQuery(
|
ast.SelectSetQuery(
|
||||||
select_queries=[
|
initial_select_query=ast.SelectQuery(select=[ast.Constant(value=1)]),
|
||||||
ast.SelectQuery(select=[ast.Constant(value=1)]),
|
subsequent_select_queries=[
|
||||||
ast.SelectQuery(select=[ast.Constant(value=2)]),
|
SelectSetNode(set_operator="UNION ALL", select_query=query)
|
||||||
ast.SelectQuery(select=[ast.Constant(value=3)]),
|
for query in (
|
||||||
]
|
ast.SelectQuery(select=[ast.Constant(value=2)]),
|
||||||
|
ast.SelectQuery(select=[ast.Constant(value=3)]),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_nested_selects(self):
|
||||||
|
self.assertEqual(
|
||||||
|
self._select("(select 1 intersect select 2) union all (select 3 except select 4)"),
|
||||||
|
SelectSetQuery(
|
||||||
|
initial_select_query=SelectSetQuery(
|
||||||
|
initial_select_query=SelectQuery(select=[Constant(value=1)]),
|
||||||
|
subsequent_select_queries=[
|
||||||
|
SelectSetNode(
|
||||||
|
select_query=SelectQuery(
|
||||||
|
select=[Constant(value=2)],
|
||||||
|
),
|
||||||
|
set_operator="INTERSECT",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
subsequent_select_queries=[
|
||||||
|
SelectSetNode(
|
||||||
|
select_query=SelectSetQuery(
|
||||||
|
initial_select_query=SelectQuery(
|
||||||
|
select=[Constant(value=3)],
|
||||||
|
),
|
||||||
|
subsequent_select_queries=[
|
||||||
|
SelectSetNode(
|
||||||
|
select_query=SelectQuery(select=[Constant(value=4)]), set_operator="EXCEPT"
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
set_operator="UNION ALL",
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -114,6 +114,136 @@ class TestPrinter(BaseTest):
|
|||||||
repsponse, f"SELECT\n plus(1, 2),\n 3\nFROM\n events\nLIMIT {MAX_SELECT_RETURNED_ROWS}"
|
repsponse, f"SELECT\n plus(1, 2),\n 3\nFROM\n events\nLIMIT {MAX_SELECT_RETURNED_ROWS}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_intersect(self):
|
||||||
|
expr = parse_select("""select 1 as id intersect select 2 as id""")
|
||||||
|
response = to_printed_hogql(expr, self.team)
|
||||||
|
self.assertEqual(
|
||||||
|
response,
|
||||||
|
f"SELECT\n 1 AS id\nLIMIT 50000\nINTERSECT\nSELECT\n 2 AS id\nLIMIT {MAX_SELECT_RETURNED_ROWS}",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_except(self):
|
||||||
|
expr = parse_select("""select 1 as id except select 2 as id""")
|
||||||
|
response = to_printed_hogql(expr, self.team)
|
||||||
|
self.assertEqual(
|
||||||
|
response,
|
||||||
|
f"SELECT\n 1 AS id\nLIMIT 50000\nEXCEPT\nSELECT\n 2 AS id\nLIMIT {MAX_SELECT_RETURNED_ROWS}",
|
||||||
|
)
|
||||||
|
|
||||||
|
# these share the same priority, should stay in order
|
||||||
|
def test_except_and_union(self):
|
||||||
|
expr = parse_select("""select 1 as id except select 2 as id union all select 3 as id""")
|
||||||
|
response = to_printed_hogql(expr, self.team)
|
||||||
|
self.assertEqual(
|
||||||
|
response,
|
||||||
|
(
|
||||||
|
"SELECT\n"
|
||||||
|
" 1 AS id\n"
|
||||||
|
"LIMIT 50000\n"
|
||||||
|
"EXCEPT\n"
|
||||||
|
"SELECT\n"
|
||||||
|
" 2 AS id\n"
|
||||||
|
"LIMIT 50000\n"
|
||||||
|
"UNION ALL\n"
|
||||||
|
"SELECT\n"
|
||||||
|
" 3 AS id\n"
|
||||||
|
"LIMIT 50000"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_union_and_except(self):
|
||||||
|
expr = parse_select("""select 1 as id union all select 2 as id except select 3 as id""")
|
||||||
|
response = to_printed_hogql(expr, self.team)
|
||||||
|
self.assertEqual(
|
||||||
|
response,
|
||||||
|
(
|
||||||
|
"SELECT\n"
|
||||||
|
" 1 AS id\n"
|
||||||
|
"LIMIT 50000\n"
|
||||||
|
"UNION ALL\n"
|
||||||
|
"SELECT\n"
|
||||||
|
" 2 AS id\n"
|
||||||
|
"LIMIT 50000\n"
|
||||||
|
"EXCEPT\n"
|
||||||
|
"SELECT\n"
|
||||||
|
" 3 AS id\n"
|
||||||
|
"LIMIT 50000"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_intersect3(self):
|
||||||
|
expr = parse_select("""select 1 as id intersect select 2 as id intersect select 3 as id""")
|
||||||
|
response = to_printed_hogql(expr, self.team)
|
||||||
|
self.assertEqual(
|
||||||
|
response,
|
||||||
|
"SELECT\n"
|
||||||
|
" 1 AS id\n"
|
||||||
|
"LIMIT 50000\n"
|
||||||
|
"INTERSECT\n"
|
||||||
|
"SELECT\n"
|
||||||
|
" 2 AS id\n"
|
||||||
|
"LIMIT 50000\n"
|
||||||
|
"INTERSECT\n"
|
||||||
|
"SELECT\n"
|
||||||
|
" 3 AS id\n"
|
||||||
|
"LIMIT 50000",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_union3(self):
|
||||||
|
expr = parse_select("""select 1 as id union all select 2 as id union all select 3 as id""")
|
||||||
|
response = to_printed_hogql(expr, self.team)
|
||||||
|
self.assertEqual(
|
||||||
|
response,
|
||||||
|
"SELECT\n"
|
||||||
|
" 1 AS id\n"
|
||||||
|
"LIMIT 50000\n"
|
||||||
|
"UNION ALL\n"
|
||||||
|
"SELECT\n"
|
||||||
|
" 2 AS id\n"
|
||||||
|
"LIMIT 50000\n"
|
||||||
|
"UNION ALL\n"
|
||||||
|
"SELECT\n"
|
||||||
|
" 3 AS id\n"
|
||||||
|
"LIMIT 50000",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_intersect_and_union_parens(self):
|
||||||
|
expr = parse_select("""select 1 as id intersect (select 2 as id union all select 3 as id)""")
|
||||||
|
response = to_printed_hogql(expr, self.team)
|
||||||
|
self.assertEqual(
|
||||||
|
response,
|
||||||
|
"SELECT\n"
|
||||||
|
" 1 AS id\n"
|
||||||
|
"LIMIT 50000\n"
|
||||||
|
"INTERSECT\n"
|
||||||
|
"(SELECT\n"
|
||||||
|
" 2 AS id\n"
|
||||||
|
"UNION ALL\n"
|
||||||
|
"SELECT\n"
|
||||||
|
" 3 AS id)",
|
||||||
|
)
|
||||||
|
|
||||||
|
# INTERSECT has higher priority than union
|
||||||
|
def test_intersect_and_union(self):
|
||||||
|
expr = parse_select("""select 1 as id union all select 2 as id intersect select 3 as id""")
|
||||||
|
response = to_printed_hogql(expr, self.team)
|
||||||
|
self.assertEqual(
|
||||||
|
response,
|
||||||
|
(
|
||||||
|
"SELECT\n"
|
||||||
|
" 1 AS id\n"
|
||||||
|
"LIMIT 50000\n"
|
||||||
|
"UNION ALL\n"
|
||||||
|
"SELECT\n"
|
||||||
|
" 2 AS id\n"
|
||||||
|
"LIMIT 50000\n"
|
||||||
|
"INTERSECT\n"
|
||||||
|
"SELECT\n"
|
||||||
|
" 3 AS id\n"
|
||||||
|
"LIMIT 50000"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def test_print_to_string(self):
|
def test_print_to_string(self):
|
||||||
assert str(parse_select("select 1 + 2, 3 from events")) == "sql(SELECT plus(1, 2), 3 FROM events)"
|
assert str(parse_select("select 1 + 2, 3 from events")) == "sql(SELECT plus(1, 2), 3 FROM events)"
|
||||||
assert str(parse_expr("1 + 2")) == "sql(plus(1, 2))"
|
assert str(parse_expr("1 + 2")) == "sql(plus(1, 2))"
|
||||||
@ -1076,7 +1206,7 @@ class TestPrinter(BaseTest):
|
|||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self._select("SELECT 1 UNION ALL (SELECT 1 UNION ALL SELECT 1) UNION ALL SELECT 1"),
|
self._select("SELECT 1 UNION ALL (SELECT 1 UNION ALL SELECT 1) UNION ALL SELECT 1"),
|
||||||
f"SELECT 1 LIMIT {MAX_SELECT_RETURNED_ROWS} UNION ALL SELECT 1 LIMIT {MAX_SELECT_RETURNED_ROWS} UNION ALL SELECT 1 LIMIT {MAX_SELECT_RETURNED_ROWS} UNION ALL SELECT 1 LIMIT {MAX_SELECT_RETURNED_ROWS}",
|
f"SELECT 1 LIMIT {MAX_SELECT_RETURNED_ROWS} UNION ALL (SELECT 1 UNION ALL SELECT 1) UNION ALL SELECT 1 LIMIT {MAX_SELECT_RETURNED_ROWS}",
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self._select("SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1"),
|
self._select("SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1"),
|
||||||
|
@ -2,6 +2,7 @@ from copy import deepcopy
|
|||||||
from typing import Optional, TypeVar, Generic, Any
|
from typing import Optional, TypeVar, Generic, Any
|
||||||
|
|
||||||
from posthog.hogql import ast
|
from posthog.hogql import ast
|
||||||
|
from posthog.hogql.ast import SelectSetNode
|
||||||
from posthog.hogql.base import AST, Expr
|
from posthog.hogql.base import AST, Expr
|
||||||
from posthog.hogql.errors import BaseHogQLError
|
from posthog.hogql.errors import BaseHogQLError
|
||||||
|
|
||||||
@ -151,9 +152,10 @@ class TraversingVisitor(Visitor[None]):
|
|||||||
for expr in (node.window_exprs or {}).values():
|
for expr in (node.window_exprs or {}).values():
|
||||||
self.visit(expr)
|
self.visit(expr)
|
||||||
|
|
||||||
def visit_select_union_query(self, node: ast.SelectUnionQuery):
|
def visit_select_set_query(self, node: ast.SelectSetQuery):
|
||||||
for expr in node.select_queries:
|
self.visit(node.initial_select_query)
|
||||||
self.visit(expr)
|
for expr in node.subsequent_select_queries:
|
||||||
|
self.visit(expr.select_query)
|
||||||
|
|
||||||
def visit_lambda_argument_type(self, node: ast.LambdaArgumentType):
|
def visit_lambda_argument_type(self, node: ast.LambdaArgumentType):
|
||||||
pass
|
pass
|
||||||
@ -174,7 +176,7 @@ class TraversingVisitor(Visitor[None]):
|
|||||||
for expr in node.columns.values():
|
for expr in node.columns.values():
|
||||||
self.visit(expr)
|
self.visit(expr)
|
||||||
|
|
||||||
def visit_select_union_query_type(self, node: ast.SelectUnionQueryType):
|
def visit_select_set_query_type(self, node: ast.SelectSetQueryType):
|
||||||
for type in node.types:
|
for type in node.types:
|
||||||
self.visit(type)
|
self.visit(type)
|
||||||
|
|
||||||
@ -600,12 +602,16 @@ class CloningVisitor(Visitor[Any]):
|
|||||||
view_name=node.view_name,
|
view_name=node.view_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
def visit_select_union_query(self, node: ast.SelectUnionQuery):
|
def visit_select_set_query(self, node: ast.SelectSetQuery):
|
||||||
return ast.SelectUnionQuery(
|
return ast.SelectSetQuery(
|
||||||
start=None if self.clear_locations else node.start,
|
start=None if self.clear_locations else node.start,
|
||||||
end=None if self.clear_locations else node.end,
|
end=None if self.clear_locations else node.end,
|
||||||
type=None if self.clear_types else node.type,
|
type=None if self.clear_types else node.type,
|
||||||
select_queries=[self.visit(expr) for expr in node.select_queries],
|
initial_select_query=self.visit(node.initial_select_query),
|
||||||
|
subsequent_select_queries=[
|
||||||
|
SelectSetNode(set_operator=expr.set_operator, select_query=self.visit(expr.select_query))
|
||||||
|
for expr in node.subsequent_select_queries
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def visit_window_expr(self, node: ast.WindowExpr):
|
def visit_window_expr(self, node: ast.WindowExpr):
|
||||||
|
@ -6,6 +6,7 @@ from posthog.hogql import ast
|
|||||||
from posthog.hogql.constants import HogQLGlobalSettings
|
from posthog.hogql.constants import HogQLGlobalSettings
|
||||||
from posthog.hogql.parser import parse_expr, parse_order_expr
|
from posthog.hogql.parser import parse_expr, parse_order_expr
|
||||||
from posthog.hogql.property import has_aggregation
|
from posthog.hogql.property import has_aggregation
|
||||||
|
from posthog.hogql.resolver_utils import extract_select_queries
|
||||||
from posthog.hogql_queries.actor_strategies import ActorStrategy, PersonStrategy, GroupStrategy
|
from posthog.hogql_queries.actor_strategies import ActorStrategy, PersonStrategy, GroupStrategy
|
||||||
from posthog.hogql_queries.insights.funnels.funnels_query_runner import FunnelsQueryRunner
|
from posthog.hogql_queries.insights.funnels.funnels_query_runner import FunnelsQueryRunner
|
||||||
from posthog.hogql_queries.insights.insight_actors_query_runner import InsightActorsQueryRunner
|
from posthog.hogql_queries.insights.insight_actors_query_runner import InsightActorsQueryRunner
|
||||||
@ -142,12 +143,12 @@ class ActorsQueryRunner(QueryRunner):
|
|||||||
return self.strategy.input_columns()
|
return self.strategy.input_columns()
|
||||||
|
|
||||||
# TODO: Figure out a more sure way of getting the actor id than using the alias or chain name
|
# TODO: Figure out a more sure way of getting the actor id than using the alias or chain name
|
||||||
def source_id_column(self, source_query: ast.SelectQuery | ast.SelectUnionQuery) -> list[str]:
|
def source_id_column(self, source_query: ast.SelectQuery | ast.SelectSetQuery) -> list[str]:
|
||||||
# Figure out the id column of the source query, first column that has id in the name
|
# Figure out the id column of the source query, first column that has id in the name
|
||||||
if isinstance(source_query, ast.SelectQuery):
|
if isinstance(source_query, ast.SelectQuery):
|
||||||
select = source_query.select
|
select = source_query.select
|
||||||
else:
|
else:
|
||||||
select = source_query.select_queries[0].select
|
select = next(extract_select_queries(source_query)).select
|
||||||
|
|
||||||
for column in select:
|
for column in select:
|
||||||
if isinstance(column, ast.Alias) and (column.alias in ("group_key", "actor_id", "person_id")):
|
if isinstance(column, ast.Alias) and (column.alias in ("group_key", "actor_id", "person_id")):
|
||||||
|
@ -49,7 +49,7 @@ class ActorsPropertyTaxonomyQueryRunner(TaxonomyCacheMixin, QueryRunner):
|
|||||||
modifiers=self.modifiers,
|
modifiers=self.modifiers,
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def to_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
query = ast.SelectQuery(
|
query = ast.SelectQuery(
|
||||||
select=[
|
select=[
|
||||||
ast.Call(name="groupArray", args=[ast.Field(chain=["prop"])], params=[ast.Constant(value=5)]),
|
ast.Call(name="groupArray", args=[ast.Field(chain=["prop"])], params=[ast.Constant(value=5)]),
|
||||||
|
@ -49,7 +49,7 @@ class EventTaxonomyQueryRunner(TaxonomyCacheMixin, QueryRunner):
|
|||||||
modifiers=self.modifiers,
|
modifiers=self.modifiers,
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def to_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
query = parse_select(
|
query = parse_select(
|
||||||
"""
|
"""
|
||||||
SELECT
|
SELECT
|
||||||
|
@ -43,7 +43,7 @@ class TeamTaxonomyQueryRunner(TaxonomyCacheMixin, QueryRunner):
|
|||||||
results=results, timings=response.timings, hogql=hogql, modifiers=self.modifiers
|
results=results, timings=response.timings, hogql=hogql, modifiers=self.modifiers
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def to_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
query = parse_select(
|
query = parse_select(
|
||||||
"""
|
"""
|
||||||
SELECT
|
SELECT
|
||||||
|
@ -532,7 +532,7 @@ class FunnelBase(ABC):
|
|||||||
|
|
||||||
return ast.JoinExpr(
|
return ast.JoinExpr(
|
||||||
join_type="INNER JOIN",
|
join_type="INNER JOIN",
|
||||||
table=ast.SelectUnionQuery(select_queries=cohort_queries),
|
table=ast.SelectSetQuery.create_from_queries(cohort_queries, "UNION ALL"),
|
||||||
alias="cohort_join",
|
alias="cohort_join",
|
||||||
constraint=ast.JoinConstraint(
|
constraint=ast.JoinConstraint(
|
||||||
expr=ast.CompareOperation(
|
expr=ast.CompareOperation(
|
||||||
|
@ -332,7 +332,7 @@ class FunnelCorrelationQueryRunner(QueryRunner):
|
|||||||
|
|
||||||
return EventDefinition(event=event, properties={}, elements=[])
|
return EventDefinition(event=event, properties={}, elements=[])
|
||||||
|
|
||||||
def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def to_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
"""
|
"""
|
||||||
Returns a query string and params, which are used to generate the contingency table.
|
Returns a query string and params, which are used to generate the contingency table.
|
||||||
The query returns success and failure count for event / property values, along with total success and failure counts.
|
The query returns success and failure count for event / property values, along with total success and failure counts.
|
||||||
@ -345,7 +345,7 @@ class FunnelCorrelationQueryRunner(QueryRunner):
|
|||||||
|
|
||||||
return self.get_event_query()
|
return self.get_event_query()
|
||||||
|
|
||||||
def to_actors_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def to_actors_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
assert self.correlation_actors_query is not None
|
assert self.correlation_actors_query is not None
|
||||||
|
|
||||||
if self.query.funnelCorrelationType == FunnelCorrelationResultsType.PROPERTIES:
|
if self.query.funnelCorrelationType == FunnelCorrelationResultsType.PROPERTIES:
|
||||||
@ -362,7 +362,7 @@ class FunnelCorrelationQueryRunner(QueryRunner):
|
|||||||
else:
|
else:
|
||||||
return self.events_actor_query()
|
return self.events_actor_query()
|
||||||
|
|
||||||
def events_actor_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def events_actor_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
assert self.correlation_actors_query is not None
|
assert self.correlation_actors_query is not None
|
||||||
|
|
||||||
if not self.correlation_actors_query.funnelCorrelationPersonEntity:
|
if not self.correlation_actors_query.funnelCorrelationPersonEntity:
|
||||||
@ -431,7 +431,7 @@ class FunnelCorrelationQueryRunner(QueryRunner):
|
|||||||
|
|
||||||
def properties_actor_query(
|
def properties_actor_query(
|
||||||
self,
|
self,
|
||||||
) -> ast.SelectQuery | ast.SelectUnionQuery:
|
) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
assert self.correlation_actors_query is not None
|
assert self.correlation_actors_query is not None
|
||||||
|
|
||||||
if not self.correlation_actors_query.funnelCorrelationPropertyValues:
|
if not self.correlation_actors_query.funnelCorrelationPropertyValues:
|
||||||
@ -470,7 +470,7 @@ class FunnelCorrelationQueryRunner(QueryRunner):
|
|||||||
|
|
||||||
return query
|
return query
|
||||||
|
|
||||||
def get_event_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def get_event_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
funnel_persons_query = self.get_funnel_actors_cte()
|
funnel_persons_query = self.get_funnel_actors_cte()
|
||||||
event_join_query = self._get_events_join_query()
|
event_join_query = self._get_events_join_query()
|
||||||
target_step = self.context.max_steps
|
target_step = self.context.max_steps
|
||||||
@ -548,7 +548,7 @@ class FunnelCorrelationQueryRunner(QueryRunner):
|
|||||||
|
|
||||||
return event_correlation_query
|
return event_correlation_query
|
||||||
|
|
||||||
def get_event_property_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def get_event_property_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
if not self.query.funnelCorrelationEventNames:
|
if not self.query.funnelCorrelationEventNames:
|
||||||
raise ValidationError("Event Property Correlation expects atleast one event name to run correlation on")
|
raise ValidationError("Event Property Correlation expects atleast one event name to run correlation on")
|
||||||
|
|
||||||
@ -646,7 +646,7 @@ class FunnelCorrelationQueryRunner(QueryRunner):
|
|||||||
|
|
||||||
return query
|
return query
|
||||||
|
|
||||||
def get_properties_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def get_properties_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
if not self.query.funnelCorrelationNames:
|
if not self.query.funnelCorrelationNames:
|
||||||
raise ValidationError("Property Correlation expects atleast one Property to run correlation on")
|
raise ValidationError("Property Correlation expects atleast one Property to run correlation on")
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ class FunnelUnordered(FunnelBase):
|
|||||||
entities_to_use.append(entities_to_use.pop(0))
|
entities_to_use.append(entities_to_use.pop(0))
|
||||||
union_queries.append(formatted_query)
|
union_queries.append(formatted_query)
|
||||||
|
|
||||||
return ast.SelectUnionQuery(select_queries=union_queries)
|
return ast.SelectSetQuery.create_from_queries(union_queries, "UNION ALL")
|
||||||
|
|
||||||
def _get_step_times(self, max_steps: int) -> list[ast.Expr]:
|
def _get_step_times(self, max_steps: int) -> list[ast.Expr]:
|
||||||
windowInterval = self.context.funnelWindowInterval
|
windowInterval = self.context.funnelWindowInterval
|
||||||
|
@ -76,7 +76,7 @@ class FunnelsQueryRunner(QueryRunner):
|
|||||||
def to_query(self) -> ast.SelectQuery:
|
def to_query(self) -> ast.SelectQuery:
|
||||||
return self.funnel_class.get_query()
|
return self.funnel_class.get_query()
|
||||||
|
|
||||||
def to_actors_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def to_actors_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
return self.funnel_actor_class.actor_query()
|
return self.funnel_actor_class.actor_query()
|
||||||
|
|
||||||
def calculate(self):
|
def calculate(self):
|
||||||
|
@ -21,7 +21,7 @@ class InsightActorsQueryOptionsRunner(QueryRunner):
|
|||||||
def source_runner(self) -> QueryRunner:
|
def source_runner(self) -> QueryRunner:
|
||||||
return get_query_runner(self.query.source.source, self.team, self.timings, self.limit_context)
|
return get_query_runner(self.query.source.source, self.team, self.timings, self.limit_context)
|
||||||
|
|
||||||
def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def to_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
raise ValueError(f"Cannot convert source query of type {self.query.source.kind} to query")
|
raise ValueError(f"Cannot convert source query of type {self.query.source.kind} to query")
|
||||||
|
|
||||||
def calculate(self) -> InsightActorsQueryOptionsResponse:
|
def calculate(self) -> InsightActorsQueryOptionsResponse:
|
||||||
|
@ -33,7 +33,7 @@ class InsightActorsQueryRunner(QueryRunner):
|
|||||||
def source_runner(self) -> QueryRunner:
|
def source_runner(self) -> QueryRunner:
|
||||||
return get_query_runner(self.query.source, self.team, self.timings, self.limit_context, self.modifiers)
|
return get_query_runner(self.query.source, self.team, self.timings, self.limit_context, self.modifiers)
|
||||||
|
|
||||||
def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def to_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
if isinstance(self.source_runner, TrendsQueryRunner):
|
if isinstance(self.source_runner, TrendsQueryRunner):
|
||||||
trends_runner = cast(TrendsQueryRunner, self.source_runner)
|
trends_runner = cast(TrendsQueryRunner, self.source_runner)
|
||||||
query = cast(InsightActorsQuery, self.query)
|
query = cast(InsightActorsQuery, self.query)
|
||||||
@ -73,7 +73,7 @@ class InsightActorsQueryRunner(QueryRunner):
|
|||||||
|
|
||||||
raise ValueError(f"Cannot convert source query of type {self.query.source.kind} to persons query")
|
raise ValueError(f"Cannot convert source query of type {self.query.source.kind} to persons query")
|
||||||
|
|
||||||
def to_actors_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def to_actors_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
return self.to_query()
|
return self.to_query()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -33,7 +33,7 @@ class LifecycleQueryRunner(QueryRunner):
|
|||||||
response: LifecycleQueryResponse
|
response: LifecycleQueryResponse
|
||||||
cached_response: CachedLifecycleQueryResponse
|
cached_response: CachedLifecycleQueryResponse
|
||||||
|
|
||||||
def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def to_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
if self.query.samplingFactor == 0:
|
if self.query.samplingFactor == 0:
|
||||||
counts_with_sampling = ast.Constant(value=0)
|
counts_with_sampling = ast.Constant(value=0)
|
||||||
elif self.query.samplingFactor is not None and self.query.samplingFactor != 1:
|
elif self.query.samplingFactor is not None and self.query.samplingFactor != 1:
|
||||||
@ -95,7 +95,7 @@ class LifecycleQueryRunner(QueryRunner):
|
|||||||
|
|
||||||
def to_actors_query(
|
def to_actors_query(
|
||||||
self, day: Optional[str] = None, status: Optional[str] = None
|
self, day: Optional[str] = None, status: Optional[str] = None
|
||||||
) -> ast.SelectQuery | ast.SelectUnionQuery:
|
) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
with self.timings.measure("actors_query"):
|
with self.timings.measure("actors_query"):
|
||||||
exprs = []
|
exprs = []
|
||||||
if day is not None:
|
if day is not None:
|
||||||
|
@ -750,7 +750,7 @@ class PathsQueryRunner(QueryRunner):
|
|||||||
)
|
)
|
||||||
return conditions
|
return conditions
|
||||||
|
|
||||||
def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def to_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
placeholders: dict[str, ast.Expr] = {
|
placeholders: dict[str, ast.Expr] = {
|
||||||
"paths_per_person_query": self.paths_per_person_query(),
|
"paths_per_person_query": self.paths_per_person_query(),
|
||||||
}
|
}
|
||||||
@ -876,7 +876,7 @@ class PathsQueryRunner(QueryRunner):
|
|||||||
def extra_event_fields_and_properties(self) -> list[str]:
|
def extra_event_fields_and_properties(self) -> list[str]:
|
||||||
return self.extra_event_fields + self.extra_event_properties
|
return self.extra_event_fields + self.extra_event_properties
|
||||||
|
|
||||||
def to_actors_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def to_actors_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
# To include matching_events, we need to add extra fields and properties
|
# To include matching_events, we need to add extra fields and properties
|
||||||
# TODO: Make sure going via self is the best way to do this
|
# TODO: Make sure going via self is the best way to do this
|
||||||
self.extra_event_fields = ["uuid", "timestamp"]
|
self.extra_event_fields = ["uuid", "timestamp"]
|
||||||
|
@ -316,7 +316,7 @@ class RetentionQueryRunner(QueryRunner):
|
|||||||
|
|
||||||
return inner_query
|
return inner_query
|
||||||
|
|
||||||
def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def to_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
with self.timings.measure("retention_query"):
|
with self.timings.measure("retention_query"):
|
||||||
if self.query.retentionFilter.cumulative:
|
if self.query.retentionFilter.cumulative:
|
||||||
actor_query = parse_select(
|
actor_query = parse_select(
|
||||||
|
@ -128,8 +128,8 @@ class StickinessQueryRunner(QueryRunner):
|
|||||||
|
|
||||||
return cast(ast.SelectQuery, select_query)
|
return cast(ast.SelectQuery, select_query)
|
||||||
|
|
||||||
def to_query(self) -> ast.SelectUnionQuery:
|
def to_query(self) -> ast.SelectSetQuery:
|
||||||
return ast.SelectUnionQuery(select_queries=self.to_queries())
|
return ast.SelectSetQuery.create_from_queries(self.to_queries(), "UNION ALL")
|
||||||
|
|
||||||
def to_queries(self) -> list[ast.SelectQuery]:
|
def to_queries(self) -> list[ast.SelectQuery]:
|
||||||
queries = []
|
queries = []
|
||||||
@ -170,7 +170,7 @@ class StickinessQueryRunner(QueryRunner):
|
|||||||
|
|
||||||
return queries
|
return queries
|
||||||
|
|
||||||
def to_actors_query(self, interval_num: Optional[int] = None) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def to_actors_query(self, interval_num: Optional[int] = None) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
queries: list[ast.SelectQuery] = []
|
queries: list[ast.SelectQuery] = []
|
||||||
|
|
||||||
for series in self.series:
|
for series in self.series:
|
||||||
@ -194,7 +194,7 @@ class StickinessQueryRunner(QueryRunner):
|
|||||||
|
|
||||||
queries.append(events_query)
|
queries.append(events_query)
|
||||||
|
|
||||||
return ast.SelectUnionQuery(select_queries=queries)
|
return ast.SelectSetQuery.create_from_queries(queries, "UNION ALL")
|
||||||
|
|
||||||
def calculate(self):
|
def calculate(self):
|
||||||
queries = self.to_queries()
|
queries = self.to_queries()
|
||||||
|
@ -187,8 +187,8 @@ class AggregationOperations(DataWarehouseInsightQueryMixin):
|
|||||||
return f"toStartOf{self.query_date_range.interval_name.title()}"
|
return f"toStartOf{self.query_date_range.interval_name.title()}"
|
||||||
|
|
||||||
def _actors_parent_select_query(
|
def _actors_parent_select_query(
|
||||||
self, inner_query: ast.SelectQuery | ast.SelectUnionQuery
|
self, inner_query: ast.SelectQuery | ast.SelectSetQuery
|
||||||
) -> ast.SelectQuery | ast.SelectUnionQuery:
|
) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
if self.is_count_per_actor_variant():
|
if self.is_count_per_actor_variant():
|
||||||
query = parse_select(
|
query = parse_select(
|
||||||
"SELECT total FROM {inner_query}",
|
"SELECT total FROM {inner_query}",
|
||||||
@ -232,8 +232,8 @@ class AggregationOperations(DataWarehouseInsightQueryMixin):
|
|||||||
return query
|
return query
|
||||||
|
|
||||||
def _actors_inner_select_query(
|
def _actors_inner_select_query(
|
||||||
self, cross_join_select_query: ast.SelectQuery | ast.SelectUnionQuery
|
self, cross_join_select_query: ast.SelectQuery | ast.SelectSetQuery
|
||||||
) -> ast.SelectQuery | ast.SelectUnionQuery:
|
) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
if self.is_count_per_actor_variant():
|
if self.is_count_per_actor_variant():
|
||||||
if self.series.math == "avg_count_per_actor":
|
if self.series.math == "avg_count_per_actor":
|
||||||
math_func = self._math_func("avg", ["total"])
|
math_func = self._math_func("avg", ["total"])
|
||||||
@ -308,7 +308,7 @@ class AggregationOperations(DataWarehouseInsightQueryMixin):
|
|||||||
|
|
||||||
def _actors_events_query(
|
def _actors_events_query(
|
||||||
self, events_where_clause: ast.Expr, sample_value: ast.RatioExpr
|
self, events_where_clause: ast.Expr, sample_value: ast.RatioExpr
|
||||||
) -> ast.SelectQuery | ast.SelectUnionQuery:
|
) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
date_from_with_lookback = "{date_from} - {inclusive_lookback}"
|
date_from_with_lookback = "{date_from} - {inclusive_lookback}"
|
||||||
if self.chart_display_type in NON_TIME_SERIES_DISPLAY_TYPES and self.series.math in (
|
if self.chart_display_type in NON_TIME_SERIES_DISPLAY_TYPES and self.series.math in (
|
||||||
BaseMathType.WEEKLY_ACTIVE,
|
BaseMathType.WEEKLY_ACTIVE,
|
||||||
|
@ -31,7 +31,7 @@ class TrendsDisplay:
|
|||||||
def should_wrap_inner_query(self) -> bool:
|
def should_wrap_inner_query(self) -> bool:
|
||||||
return self.display_type == ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE
|
return self.display_type == ChartDisplayType.ACTIONS_LINE_GRAPH_CUMULATIVE
|
||||||
|
|
||||||
def _build_aggregate_dates(self, dates_queries: ast.SelectUnionQuery) -> ast.Expr:
|
def _build_aggregate_dates(self, dates_queries: ast.SelectSetQuery) -> ast.Expr:
|
||||||
return parse_select(
|
return parse_select(
|
||||||
"""
|
"""
|
||||||
SELECT day_start
|
SELECT day_start
|
||||||
@ -49,7 +49,7 @@ class TrendsDisplay:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def modify_outer_query(
|
def modify_outer_query(
|
||||||
self, outer_query: ast.SelectQuery, inner_query: ast.SelectQuery, dates_queries: ast.SelectUnionQuery
|
self, outer_query: ast.SelectQuery, inner_query: ast.SelectQuery, dates_queries: ast.SelectSetQuery
|
||||||
) -> ast.SelectQuery:
|
) -> ast.SelectQuery:
|
||||||
if not self.is_total_value():
|
if not self.is_total_value():
|
||||||
return outer_query
|
return outer_query
|
||||||
|
@ -167,7 +167,7 @@ class TrendsActorsQueryBuilder:
|
|||||||
def is_total_value(self) -> bool:
|
def is_total_value(self) -> bool:
|
||||||
return self.trends_display.is_total_value()
|
return self.trends_display.is_total_value()
|
||||||
|
|
||||||
def build_actors_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def build_actors_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
return ast.SelectQuery(
|
return ast.SelectQuery(
|
||||||
select=[
|
select=[
|
||||||
ast.Field(chain=["actor_id"]),
|
ast.Field(chain=["actor_id"]),
|
||||||
|
@ -60,7 +60,7 @@ class TrendsQueryBuilder(DataWarehouseInsightQueryMixin):
|
|||||||
self.modifiers = modifiers
|
self.modifiers = modifiers
|
||||||
self.limit_context = limit_context
|
self.limit_context = limit_context
|
||||||
|
|
||||||
def build_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def build_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
breakdown = self.breakdown
|
breakdown = self.breakdown
|
||||||
events_query = self._get_events_subquery(False, is_actors_query=False, breakdown=breakdown)
|
events_query = self._get_events_subquery(False, is_actors_query=False, breakdown=breakdown)
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ class TrendsQueryBuilder(DataWarehouseInsightQueryMixin):
|
|||||||
|
|
||||||
def _get_wrapper_query(
|
def _get_wrapper_query(
|
||||||
self, events_query: ast.SelectQuery, breakdown: Breakdown
|
self, events_query: ast.SelectQuery, breakdown: Breakdown
|
||||||
) -> ast.SelectQuery | ast.SelectUnionQuery:
|
) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
if not breakdown.enabled:
|
if not breakdown.enabled:
|
||||||
return events_query
|
return events_query
|
||||||
|
|
||||||
@ -312,7 +312,7 @@ class TrendsQueryBuilder(DataWarehouseInsightQueryMixin):
|
|||||||
|
|
||||||
def _outer_select_query(
|
def _outer_select_query(
|
||||||
self, breakdown: Breakdown, inner_query: ast.SelectQuery
|
self, breakdown: Breakdown, inner_query: ast.SelectQuery
|
||||||
) -> ast.SelectQuery | ast.SelectUnionQuery:
|
) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
total_array = parse_expr(
|
total_array = parse_expr(
|
||||||
"""
|
"""
|
||||||
arrayMap(
|
arrayMap(
|
||||||
@ -453,7 +453,7 @@ class TrendsQueryBuilder(DataWarehouseInsightQueryMixin):
|
|||||||
) or get_breakdown_limit_for_context(self.limit_context)
|
) or get_breakdown_limit_for_context(self.limit_context)
|
||||||
|
|
||||||
def _inner_select_query(
|
def _inner_select_query(
|
||||||
self, breakdown: Breakdown, inner_query: ast.SelectQuery | ast.SelectUnionQuery
|
self, breakdown: Breakdown, inner_query: ast.SelectQuery | ast.SelectSetQuery
|
||||||
) -> ast.SelectQuery:
|
) -> ast.SelectQuery:
|
||||||
query = cast(
|
query = cast(
|
||||||
ast.SelectQuery,
|
ast.SelectQuery,
|
||||||
|
@ -110,16 +110,10 @@ class TrendsQueryRunner(QueryRunner):
|
|||||||
|
|
||||||
return BASE_MINIMUM_INSIGHT_REFRESH_INTERVAL
|
return BASE_MINIMUM_INSIGHT_REFRESH_INTERVAL
|
||||||
|
|
||||||
def to_query(self) -> ast.SelectUnionQuery:
|
def to_query(self) -> ast.SelectSetQuery:
|
||||||
queries = []
|
return ast.SelectSetQuery.create_from_queries(self.to_queries(), "UNION ALL")
|
||||||
for query in self.to_queries():
|
|
||||||
if isinstance(query, ast.SelectQuery):
|
|
||||||
queries.append(query)
|
|
||||||
else:
|
|
||||||
queries.extend(query.select_queries)
|
|
||||||
return ast.SelectUnionQuery(select_queries=queries)
|
|
||||||
|
|
||||||
def to_queries(self) -> list[ast.SelectQuery | ast.SelectUnionQuery]:
|
def to_queries(self) -> list[ast.SelectQuery | ast.SelectSetQuery]:
|
||||||
queries = []
|
queries = []
|
||||||
with self.timings.measure("trends_to_query"):
|
with self.timings.measure("trends_to_query"):
|
||||||
for series in self.series:
|
for series in self.series:
|
||||||
@ -154,7 +148,7 @@ class TrendsQueryRunner(QueryRunner):
|
|||||||
breakdown_value: Optional[str | int | list[str]] = None,
|
breakdown_value: Optional[str | int | list[str]] = None,
|
||||||
compare_value: Optional[Compare] = None,
|
compare_value: Optional[Compare] = None,
|
||||||
include_recordings: Optional[bool] = None,
|
include_recordings: Optional[bool] = None,
|
||||||
) -> ast.SelectQuery | ast.SelectUnionQuery:
|
) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
with self.timings.measure("trends_to_actors_query"):
|
with self.timings.measure("trends_to_actors_query"):
|
||||||
if self.query.breakdownFilter and self.query.breakdownFilter.breakdown_type == BreakdownType.COHORT:
|
if self.query.breakdownFilter and self.query.breakdownFilter.breakdown_type == BreakdownType.COHORT:
|
||||||
if self.query.breakdownFilter.breakdown in ("all", ["all"]) or breakdown_value == "all":
|
if self.query.breakdownFilter.breakdown in ("all", ["all"]) or breakdown_value == "all":
|
||||||
@ -300,18 +294,16 @@ class TrendsQueryRunner(QueryRunner):
|
|||||||
def calculate(self):
|
def calculate(self):
|
||||||
queries = self.to_queries()
|
queries = self.to_queries()
|
||||||
|
|
||||||
if len(queries) == 1:
|
if len(queries) == 0:
|
||||||
response_hogql_query = queries[0]
|
response_hogql = ""
|
||||||
else:
|
else:
|
||||||
response_hogql_query = ast.SelectUnionQuery(select_queries=[])
|
if len(queries) == 1:
|
||||||
for query in queries:
|
response_hogql_query = queries[0]
|
||||||
if isinstance(query, ast.SelectQuery):
|
else:
|
||||||
response_hogql_query.select_queries.append(query)
|
response_hogql_query = ast.SelectSetQuery.create_from_queries(queries, "UNION ALL")
|
||||||
else:
|
|
||||||
response_hogql_query.select_queries.extend(query.select_queries)
|
|
||||||
|
|
||||||
with self.timings.measure("printing_hogql_for_response"):
|
with self.timings.measure("printing_hogql_for_response"):
|
||||||
response_hogql = to_printed_hogql(response_hogql_query, self.team, self.modifiers)
|
response_hogql = to_printed_hogql(response_hogql_query, self.team, self.modifiers)
|
||||||
|
|
||||||
res_matrix: list[list[Any] | Any | None] = [None] * len(queries)
|
res_matrix: list[list[Any] | Any | None] = [None] * len(queries)
|
||||||
timings_matrix: list[list[QueryTiming] | None] = [None] * (2 + len(queries))
|
timings_matrix: list[list[QueryTiming] | None] = [None] * (2 + len(queries))
|
||||||
@ -320,7 +312,7 @@ class TrendsQueryRunner(QueryRunner):
|
|||||||
|
|
||||||
def run(
|
def run(
|
||||||
index: int,
|
index: int,
|
||||||
query: ast.SelectQuery | ast.SelectUnionQuery,
|
query: ast.SelectQuery | ast.SelectSetQuery,
|
||||||
timings: HogQLTimings,
|
timings: HogQLTimings,
|
||||||
is_parallel: bool,
|
is_parallel: bool,
|
||||||
query_tags: Optional[dict] = None,
|
query_tags: Optional[dict] = None,
|
||||||
|
@ -11,7 +11,7 @@ class QueryAlternator:
|
|||||||
_group_bys: list[ast.Expr]
|
_group_bys: list[ast.Expr]
|
||||||
_select_from: ast.JoinExpr | None
|
_select_from: ast.JoinExpr | None
|
||||||
|
|
||||||
def __init__(self, query: ast.SelectQuery | ast.SelectUnionQuery):
|
def __init__(self, query: ast.SelectQuery | ast.SelectSetQuery):
|
||||||
assert isinstance(query, ast.SelectQuery)
|
assert isinstance(query, ast.SelectQuery)
|
||||||
|
|
||||||
self._query = query
|
self._query = query
|
||||||
@ -19,7 +19,7 @@ class QueryAlternator:
|
|||||||
self._group_bys = []
|
self._group_bys = []
|
||||||
self._select_from = None
|
self._select_from = None
|
||||||
|
|
||||||
def build(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def build(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
if len(self._selects) > 0:
|
if len(self._selects) > 0:
|
||||||
self._query.select.extend(self._selects)
|
self._query.select.extend(self._selects)
|
||||||
|
|
||||||
|
@ -708,10 +708,10 @@ class QueryRunner(ABC, Generic[Q, R, CR]):
|
|||||||
return fresh_response
|
return fresh_response
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def to_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def to_actors_query(self, *args, **kwargs) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def to_actors_query(self, *args, **kwargs) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
# TODO: add support for selecting and filtering by breakdowns
|
# TODO: add support for selecting and filtering by breakdowns
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ class WebTopClicksQueryRunner(WebAnalyticsQueryRunner):
|
|||||||
response: WebTopClicksQueryResponse
|
response: WebTopClicksQueryResponse
|
||||||
cached_response: CachedWebTopClicksQueryResponse
|
cached_response: CachedWebTopClicksQueryResponse
|
||||||
|
|
||||||
def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def to_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
with self.timings.measure("top_clicks_query"):
|
with self.timings.measure("top_clicks_query"):
|
||||||
top_sources_query = parse_select(
|
top_sources_query = parse_select(
|
||||||
"""
|
"""
|
||||||
|
@ -24,7 +24,7 @@ class WebGoalsQueryRunner(WebAnalyticsQueryRunner):
|
|||||||
response: WebGoalsQueryResponse
|
response: WebGoalsQueryResponse
|
||||||
cached_response: CachedWebGoalsQueryResponse
|
cached_response: CachedWebGoalsQueryResponse
|
||||||
|
|
||||||
def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def to_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
with self.timings.measure("date_expr"):
|
with self.timings.measure("date_expr"):
|
||||||
start = self.query_date_range.date_from_as_hogql()
|
start = self.query_date_range.date_from_as_hogql()
|
||||||
end = self.query_date_range.date_to_as_hogql()
|
end = self.query_date_range.date_to_as_hogql()
|
||||||
|
@ -28,7 +28,7 @@ class WebOverviewQueryRunner(WebAnalyticsQueryRunner):
|
|||||||
response: WebOverviewQueryResponse
|
response: WebOverviewQueryResponse
|
||||||
cached_response: CachedWebOverviewQueryResponse
|
cached_response: CachedWebOverviewQueryResponse
|
||||||
|
|
||||||
def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def to_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
return self.outer_select
|
return self.outer_select
|
||||||
|
|
||||||
def calculate(self):
|
def calculate(self):
|
||||||
|
@ -7,6 +7,8 @@ from dateutil import parser
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
|
|
||||||
|
from posthog.hogql.resolver_utils import extract_select_queries
|
||||||
from posthog.queries.util import PersonPropertiesMode
|
from posthog.queries.util import PersonPropertiesMode
|
||||||
from posthog.clickhouse.client.connection import Workload
|
from posthog.clickhouse.client.connection import Workload
|
||||||
from posthog.clickhouse.query_tagging import tag_queries
|
from posthog.clickhouse.query_tagging import tag_queries
|
||||||
@ -83,8 +85,7 @@ def print_cohort_hogql_query(cohort: Cohort, hogql_context: HogQLContext) -> str
|
|||||||
cast(dict, cohort.query), team=cast(Team, cohort.team), limit_context=LimitContext.COHORT_CALCULATION
|
cast(dict, cohort.query), team=cast(Team, cohort.team), limit_context=LimitContext.COHORT_CALCULATION
|
||||||
).to_query()
|
).to_query()
|
||||||
|
|
||||||
select_queries: list[ast.SelectQuery] = [query] if isinstance(query, ast.SelectQuery) else query.select_queries
|
for select_query in extract_select_queries(query):
|
||||||
for select_query in select_queries:
|
|
||||||
columns: dict[str, ast.Expr] = {}
|
columns: dict[str, ast.Expr] = {}
|
||||||
for expr in select_query.select:
|
for expr in select_query.select:
|
||||||
if isinstance(expr, ast.Alias):
|
if isinstance(expr, ast.Alias):
|
||||||
|
@ -328,7 +328,7 @@ class PersonsPropertiesSubQuery:
|
|||||||
self._filter = filter
|
self._filter = filter
|
||||||
self._ttl_days = ttl_days
|
self._ttl_days = ttl_days
|
||||||
|
|
||||||
def get_query(self) -> ast.SelectQuery | ast.SelectUnionQuery | None:
|
def get_query(self) -> ast.SelectQuery | ast.SelectSetQuery | None:
|
||||||
if self.person_properties and not poe_is_active(self._team):
|
if self.person_properties and not poe_is_active(self._team):
|
||||||
return parse_select(
|
return parse_select(
|
||||||
"""
|
"""
|
||||||
@ -383,7 +383,7 @@ HAVING argMax(is_deleted, version) = 0 AND {cohort_predicate}
|
|||||||
self._filter = filter
|
self._filter = filter
|
||||||
self._ttl_days = ttl_days
|
self._ttl_days = ttl_days
|
||||||
|
|
||||||
def get_query(self) -> ast.SelectQuery | ast.SelectUnionQuery | None:
|
def get_query(self) -> ast.SelectQuery | ast.SelectSetQuery | None:
|
||||||
if self.cohort_properties:
|
if self.cohort_properties:
|
||||||
return parse_select(
|
return parse_select(
|
||||||
self.raw_cohort_to_distinct_id,
|
self.raw_cohort_to_distinct_id,
|
||||||
@ -433,7 +433,7 @@ class PersonsIdCompareOperation:
|
|||||||
right=q,
|
right=q,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_query(self) -> ast.SelectQuery | ast.SelectUnionQuery | None:
|
def get_query(self) -> ast.SelectQuery | ast.SelectSetQuery | None:
|
||||||
if not self._filter.person_uuid:
|
if not self._filter.person_uuid:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -529,14 +529,14 @@ class ReplayFiltersEventsSubQuery:
|
|||||||
group_by=[ast.Field(chain=["$session_id"])],
|
group_by=[ast.Field(chain=["$session_id"])],
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_query_for_session_id_matching(self) -> ast.SelectQuery | ast.SelectUnionQuery | None:
|
def get_query_for_session_id_matching(self) -> ast.SelectQuery | ast.SelectSetQuery | None:
|
||||||
use_poe = poe_is_active(self._team) and self.person_properties
|
use_poe = poe_is_active(self._team) and self.person_properties
|
||||||
if self._filter.entities or self.event_properties or self.group_properties or use_poe:
|
if self._filter.entities or self.event_properties or self.group_properties or use_poe:
|
||||||
return self._select_from_events(ast.Alias(alias="session_id", expr=ast.Field(chain=["$session_id"])))
|
return self._select_from_events(ast.Alias(alias="session_id", expr=ast.Field(chain=["$session_id"])))
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_query_for_event_id_matching(self) -> ast.SelectQuery | ast.SelectUnionQuery:
|
def get_query_for_event_id_matching(self) -> ast.SelectQuery | ast.SelectSetQuery:
|
||||||
return self._select_from_events(ast.Call(name="groupUniqArray", args=[ast.Field(chain=["uuid"])]))
|
return self._select_from_events(ast.Call(name="groupUniqArray", args=[ast.Field(chain=["uuid"])]))
|
||||||
|
|
||||||
def get_event_ids_for_session(self) -> SessionRecordingQueryResult:
|
def get_event_ids_for_session(self) -> SessionRecordingQueryResult:
|
||||||
|
@ -10,6 +10,7 @@ from django.db import connection, models, transaction
|
|||||||
from posthog.hogql import ast
|
from posthog.hogql import ast
|
||||||
from posthog.hogql.database.database import Database, create_hogql_database
|
from posthog.hogql.database.database import Database, create_hogql_database
|
||||||
from posthog.hogql.parser import parse_select
|
from posthog.hogql.parser import parse_select
|
||||||
|
from posthog.hogql.resolver_utils import extract_select_queries
|
||||||
from posthog.models.team import Team
|
from posthog.models.team import Team
|
||||||
from posthog.models.user import User
|
from posthog.models.user import User
|
||||||
from posthog.models.utils import (
|
from posthog.models.utils import (
|
||||||
@ -99,8 +100,8 @@ def get_parents_from_model_query(model_query: str) -> set[str]:
|
|||||||
|
|
||||||
hogql_query = parse_select(model_query)
|
hogql_query = parse_select(model_query)
|
||||||
|
|
||||||
if isinstance(hogql_query, ast.SelectUnionQuery):
|
if isinstance(hogql_query, ast.SelectSetQuery):
|
||||||
queries = hogql_query.select_queries
|
queries = list(extract_select_queries(hogql_query))
|
||||||
else:
|
else:
|
||||||
queries = [hogql_query]
|
queries = [hogql_query]
|
||||||
|
|
||||||
@ -114,8 +115,8 @@ def get_parents_from_model_query(model_query: str) -> set[str]:
|
|||||||
for name, cte in query.ctes.items():
|
for name, cte in query.ctes.items():
|
||||||
ctes.add(name)
|
ctes.add(name)
|
||||||
|
|
||||||
if isinstance(cte.expr, ast.SelectUnionQuery):
|
if isinstance(cte.expr, ast.SelectSetQuery):
|
||||||
queries.extend(cte.expr.select_queries)
|
queries.extend(list(extract_select_queries(cte.expr)))
|
||||||
elif isinstance(cte.expr, ast.SelectQuery):
|
elif isinstance(cte.expr, ast.SelectQuery):
|
||||||
queries.append(cte.expr)
|
queries.append(cte.expr)
|
||||||
|
|
||||||
@ -130,8 +131,8 @@ def get_parents_from_model_query(model_query: str) -> set[str]:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
queries.append(join.table)
|
queries.append(join.table)
|
||||||
elif isinstance(join.table, ast.SelectUnionQuery):
|
elif isinstance(join.table, ast.SelectSetQuery):
|
||||||
queries.extend(join.table.select_queries)
|
queries.extend(list(extract_select_queries(join.table)))
|
||||||
|
|
||||||
while join is not None:
|
while join is not None:
|
||||||
parent_name = join.table.chain[0] # type: ignore
|
parent_name = join.table.chain[0] # type: ignore
|
||||||
|
@ -105,7 +105,7 @@ phonenumberslite==8.13.6
|
|||||||
openai==1.51.2
|
openai==1.51.2
|
||||||
tiktoken==0.8.0
|
tiktoken==0.8.0
|
||||||
nh3==0.2.14
|
nh3==0.2.14
|
||||||
hogql-parser==1.0.45
|
hogql-parser==1.0.46
|
||||||
zxcvbn==4.4.28
|
zxcvbn==4.4.28
|
||||||
zstd==1.5.5.1
|
zstd==1.5.5.1
|
||||||
xmlsec==1.3.13 # Do not change this version - it will break SAML
|
xmlsec==1.3.13 # Do not change this version - it will break SAML
|
||||||
|
@ -282,7 +282,7 @@ h11==0.13.0
|
|||||||
# wsproto
|
# wsproto
|
||||||
hexbytes==1.0.0
|
hexbytes==1.0.0
|
||||||
# via dlt
|
# via dlt
|
||||||
hogql-parser==1.0.45
|
hogql-parser==1.0.46
|
||||||
# via -r requirements.in
|
# via -r requirements.in
|
||||||
httpcore==1.0.2
|
httpcore==1.0.2
|
||||||
# via httpx
|
# via httpx
|
||||||
|
Loading…
Reference in New Issue
Block a user