0
0
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:
Sandy Spicer 2024-10-29 11:31:56 -07:00 committed by GitHub
parent e6877d3436
commit 447a18930f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
69 changed files with 7428 additions and 6974 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
} }

View File

@ -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;

View File

@ -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.

View File

@ -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()); }

View File

@ -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",

View File

@ -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]

View File

@ -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),

View File

@ -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)

View File

@ -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)

View File

@ -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 = (

View File

@ -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

View File

@ -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\
""") """
)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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())

View File

@ -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):

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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",
)
],
), ),
) )

View File

@ -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"),

View File

@ -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):

View File

@ -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")):

View File

@ -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)]),

View File

@ -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

View File

@ -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

View File

@ -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(

View File

@ -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")

View File

@ -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

View File

@ -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):

View File

@ -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:

View File

@ -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

View File

@ -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:

View File

@ -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"]

View File

@ -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(

View File

@ -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()

View File

@ -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,

View File

@ -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

View File

@ -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"]),

View File

@ -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,

View File

@ -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,

View File

@ -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)

View File

@ -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()

View File

@ -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(
""" """

View File

@ -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()

View File

@ -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):

View File

@ -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):

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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