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,
COHORT = 15, COLLATE = 16, CROSS = 17, CUBE = 18, CURRENT = 19, DATE = 20,
DAY = 21, DESC = 22, DESCENDING = 23, DISTINCT = 24, ELSE = 25, END = 26,
EXTRACT = 27, FINAL = 28, FINALLY = 29, FIRST = 30, FN = 31, FOLLOWING = 32,
FOR = 33, FROM = 34, FULL = 35, FUN = 36, GROUP = 37, HAVING = 38, HOUR = 39,
ID = 40, IF = 41, ILIKE = 42, IN = 43, INF = 44, INNER = 45, INTERVAL = 46,
IS = 47, JOIN = 48, KEY = 49, LAST = 50, LEADING = 51, LEFT = 52, LET = 53,
LIKE = 54, LIMIT = 55, MINUTE = 56, MONTH = 57, NAN_SQL = 58, NOT = 59,
NULL_SQL = 60, NULLS = 61, OFFSET = 62, ON = 63, OR = 64, ORDER = 65,
OUTER = 66, OVER = 67, PARTITION = 68, PRECEDING = 69, PREWHERE = 70,
QUARTER = 71, RANGE = 72, RETURN = 73, RIGHT = 74, ROLLUP = 75, ROW = 76,
ROWS = 77, SAMPLE = 78, SECOND = 79, SELECT = 80, SEMI = 81, SETTINGS = 82,
SUBSTRING = 83, THEN = 84, THROW = 85, TIES = 86, TIMESTAMP = 87, TO = 88,
TOP = 89, TOTALS = 90, TRAILING = 91, TRIM = 92, TRUNCATE = 93, TRY = 94,
UNBOUNDED = 95, UNION = 96, USING = 97, WEEK = 98, WHEN = 99, WHERE = 100,
WHILE = 101, WINDOW = 102, WITH = 103, YEAR = 104, ESCAPE_CHAR_COMMON = 105,
IDENTIFIER = 106, FLOATING_LITERAL = 107, OCTAL_LITERAL = 108, DECIMAL_LITERAL = 109,
HEXADECIMAL_LITERAL = 110, STRING_LITERAL = 111, ARROW = 112, ASTERISK = 113,
BACKQUOTE = 114, BACKSLASH = 115, COLON = 116, COMMA = 117, CONCAT = 118,
DASH = 119, DOLLAR = 120, DOT = 121, EQ_DOUBLE = 122, EQ_SINGLE = 123,
GT_EQ = 124, GT = 125, HASH = 126, IREGEX_SINGLE = 127, IREGEX_DOUBLE = 128,
LBRACE = 129, LBRACKET = 130, LPAREN = 131, LT_EQ = 132, LT = 133, NOT_EQ = 134,
NOT_IREGEX = 135, NOT_REGEX = 136, NULL_PROPERTY = 137, NULLISH = 138,
PERCENT = 139, PLUS = 140, QUERY = 141, QUOTE_DOUBLE = 142, QUOTE_SINGLE_TEMPLATE = 143,
QUOTE_SINGLE_TEMPLATE_FULL = 144, QUOTE_SINGLE = 145, REGEX_SINGLE = 146,
REGEX_DOUBLE = 147, RBRACE = 148, RBRACKET = 149, RPAREN = 150, SEMICOLON = 151,
SLASH = 152, UNDERSCORE = 153, MULTI_LINE_COMMENT = 154, SINGLE_LINE_COMMENT = 155,
WHITESPACE = 156, STRING_TEXT = 157, STRING_ESCAPE_TRIGGER = 158, FULL_STRING_TEXT = 159,
FULL_STRING_ESCAPE_TRIGGER = 160
EXCEPT = 27, EXTRACT = 28, FINAL = 29, FINALLY = 30, FIRST = 31, FN = 32,
FOLLOWING = 33, FOR = 34, FROM = 35, FULL = 36, FUN = 37, GROUP = 38,
HAVING = 39, HOUR = 40, ID = 41, IF = 42, ILIKE = 43, IN = 44, INF = 45,
INNER = 46, INTERSECT = 47, INTERVAL = 48, IS = 49, JOIN = 50, KEY = 51,
LAST = 52, LEADING = 53, LEFT = 54, LET = 55, LIKE = 56, LIMIT = 57,
MINUTE = 58, MONTH = 59, NAN_SQL = 60, NOT = 61, NULL_SQL = 62, NULLS = 63,
OFFSET = 64, ON = 65, OR = 66, ORDER = 67, OUTER = 68, OVER = 69, PARTITION = 70,
PRECEDING = 71, PREWHERE = 72, QUARTER = 73, RANGE = 74, RETURN = 75,
RIGHT = 76, ROLLUP = 77, ROW = 78, ROWS = 79, SAMPLE = 80, SECOND = 81,
SELECT = 82, SEMI = 83, SETTINGS = 84, SUBSTRING = 85, THEN = 86, THROW = 87,
TIES = 88, TIMESTAMP = 89, TO = 90, TOP = 91, TOTALS = 92, TRAILING = 93,
TRIM = 94, TRUNCATE = 95, TRY = 96, UNBOUNDED = 97, UNION = 98, USING = 99,
WEEK = 100, WHEN = 101, WHERE = 102, WHILE = 103, WINDOW = 104, WITH = 105,
YEAR = 106, ESCAPE_CHAR_COMMON = 107, IDENTIFIER = 108, FLOATING_LITERAL = 109,
OCTAL_LITERAL = 110, DECIMAL_LITERAL = 111, HEXADECIMAL_LITERAL = 112,
STRING_LITERAL = 113, ARROW = 114, ASTERISK = 115, BACKQUOTE = 116,
BACKSLASH = 117, COLON = 118, COMMA = 119, CONCAT = 120, DASH = 121,
DOLLAR = 122, DOT = 123, EQ_DOUBLE = 124, EQ_SINGLE = 125, GT_EQ = 126,
GT = 127, HASH = 128, IREGEX_SINGLE = 129, IREGEX_DOUBLE = 130, LBRACE = 131,
LBRACKET = 132, LPAREN = 133, LT_EQ = 134, LT = 135, NOT_EQ = 136, NOT_IREGEX = 137,
NOT_REGEX = 138, NULL_PROPERTY = 139, NULLISH = 140, PERCENT = 141,
PLUS = 142, QUERY = 143, QUOTE_DOUBLE = 144, QUOTE_SINGLE_TEMPLATE = 145,
QUOTE_SINGLE_TEMPLATE_FULL = 146, QUOTE_SINGLE = 147, REGEX_SINGLE = 148,
REGEX_DOUBLE = 149, RBRACE = 150, RBRACKET = 151, RPAREN = 152, SEMICOLON = 153,
SLASH = 154, UNDERSCORE = 155, MULTI_LINE_COMMENT = 156, SINGLE_LINE_COMMENT = 157,
WHITESPACE = 158, STRING_TEXT = 159, STRING_ESCAPE_TRIGGER = 160, FULL_STRING_TEXT = 161,
FULL_STRING_ESCAPE_TRIGGER = 162
};
enum {

File diff suppressed because one or more lines are too long

View File

@ -24,178 +24,180 @@ DESCENDING=23
DISTINCT=24
ELSE=25
END=26
EXTRACT=27
FINAL=28
FINALLY=29
FIRST=30
FN=31
FOLLOWING=32
FOR=33
FROM=34
FULL=35
FUN=36
GROUP=37
HAVING=38
HOUR=39
ID=40
IF=41
ILIKE=42
IN=43
INF=44
INNER=45
INTERVAL=46
IS=47
JOIN=48
KEY=49
LAST=50
LEADING=51
LEFT=52
LET=53
LIKE=54
LIMIT=55
MINUTE=56
MONTH=57
NAN_SQL=58
NOT=59
NULL_SQL=60
NULLS=61
OFFSET=62
ON=63
OR=64
ORDER=65
OUTER=66
OVER=67
PARTITION=68
PRECEDING=69
PREWHERE=70
QUARTER=71
RANGE=72
RETURN=73
RIGHT=74
ROLLUP=75
ROW=76
ROWS=77
SAMPLE=78
SECOND=79
SELECT=80
SEMI=81
SETTINGS=82
SUBSTRING=83
THEN=84
THROW=85
TIES=86
TIMESTAMP=87
TO=88
TOP=89
TOTALS=90
TRAILING=91
TRIM=92
TRUNCATE=93
TRY=94
UNBOUNDED=95
UNION=96
USING=97
WEEK=98
WHEN=99
WHERE=100
WHILE=101
WINDOW=102
WITH=103
YEAR=104
ESCAPE_CHAR_COMMON=105
IDENTIFIER=106
FLOATING_LITERAL=107
OCTAL_LITERAL=108
DECIMAL_LITERAL=109
HEXADECIMAL_LITERAL=110
STRING_LITERAL=111
ARROW=112
ASTERISK=113
BACKQUOTE=114
BACKSLASH=115
COLON=116
COMMA=117
CONCAT=118
DASH=119
DOLLAR=120
DOT=121
EQ_DOUBLE=122
EQ_SINGLE=123
GT_EQ=124
GT=125
HASH=126
IREGEX_SINGLE=127
IREGEX_DOUBLE=128
LBRACE=129
LBRACKET=130
LPAREN=131
LT_EQ=132
LT=133
NOT_EQ=134
NOT_IREGEX=135
NOT_REGEX=136
NULL_PROPERTY=137
NULLISH=138
PERCENT=139
PLUS=140
QUERY=141
QUOTE_DOUBLE=142
QUOTE_SINGLE_TEMPLATE=143
QUOTE_SINGLE_TEMPLATE_FULL=144
QUOTE_SINGLE=145
REGEX_SINGLE=146
REGEX_DOUBLE=147
RBRACE=148
RBRACKET=149
RPAREN=150
SEMICOLON=151
SLASH=152
UNDERSCORE=153
MULTI_LINE_COMMENT=154
SINGLE_LINE_COMMENT=155
WHITESPACE=156
STRING_TEXT=157
STRING_ESCAPE_TRIGGER=158
FULL_STRING_TEXT=159
FULL_STRING_ESCAPE_TRIGGER=160
'->'=112
'*'=113
'`'=114
'\\'=115
':'=116
','=117
'||'=118
'-'=119
'$'=120
'.'=121
'=='=122
'='=123
'>='=124
'>'=125
'#'=126
'~*'=127
'=~*'=128
'{'=129
'['=130
'('=131
'<='=132
'<'=133
'!~*'=135
'!~'=136
'?.'=137
'??'=138
'%'=139
'+'=140
'?'=141
'"'=142
'f\''=143
'F\''=144
'\''=145
'~'=146
'=~'=147
'}'=148
']'=149
')'=150
';'=151
'/'=152
'_'=153
EXCEPT=27
EXTRACT=28
FINAL=29
FINALLY=30
FIRST=31
FN=32
FOLLOWING=33
FOR=34
FROM=35
FULL=36
FUN=37
GROUP=38
HAVING=39
HOUR=40
ID=41
IF=42
ILIKE=43
IN=44
INF=45
INNER=46
INTERSECT=47
INTERVAL=48
IS=49
JOIN=50
KEY=51
LAST=52
LEADING=53
LEFT=54
LET=55
LIKE=56
LIMIT=57
MINUTE=58
MONTH=59
NAN_SQL=60
NOT=61
NULL_SQL=62
NULLS=63
OFFSET=64
ON=65
OR=66
ORDER=67
OUTER=68
OVER=69
PARTITION=70
PRECEDING=71
PREWHERE=72
QUARTER=73
RANGE=74
RETURN=75
RIGHT=76
ROLLUP=77
ROW=78
ROWS=79
SAMPLE=80
SECOND=81
SELECT=82
SEMI=83
SETTINGS=84
SUBSTRING=85
THEN=86
THROW=87
TIES=88
TIMESTAMP=89
TO=90
TOP=91
TOTALS=92
TRAILING=93
TRIM=94
TRUNCATE=95
TRY=96
UNBOUNDED=97
UNION=98
USING=99
WEEK=100
WHEN=101
WHERE=102
WHILE=103
WINDOW=104
WITH=105
YEAR=106
ESCAPE_CHAR_COMMON=107
IDENTIFIER=108
FLOATING_LITERAL=109
OCTAL_LITERAL=110
DECIMAL_LITERAL=111
HEXADECIMAL_LITERAL=112
STRING_LITERAL=113
ARROW=114
ASTERISK=115
BACKQUOTE=116
BACKSLASH=117
COLON=118
COMMA=119
CONCAT=120
DASH=121
DOLLAR=122
DOT=123
EQ_DOUBLE=124
EQ_SINGLE=125
GT_EQ=126
GT=127
HASH=128
IREGEX_SINGLE=129
IREGEX_DOUBLE=130
LBRACE=131
LBRACKET=132
LPAREN=133
LT_EQ=134
LT=135
NOT_EQ=136
NOT_IREGEX=137
NOT_REGEX=138
NULL_PROPERTY=139
NULLISH=140
PERCENT=141
PLUS=142
QUERY=143
QUOTE_DOUBLE=144
QUOTE_SINGLE_TEMPLATE=145
QUOTE_SINGLE_TEMPLATE_FULL=146
QUOTE_SINGLE=147
REGEX_SINGLE=148
REGEX_DOUBLE=149
RBRACE=150
RBRACKET=151
RPAREN=152
SEMICOLON=153
SLASH=154
UNDERSCORE=155
MULTI_LINE_COMMENT=156
SINGLE_LINE_COMMENT=157
WHITESPACE=158
STRING_TEXT=159
STRING_ESCAPE_TRIGGER=160
FULL_STRING_TEXT=161
FULL_STRING_ESCAPE_TRIGGER=162
'->'=114
'*'=115
'`'=116
'\\'=117
':'=118
','=119
'||'=120
'-'=121
'$'=122
'.'=123
'=='=124
'='=125
'>='=126
'>'=127
'#'=128
'~*'=129
'=~*'=130
'{'=131
'['=132
'('=133
'<='=134
'<'=135
'!~*'=137
'!~'=138
'?.'=139
'??'=140
'%'=141
'+'=142
'?'=143
'"'=144
'f\''=145
'F\''=146
'\''=147
'~'=148
'=~'=149
'}'=150
']'=151
')'=152
';'=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,
COHORT = 15, COLLATE = 16, CROSS = 17, CUBE = 18, CURRENT = 19, DATE = 20,
DAY = 21, DESC = 22, DESCENDING = 23, DISTINCT = 24, ELSE = 25, END = 26,
EXTRACT = 27, FINAL = 28, FINALLY = 29, FIRST = 30, FN = 31, FOLLOWING = 32,
FOR = 33, FROM = 34, FULL = 35, FUN = 36, GROUP = 37, HAVING = 38, HOUR = 39,
ID = 40, IF = 41, ILIKE = 42, IN = 43, INF = 44, INNER = 45, INTERVAL = 46,
IS = 47, JOIN = 48, KEY = 49, LAST = 50, LEADING = 51, LEFT = 52, LET = 53,
LIKE = 54, LIMIT = 55, MINUTE = 56, MONTH = 57, NAN_SQL = 58, NOT = 59,
NULL_SQL = 60, NULLS = 61, OFFSET = 62, ON = 63, OR = 64, ORDER = 65,
OUTER = 66, OVER = 67, PARTITION = 68, PRECEDING = 69, PREWHERE = 70,
QUARTER = 71, RANGE = 72, RETURN = 73, RIGHT = 74, ROLLUP = 75, ROW = 76,
ROWS = 77, SAMPLE = 78, SECOND = 79, SELECT = 80, SEMI = 81, SETTINGS = 82,
SUBSTRING = 83, THEN = 84, THROW = 85, TIES = 86, TIMESTAMP = 87, TO = 88,
TOP = 89, TOTALS = 90, TRAILING = 91, TRIM = 92, TRUNCATE = 93, TRY = 94,
UNBOUNDED = 95, UNION = 96, USING = 97, WEEK = 98, WHEN = 99, WHERE = 100,
WHILE = 101, WINDOW = 102, WITH = 103, YEAR = 104, ESCAPE_CHAR_COMMON = 105,
IDENTIFIER = 106, FLOATING_LITERAL = 107, OCTAL_LITERAL = 108, DECIMAL_LITERAL = 109,
HEXADECIMAL_LITERAL = 110, STRING_LITERAL = 111, ARROW = 112, ASTERISK = 113,
BACKQUOTE = 114, BACKSLASH = 115, COLON = 116, COMMA = 117, CONCAT = 118,
DASH = 119, DOLLAR = 120, DOT = 121, EQ_DOUBLE = 122, EQ_SINGLE = 123,
GT_EQ = 124, GT = 125, HASH = 126, IREGEX_SINGLE = 127, IREGEX_DOUBLE = 128,
LBRACE = 129, LBRACKET = 130, LPAREN = 131, LT_EQ = 132, LT = 133, NOT_EQ = 134,
NOT_IREGEX = 135, NOT_REGEX = 136, NULL_PROPERTY = 137, NULLISH = 138,
PERCENT = 139, PLUS = 140, QUERY = 141, QUOTE_DOUBLE = 142, QUOTE_SINGLE_TEMPLATE = 143,
QUOTE_SINGLE_TEMPLATE_FULL = 144, QUOTE_SINGLE = 145, REGEX_SINGLE = 146,
REGEX_DOUBLE = 147, RBRACE = 148, RBRACKET = 149, RPAREN = 150, SEMICOLON = 151,
SLASH = 152, UNDERSCORE = 153, MULTI_LINE_COMMENT = 154, SINGLE_LINE_COMMENT = 155,
WHITESPACE = 156, STRING_TEXT = 157, STRING_ESCAPE_TRIGGER = 158, FULL_STRING_TEXT = 159,
FULL_STRING_ESCAPE_TRIGGER = 160
EXCEPT = 27, EXTRACT = 28, FINAL = 29, FINALLY = 30, FIRST = 31, FN = 32,
FOLLOWING = 33, FOR = 34, FROM = 35, FULL = 36, FUN = 37, GROUP = 38,
HAVING = 39, HOUR = 40, ID = 41, IF = 42, ILIKE = 43, IN = 44, INF = 45,
INNER = 46, INTERSECT = 47, INTERVAL = 48, IS = 49, JOIN = 50, KEY = 51,
LAST = 52, LEADING = 53, LEFT = 54, LET = 55, LIKE = 56, LIMIT = 57,
MINUTE = 58, MONTH = 59, NAN_SQL = 60, NOT = 61, NULL_SQL = 62, NULLS = 63,
OFFSET = 64, ON = 65, OR = 66, ORDER = 67, OUTER = 68, OVER = 69, PARTITION = 70,
PRECEDING = 71, PREWHERE = 72, QUARTER = 73, RANGE = 74, RETURN = 75,
RIGHT = 76, ROLLUP = 77, ROW = 78, ROWS = 79, SAMPLE = 80, SECOND = 81,
SELECT = 82, SEMI = 83, SETTINGS = 84, SUBSTRING = 85, THEN = 86, THROW = 87,
TIES = 88, TIMESTAMP = 89, TO = 90, TOP = 91, TOTALS = 92, TRAILING = 93,
TRIM = 94, TRUNCATE = 95, TRY = 96, UNBOUNDED = 97, UNION = 98, USING = 99,
WEEK = 100, WHEN = 101, WHERE = 102, WHILE = 103, WINDOW = 104, WITH = 105,
YEAR = 106, ESCAPE_CHAR_COMMON = 107, IDENTIFIER = 108, FLOATING_LITERAL = 109,
OCTAL_LITERAL = 110, DECIMAL_LITERAL = 111, HEXADECIMAL_LITERAL = 112,
STRING_LITERAL = 113, ARROW = 114, ASTERISK = 115, BACKQUOTE = 116,
BACKSLASH = 117, COLON = 118, COMMA = 119, CONCAT = 120, DASH = 121,
DOLLAR = 122, DOT = 123, EQ_DOUBLE = 124, EQ_SINGLE = 125, GT_EQ = 126,
GT = 127, HASH = 128, IREGEX_SINGLE = 129, IREGEX_DOUBLE = 130, LBRACE = 131,
LBRACKET = 132, LPAREN = 133, LT_EQ = 134, LT = 135, NOT_EQ = 136, NOT_IREGEX = 137,
NOT_REGEX = 138, NULL_PROPERTY = 139, NULLISH = 140, PERCENT = 141,
PLUS = 142, QUERY = 143, QUOTE_DOUBLE = 144, QUOTE_SINGLE_TEMPLATE = 145,
QUOTE_SINGLE_TEMPLATE_FULL = 146, QUOTE_SINGLE = 147, REGEX_SINGLE = 148,
REGEX_DOUBLE = 149, RBRACE = 150, RBRACKET = 151, RPAREN = 152, SEMICOLON = 153,
SLASH = 154, UNDERSCORE = 155, MULTI_LINE_COMMENT = 156, SINGLE_LINE_COMMENT = 157,
WHITESPACE = 158, STRING_TEXT = 159, STRING_ESCAPE_TRIGGER = 160, FULL_STRING_TEXT = 161,
FULL_STRING_ESCAPE_TRIGGER = 162
};
enum {
@ -50,26 +51,27 @@ public:
RuleCatchBlock = 8, RuleTryCatchStmt = 9, RuleIfStmt = 10, RuleWhileStmt = 11,
RuleForStmt = 12, RuleForInStmt = 13, RuleFuncStmt = 14, RuleVarAssignment = 15,
RuleExprStmt = 16, RuleEmptyStmt = 17, RuleBlock = 18, RuleKvPair = 19,
RuleKvPairList = 20, RuleSelect = 21, RuleSelectUnionStmt = 22, RuleSelectStmtWithParens = 23,
RuleSelectStmt = 24, RuleWithClause = 25, RuleTopClause = 26, RuleFromClause = 27,
RuleArrayJoinClause = 28, RuleWindowClause = 29, RulePrewhereClause = 30,
RuleWhereClause = 31, RuleGroupByClause = 32, RuleHavingClause = 33,
RuleOrderByClause = 34, RuleProjectionOrderByClause = 35, RuleLimitAndOffsetClause = 36,
RuleOffsetOnlyClause = 37, RuleSettingsClause = 38, RuleJoinExpr = 39,
RuleJoinOp = 40, RuleJoinOpCross = 41, RuleJoinConstraintClause = 42,
RuleSampleClause = 43, RuleOrderExprList = 44, RuleOrderExpr = 45, RuleRatioExpr = 46,
RuleSettingExprList = 47, RuleSettingExpr = 48, RuleWindowExpr = 49,
RuleWinPartitionByClause = 50, RuleWinOrderByClause = 51, RuleWinFrameClause = 52,
RuleWinFrameExtend = 53, RuleWinFrameBound = 54, RuleExpr = 55, RuleColumnTypeExpr = 56,
RuleColumnExprList = 57, RuleColumnExpr = 58, RuleColumnLambdaExpr = 59,
RuleHogqlxTagElement = 60, RuleHogqlxTagAttribute = 61, RuleWithExprList = 62,
RuleWithExpr = 63, RuleColumnIdentifier = 64, RuleNestedIdentifier = 65,
RuleTableExpr = 66, RuleTableFunctionExpr = 67, RuleTableIdentifier = 68,
RuleTableArgList = 69, RuleDatabaseIdentifier = 70, RuleFloatingLiteral = 71,
RuleNumberLiteral = 72, RuleLiteral = 73, RuleInterval = 74, RuleKeyword = 75,
RuleKeywordForAlias = 76, RuleAlias = 77, RuleIdentifier = 78, RuleEnumValue = 79,
RulePlaceholder = 80, RuleString = 81, RuleTemplateString = 82, RuleStringContents = 83,
RuleFullTemplateString = 84, RuleStringContentsFull = 85
RuleKvPairList = 20, RuleSelect = 21, RuleSelectStmtWithParens = 22,
RuleSubsequentSelectSetClause = 23, RuleSelectSetStmt = 24, RuleSelectStmt = 25,
RuleWithClause = 26, RuleTopClause = 27, RuleFromClause = 28, RuleArrayJoinClause = 29,
RuleWindowClause = 30, RulePrewhereClause = 31, RuleWhereClause = 32,
RuleGroupByClause = 33, RuleHavingClause = 34, RuleOrderByClause = 35,
RuleProjectionOrderByClause = 36, RuleLimitAndOffsetClause = 37, RuleOffsetOnlyClause = 38,
RuleSettingsClause = 39, RuleJoinExpr = 40, RuleJoinOp = 41, RuleJoinOpCross = 42,
RuleJoinConstraintClause = 43, RuleSampleClause = 44, RuleOrderExprList = 45,
RuleOrderExpr = 46, RuleRatioExpr = 47, RuleSettingExprList = 48, RuleSettingExpr = 49,
RuleWindowExpr = 50, RuleWinPartitionByClause = 51, RuleWinOrderByClause = 52,
RuleWinFrameClause = 53, RuleWinFrameExtend = 54, RuleWinFrameBound = 55,
RuleExpr = 56, RuleColumnTypeExpr = 57, RuleColumnExprList = 58, RuleColumnExpr = 59,
RuleColumnLambdaExpr = 60, RuleHogqlxTagElement = 61, RuleHogqlxTagAttribute = 62,
RuleWithExprList = 63, RuleWithExpr = 64, RuleColumnIdentifier = 65,
RuleNestedIdentifier = 66, RuleTableExpr = 67, RuleTableFunctionExpr = 68,
RuleTableIdentifier = 69, RuleTableArgList = 70, RuleDatabaseIdentifier = 71,
RuleFloatingLiteral = 72, RuleNumberLiteral = 73, RuleLiteral = 74,
RuleInterval = 75, RuleKeyword = 76, RuleKeywordForAlias = 77, RuleAlias = 78,
RuleIdentifier = 79, RuleEnumValue = 80, RulePlaceholder = 81, RuleString = 82,
RuleTemplateString = 83, RuleStringContents = 84, RuleFullTemplateString = 85,
RuleStringContentsFull = 86
};
explicit HogQLParser(antlr4::TokenStream *input);
@ -111,8 +113,9 @@ public:
class KvPairContext;
class KvPairListContext;
class SelectContext;
class SelectUnionStmtContext;
class SelectStmtWithParensContext;
class SubsequentSelectSetClauseContext;
class SelectSetStmtContext;
class SelectStmtContext;
class WithClauseContext;
class TopClauseContext;
@ -552,7 +555,7 @@ public:
SelectContext(antlr4::ParserRuleContext *parent, size_t invokingState);
virtual size_t getRuleIndex() const override;
antlr4::tree::TerminalNode *EOF();
SelectUnionStmtContext *selectUnionStmt();
SelectSetStmtContext *selectSetStmt();
SelectStmtContext *selectStmt();
HogqlxTagElementContext *hogqlxTagElement();
@ -563,31 +566,13 @@ public:
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 {
public:
SelectStmtWithParensContext(antlr4::ParserRuleContext *parent, size_t invokingState);
virtual size_t getRuleIndex() const override;
SelectStmtContext *selectStmt();
antlr4::tree::TerminalNode *LPAREN();
SelectUnionStmtContext *selectUnionStmt();
SelectSetStmtContext *selectSetStmt();
antlr4::tree::TerminalNode *RPAREN();
PlaceholderContext *placeholder();
@ -598,6 +583,38 @@ public:
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 {
public:
HogQLParser::WithClauseContext *with = nullptr;
@ -1398,7 +1415,7 @@ public:
ColumnExprSubqueryContext(ColumnExprContext *ctx);
antlr4::tree::TerminalNode *LPAREN();
SelectUnionStmtContext *selectUnionStmt();
SelectSetStmtContext *selectSetStmt();
antlr4::tree::TerminalNode *RPAREN();
virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
@ -1999,7 +2016,7 @@ public:
IdentifierContext *identifier();
antlr4::tree::TerminalNode *AS();
antlr4::tree::TerminalNode *LPAREN();
SelectUnionStmtContext *selectUnionStmt();
SelectSetStmtContext *selectSetStmt();
antlr4::tree::TerminalNode *RPAREN();
virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
@ -2084,7 +2101,7 @@ public:
TableExprSubqueryContext(TableExprContext *ctx);
antlr4::tree::TerminalNode *LPAREN();
SelectUnionStmtContext *selectUnionStmt();
SelectSetStmtContext *selectSetStmt();
antlr4::tree::TerminalNode *RPAREN();
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
ELSE=25
END=26
EXTRACT=27
FINAL=28
FINALLY=29
FIRST=30
FN=31
FOLLOWING=32
FOR=33
FROM=34
FULL=35
FUN=36
GROUP=37
HAVING=38
HOUR=39
ID=40
IF=41
ILIKE=42
IN=43
INF=44
INNER=45
INTERVAL=46
IS=47
JOIN=48
KEY=49
LAST=50
LEADING=51
LEFT=52
LET=53
LIKE=54
LIMIT=55
MINUTE=56
MONTH=57
NAN_SQL=58
NOT=59
NULL_SQL=60
NULLS=61
OFFSET=62
ON=63
OR=64
ORDER=65
OUTER=66
OVER=67
PARTITION=68
PRECEDING=69
PREWHERE=70
QUARTER=71
RANGE=72
RETURN=73
RIGHT=74
ROLLUP=75
ROW=76
ROWS=77
SAMPLE=78
SECOND=79
SELECT=80
SEMI=81
SETTINGS=82
SUBSTRING=83
THEN=84
THROW=85
TIES=86
TIMESTAMP=87
TO=88
TOP=89
TOTALS=90
TRAILING=91
TRIM=92
TRUNCATE=93
TRY=94
UNBOUNDED=95
UNION=96
USING=97
WEEK=98
WHEN=99
WHERE=100
WHILE=101
WINDOW=102
WITH=103
YEAR=104
ESCAPE_CHAR_COMMON=105
IDENTIFIER=106
FLOATING_LITERAL=107
OCTAL_LITERAL=108
DECIMAL_LITERAL=109
HEXADECIMAL_LITERAL=110
STRING_LITERAL=111
ARROW=112
ASTERISK=113
BACKQUOTE=114
BACKSLASH=115
COLON=116
COMMA=117
CONCAT=118
DASH=119
DOLLAR=120
DOT=121
EQ_DOUBLE=122
EQ_SINGLE=123
GT_EQ=124
GT=125
HASH=126
IREGEX_SINGLE=127
IREGEX_DOUBLE=128
LBRACE=129
LBRACKET=130
LPAREN=131
LT_EQ=132
LT=133
NOT_EQ=134
NOT_IREGEX=135
NOT_REGEX=136
NULL_PROPERTY=137
NULLISH=138
PERCENT=139
PLUS=140
QUERY=141
QUOTE_DOUBLE=142
QUOTE_SINGLE_TEMPLATE=143
QUOTE_SINGLE_TEMPLATE_FULL=144
QUOTE_SINGLE=145
REGEX_SINGLE=146
REGEX_DOUBLE=147
RBRACE=148
RBRACKET=149
RPAREN=150
SEMICOLON=151
SLASH=152
UNDERSCORE=153
MULTI_LINE_COMMENT=154
SINGLE_LINE_COMMENT=155
WHITESPACE=156
STRING_TEXT=157
STRING_ESCAPE_TRIGGER=158
FULL_STRING_TEXT=159
FULL_STRING_ESCAPE_TRIGGER=160
'->'=112
'*'=113
'`'=114
'\\'=115
':'=116
','=117
'||'=118
'-'=119
'$'=120
'.'=121
'=='=122
'='=123
'>='=124
'>'=125
'#'=126
'~*'=127
'=~*'=128
'{'=129
'['=130
'('=131
'<='=132
'<'=133
'!~*'=135
'!~'=136
'?.'=137
'??'=138
'%'=139
'+'=140
'?'=141
'"'=142
'f\''=143
'F\''=144
'\''=145
'~'=146
'=~'=147
'}'=148
']'=149
')'=150
';'=151
'/'=152
'_'=153
EXCEPT=27
EXTRACT=28
FINAL=29
FINALLY=30
FIRST=31
FN=32
FOLLOWING=33
FOR=34
FROM=35
FULL=36
FUN=37
GROUP=38
HAVING=39
HOUR=40
ID=41
IF=42
ILIKE=43
IN=44
INF=45
INNER=46
INTERSECT=47
INTERVAL=48
IS=49
JOIN=50
KEY=51
LAST=52
LEADING=53
LEFT=54
LET=55
LIKE=56
LIMIT=57
MINUTE=58
MONTH=59
NAN_SQL=60
NOT=61
NULL_SQL=62
NULLS=63
OFFSET=64
ON=65
OR=66
ORDER=67
OUTER=68
OVER=69
PARTITION=70
PRECEDING=71
PREWHERE=72
QUARTER=73
RANGE=74
RETURN=75
RIGHT=76
ROLLUP=77
ROW=78
ROWS=79
SAMPLE=80
SECOND=81
SELECT=82
SEMI=83
SETTINGS=84
SUBSTRING=85
THEN=86
THROW=87
TIES=88
TIMESTAMP=89
TO=90
TOP=91
TOTALS=92
TRAILING=93
TRIM=94
TRUNCATE=95
TRY=96
UNBOUNDED=97
UNION=98
USING=99
WEEK=100
WHEN=101
WHERE=102
WHILE=103
WINDOW=104
WITH=105
YEAR=106
ESCAPE_CHAR_COMMON=107
IDENTIFIER=108
FLOATING_LITERAL=109
OCTAL_LITERAL=110
DECIMAL_LITERAL=111
HEXADECIMAL_LITERAL=112
STRING_LITERAL=113
ARROW=114
ASTERISK=115
BACKQUOTE=116
BACKSLASH=117
COLON=118
COMMA=119
CONCAT=120
DASH=121
DOLLAR=122
DOT=123
EQ_DOUBLE=124
EQ_SINGLE=125
GT_EQ=126
GT=127
HASH=128
IREGEX_SINGLE=129
IREGEX_DOUBLE=130
LBRACE=131
LBRACKET=132
LPAREN=133
LT_EQ=134
LT=135
NOT_EQ=136
NOT_IREGEX=137
NOT_REGEX=138
NULL_PROPERTY=139
NULLISH=140
PERCENT=141
PLUS=142
QUERY=143
QUOTE_DOUBLE=144
QUOTE_SINGLE_TEMPLATE=145
QUOTE_SINGLE_TEMPLATE_FULL=146
QUOTE_SINGLE=147
REGEX_SINGLE=148
REGEX_DOUBLE=149
RBRACE=150
RBRACKET=151
RPAREN=152
SEMICOLON=153
SLASH=154
UNDERSCORE=155
MULTI_LINE_COMMENT=156
SINGLE_LINE_COMMENT=157
WHITESPACE=158
STRING_TEXT=159
STRING_ESCAPE_TRIGGER=160
FULL_STRING_TEXT=161
FULL_STRING_ESCAPE_TRIGGER=162
'->'=114
'*'=115
'`'=116
'\\'=117
':'=118
','=119
'||'=120
'-'=121
'$'=122
'.'=123
'=='=124
'='=125
'>='=126
'>'=127
'#'=128
'~*'=129
'=~*'=130
'{'=131
'['=132
'('=133
'<='=134
'<'=135
'!~*'=137
'!~'=138
'?.'=139
'??'=140
'%'=141
'+'=142
'?'=143
'"'=144
'f\''=145
'F\''=146
'\''=147
'~'=148
'=~'=149
'}'=150
']'=151
')'=152
';'=153
'/'=154
'_'=155

View File

@ -103,11 +103,15 @@ public:
return visitChildren(ctx);
}
virtual std::any visitSelectUnionStmt(HogQLParser::SelectUnionStmtContext *ctx) override {
virtual std::any visitSelectStmtWithParens(HogQLParser::SelectStmtWithParensContext *ctx) override {
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);
}

View File

@ -63,10 +63,12 @@ public:
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 visitSubsequentSelectSetClause(HogQLParser::SubsequentSelectSetClauseContext *context) = 0;
virtual std::any visitSelectSetStmt(HogQLParser::SelectSetStmtContext *context) = 0;
virtual std::any visitSelectStmt(HogQLParser::SelectStmtContext *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
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.
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
VISIT(Select) {
auto select_union_stmt_ctx = ctx->selectUnionStmt();
if (select_union_stmt_ctx) {
return visit(select_union_stmt_ctx);
auto select_set_stmt_ctx = ctx->selectSetStmt();
if (select_set_stmt_ctx) {
return visit(select_set_stmt_ctx);
}
auto select_stmt_ctx = ctx->selectStmt();
@ -851,68 +851,48 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
return visitAsPyObject(placeholder_ctx);
}
return visit(ctx->selectUnionStmt());
return visit(ctx->selectSetStmt());
}
VISIT(SelectUnionStmt) {
// Using a vector of PyObjects atypically here, because this is a precursor of flattened_queries
vector<PyObject*> select_queries;
auto select_stmt_with_parens_ctxs = ctx->selectStmtWithParens();
select_queries.reserve(select_stmt_with_parens_ctxs.size());
for (auto select_stmt_with_parens_ctx : select_stmt_with_parens_ctxs) {
try {
select_queries.push_back(visitAsPyObject(select_stmt_with_parens_ctx));
} catch (...) {
X_Py_DECREF_ALL(select_queries);
VISIT(SelectSetStmt) {
PyObject* initial_query = visitAsPyObject(ctx->selectStmtWithParens());
PyObject* select_query = NULL;
PyObject* select_queries = PyList_New(0);
if (!select_queries) {
throw PyInternalError();
}
try {
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;
}
}
PyObject* flattened_queries = PyList_New(0);
if (!flattened_queries) {
X_Py_DECREF_ALL(select_queries);
throw PyInternalError();
if (PyList_Size(select_queries) == 0) {
Py_DECREF(select_queries);
return initial_query;
}
for (auto query : select_queries) {
int is_select_query = is_ast_node_instance(query, "SelectQuery");
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);
RETURN_NEW_AST_NODE("SelectSetQuery", "{s:N, s:N}", "initial_select_query", initial_query, "subsequent_select_queries", select_queries);
}
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);
}
VISIT(ColumnExprSubquery) { return visit(ctx->selectUnionStmt()); }
VISIT(ColumnExprSubquery) { return visit(ctx->selectSetStmt()); }
VISIT(ColumnExprArray) {
RETURN_NEW_AST_NODE("Array", "{s:N}", "exprs", visitAsPyObjectOrEmptyList(ctx->columnExprList()));
@ -2286,7 +2266,7 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
VISIT(WithExprSubquery) {
string name = visitAsString(ctx->identifier());
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"
);
}
@ -2332,7 +2312,7 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
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()); }

View File

@ -32,7 +32,7 @@ module = Extension(
setup(
name="hogql_parser",
version="1.0.45",
version="1.0.46",
url="https://github.com/PostHog/posthog/tree/master/hogql_parser",
author="PostHog Inc.",
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 "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 "FieldAliasType", 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 | 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 | 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/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/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]
@ -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: 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/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: 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: 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: 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: 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: Item "None" of "SelectQuery | SelectUnionQuery | 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: Item "None" of "SelectQuery | SelectUnionQuery | 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: 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 "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 | 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 | SelectSetQueryType" [arg-type]
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 | SelectSetQueryType | SelectQueryType | SelectQueryAliasType | SelectViewType | None") [assignment]
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: 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: 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: Item "SelectUnionQuery" of "SelectQuery | SelectUnionQuery" 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 "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 "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 "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 "SelectUnionQuery" of "SelectQuery | SelectUnionQuery" 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 "select" [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/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]
@ -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: 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: 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: 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
@ -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: 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: 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]
@ -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: 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/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 "Expr", variable has type "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 | 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: "SelectQuery" has no attribute "select_queries" [attr-defined]
posthog/hogql/query.py:0: error: Subclass of "SelectQuery" and "SelectUnionQuery" cannot exist: would have incompatible method signatures [unreachable]
posthog/hogql/query.py:0: error: Subclass of "SelectQuery" and "SelectSetQuery" 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/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/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: 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/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]
@ -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: 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: error: Item "SelectUnionQuery" of "SelectQuery | SelectUnionQuery" 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: 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 | 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: 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: 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: 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/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]
@ -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 | 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 "SelectUnionQueryType" of "SelectQueryType | SelectUnionQueryType | 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 "SelectSetQueryType" of "SelectQueryType | SelectSetQueryType | 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: 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_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: Item "SelectUnionQuery" of "SelectQuery | SelectUnionQuery" 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: 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_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_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]

View File

@ -371,35 +371,36 @@ class TestPluginAPI(APIBaseTest, QueryMatchingTest):
self.assertEqual(response.status_code, 403)
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):
repo_url = "https://github.com/PostHog/helloworldplugin"
response = self.client.post("/api/organizations/@current/plugins/", {"url": repo_url})
self.assertEqual(response.status_code, 201)
with freeze_time("2021-08-25T22:09:14.252Z"):
repo_url = "https://github.com/PostHog/helloworldplugin"
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 (
Organization.PluginsAccessLevel.NONE,
Organization.PluginsAccessLevel.CONFIG,
):
self.organization.plugins_access_level = level
for level in (
Organization.PluginsAccessLevel.NONE,
Organization.PluginsAccessLevel.CONFIG,
):
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()
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.assert_plugin_activity(
[
{
"user": {"first_name": "", "email": "user1@posthog.com"},
"activity": "installed",
"created_at": "2021-08-25T22:09:14.252000Z",
"activity": "uninstalled",
"created_at": "2021-08-25T22:09:14.253000Z",
"scope": "Plugin",
"item_id": str(plugin_id),
"detail": {
@ -412,7 +413,7 @@ class TestPluginAPI(APIBaseTest, QueryMatchingTest):
},
{
"user": {"first_name": "", "email": "user1@posthog.com"},
"activity": "uninstalled",
"activity": "installed",
"created_at": "2021-08-25T22:09:14.252000Z",
"scope": "Plugin",
"item_id": str(plugin_id),

View File

@ -183,7 +183,7 @@ class BatchExportDestinationSerializer(serializers.ModelSerializer):
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."""
try:
parsed_query = parse_select(data)
@ -329,7 +329,7 @@ class BatchExportSerializer(serializers.ModelSerializer):
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.
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.
"""
if isinstance(hogql_query, ast.SelectUnionQuery):
if isinstance(hogql_query, ast.SelectSetQuery):
raise serializers.ValidationError("UNIONs are not supported")
parsed = cast(ast.SelectQuery, hogql_query)

View File

@ -1,5 +1,6 @@
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 posthog.hogql.base import Type, Expr, CTE, ConstantType, UnknownType, AST
@ -173,7 +174,7 @@ class BaseTableType(Type):
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)
ctes: dict[str, CTE] = field(default_factory=dict)
# 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
parent: Optional[Union["SelectQueryType", "SelectUnionQueryType"]] = None
parent: Optional[Union["SelectQueryType", "SelectSetQueryType"]] = None
def get_alias_for_table_type(self, table_type: TableOrSelectType) -> Optional[str]:
for key, value in self.tables.items():
@ -276,8 +277,8 @@ class SelectQueryType(Type):
@dataclass(kw_only=True)
class SelectUnionQueryType(Type):
types: list[SelectQueryType]
class SelectSetQueryType(Type):
types: list[Union[SelectQueryType, "SelectSetQueryType"]]
def get_alias_for_table_type(self, table_type: TableOrSelectType) -> Optional[str]:
return self.types[0].get_alias_for_table_type(table_type)
@ -296,7 +297,7 @@ class SelectUnionQueryType(Type):
class SelectViewType(Type):
view_name: str
alias: str
select_query_type: SelectQueryType | SelectUnionQueryType
select_query_type: SelectQueryType | SelectSetQueryType
def get_child(self, name: str, context: HogQLContext) -> Type:
if name == "*":
@ -343,7 +344,7 @@ class SelectViewType(Type):
@dataclass(kw_only=True)
class SelectQueryAliasType(Type):
alias: str
select_query_type: SelectQueryType | SelectUnionQueryType
select_query_type: SelectQueryType | SelectSetQueryType
def get_child(self, name: str, context: HogQLContext) -> Type:
if name == "*":
@ -763,7 +764,7 @@ class JoinExpr(Expr):
type: Optional[TableOrSelectType] = 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
alias: Optional[str] = None
table_final: Optional[bool] = None
@ -820,10 +821,35 @@ class SelectQuery(Expr):
view_name: Optional[str] = None
SetOperator = Literal["UNION ALL", "INTERSECT", "EXCEPT"]
@dataclass(kw_only=True)
class SelectUnionQuery(Expr):
type: Optional[SelectUnionQueryType] = None
select_queries: list[SelectQuery]
class SelectSetNode:
select_query: Union[SelectQuery, "SelectSetQuery"]
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)

View File

@ -28,6 +28,7 @@ from posthog.hogql.resolver import resolve_types
from posthog.hogql.timings import HogQLTimings
from posthog.hogql.visitor import TraversingVisitor, clone_expr
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.property_definition import PropertyDefinition
from posthog.models.team.team import Team
@ -475,8 +476,8 @@ def get_hogql_autocomplete(
if isinstance(select_ast, ast.SelectQuery):
ctes = select_ast.ctes
elif isinstance(select_ast, ast.SelectUnionQuery):
ctes = select_ast.select_queries[0].ctes
elif isinstance(select_ast, ast.SelectSetQuery):
ctes = next(extract_select_queries(select_ast)).ctes
nearest_select = find_node.nearest_select_query or select_ast
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(
s: str,
placeholders: Optional[dict[str, ast.Expr]] = None,
) -> ast.SelectQuery | ast.SelectUnionQuery:
) -> ast.SelectQuery | ast.SelectSetQuery:
parsed = parse_select(s, placeholders=placeholders)
return parsed

View File

@ -26,7 +26,7 @@ def f(s: Union[str, ast.Expr, None], placeholders: Optional[dict[str, ast.Expr]]
def parse(
s: str,
placeholders: Optional[dict[str, ast.Expr]] = None,
) -> ast.SelectQuery | ast.SelectUnionQuery:
) -> ast.SelectQuery | ast.SelectSetQuery:
parsed = parse_select(s, placeholders=placeholders)
return parsed
@ -363,7 +363,8 @@ class TestSessionsV2QueriesHogQLToClickhouse(ClickhouseTestMixin, APIBaseTest):
def test_select_with_timestamp(self):
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
sessions.session_id AS session_id
FROM
@ -381,7 +382,8 @@ FROM
WHERE
ifNull(greater(sessions.`$start_timestamp`, %(hogql_val_2)s), 0)
LIMIT 50000\
""")
"""
)
def test_join_with_events(self):
actual = self.print_query(
@ -396,7 +398,8 @@ WHERE events.timestamp > '2021-01-01'
GROUP BY sessions.session_id
"""
)
assert self.generalize_sql(actual) == snapshot("""\
assert self.generalize_sql(actual) == snapshot(
"""\
SELECT
sessions.session_id AS session_id,
uniq(events.uuid)
@ -417,7 +420,8 @@ WHERE
GROUP BY
sessions.session_id
LIMIT 50000\
""")
"""
)
def test_union(self):
actual = self.print_query(
@ -429,7 +433,8 @@ FROM events
WHERE events.timestamp < today()
"""
)
assert self.generalize_sql(actual) == snapshot("""\
assert self.generalize_sql(actual) == snapshot(
"""\
SELECT
0 AS duration
LIMIT 50000
@ -451,7 +456,8 @@ FROM
WHERE
and(equals(events.team_id, <TEAM_ID>), less(toTimeZone(events.timestamp, %(hogql_val_4)s), today()))
LIMIT 50000\
""")
"""
)
def test_session_breakdown(self):
actual = self.print_query(
@ -495,7 +501,8 @@ WHERE and(greaterOrEquals(timestamp, toStartOfDay(assumeNotNull(toDateTime('2024
GROUP BY day_start,
breakdown_value"""
)
assert self.generalize_sql(actual) == snapshot("""\
assert self.generalize_sql(actual) == snapshot(
"""\
SELECT
count(DISTINCT e.`$session_id`) AS total,
toStartOfDay(toTimeZone(e.timestamp, %(hogql_val_8)s)) AS day_start,
@ -535,7 +542,8 @@ GROUP BY
day_start,
breakdown_value
LIMIT 50000\
""")
"""
)
def test_session_replay_query(self):
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
"""
)
assert self.generalize_sql(actual) == snapshot("""\
assert self.generalize_sql(actual) == snapshot(
"""\
SELECT
s.session_id AS session_id,
min(toTimeZone(s.min_first_timestamp, %(hogql_val_5)s)) AS start_time
@ -569,7 +578,8 @@ WHERE
GROUP BY
s.session_id
LIMIT 50000\
""")
"""
)
def test_urls_in_sessions_in_timestamp_query(self):
actual = self.print_query(
@ -582,7 +592,8 @@ from sessions
where `$start_timestamp` >= now() - toIntervalDay(7)
"""
)
assert self.generalize_sql(actual) == snapshot("""\
assert self.generalize_sql(actual) == snapshot(
"""\
SELECT
sessions.session_id AS session_id,
sessions.`$urls` AS `$urls`,
@ -603,4 +614,5 @@ FROM
WHERE
ifNull(greaterOrEquals(sessions.`$start_timestamp`, minus(now64(6, %(hogql_val_2)s), toIntervalDay(7))), 0)
LIMIT 50000\
""")
"""
)

View File

@ -26,7 +26,7 @@ def f(s: Union[str, ast.Expr, None], placeholders: Optional[dict[str, ast.Expr]]
def parse(
s: str,
placeholders: Optional[dict[str, ast.Expr]] = None,
) -> ast.SelectQuery | ast.SelectUnionQuery:
) -> ast.SelectQuery | ast.SelectSetQuery:
parsed = parse_select(s, placeholders=placeholders)
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;
ELSE: E L S E;
END: E N D;
EXCEPT: E X C E P T;
EXTRACT: E X T R A C T;
FINAL: F I N A L;
FINALLY: F I N A L L Y;
@ -49,6 +50,7 @@ ILIKE: I L I K E;
IN: I N;
INF: I N F | I N F I N I T Y;
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;
IS: I S;
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
ELSE=25
END=26
EXTRACT=27
FINAL=28
FINALLY=29
FIRST=30
FN=31
FOLLOWING=32
FOR=33
FROM=34
FULL=35
FUN=36
GROUP=37
HAVING=38
HOUR=39
ID=40
IF=41
ILIKE=42
IN=43
INF=44
INNER=45
INTERVAL=46
IS=47
JOIN=48
KEY=49
LAST=50
LEADING=51
LEFT=52
LET=53
LIKE=54
LIMIT=55
MINUTE=56
MONTH=57
NAN_SQL=58
NOT=59
NULL_SQL=60
NULLS=61
OFFSET=62
ON=63
OR=64
ORDER=65
OUTER=66
OVER=67
PARTITION=68
PRECEDING=69
PREWHERE=70
QUARTER=71
RANGE=72
RETURN=73
RIGHT=74
ROLLUP=75
ROW=76
ROWS=77
SAMPLE=78
SECOND=79
SELECT=80
SEMI=81
SETTINGS=82
SUBSTRING=83
THEN=84
THROW=85
TIES=86
TIMESTAMP=87
TO=88
TOP=89
TOTALS=90
TRAILING=91
TRIM=92
TRUNCATE=93
TRY=94
UNBOUNDED=95
UNION=96
USING=97
WEEK=98
WHEN=99
WHERE=100
WHILE=101
WINDOW=102
WITH=103
YEAR=104
ESCAPE_CHAR_COMMON=105
IDENTIFIER=106
FLOATING_LITERAL=107
OCTAL_LITERAL=108
DECIMAL_LITERAL=109
HEXADECIMAL_LITERAL=110
STRING_LITERAL=111
ARROW=112
ASTERISK=113
BACKQUOTE=114
BACKSLASH=115
COLON=116
COMMA=117
CONCAT=118
DASH=119
DOLLAR=120
DOT=121
EQ_DOUBLE=122
EQ_SINGLE=123
GT_EQ=124
GT=125
HASH=126
IREGEX_SINGLE=127
IREGEX_DOUBLE=128
LBRACE=129
LBRACKET=130
LPAREN=131
LT_EQ=132
LT=133
NOT_EQ=134
NOT_IREGEX=135
NOT_REGEX=136
NULL_PROPERTY=137
NULLISH=138
PERCENT=139
PLUS=140
QUERY=141
QUOTE_DOUBLE=142
QUOTE_SINGLE_TEMPLATE=143
QUOTE_SINGLE_TEMPLATE_FULL=144
QUOTE_SINGLE=145
REGEX_SINGLE=146
REGEX_DOUBLE=147
RBRACE=148
RBRACKET=149
RPAREN=150
SEMICOLON=151
SLASH=152
UNDERSCORE=153
MULTI_LINE_COMMENT=154
SINGLE_LINE_COMMENT=155
WHITESPACE=156
STRING_TEXT=157
STRING_ESCAPE_TRIGGER=158
FULL_STRING_TEXT=159
FULL_STRING_ESCAPE_TRIGGER=160
'->'=112
'*'=113
'`'=114
'\\'=115
':'=116
','=117
'||'=118
'-'=119
'$'=120
'.'=121
'=='=122
'='=123
'>='=124
'>'=125
'#'=126
'~*'=127
'=~*'=128
'{'=129
'['=130
'('=131
'<='=132
'<'=133
'!~*'=135
'!~'=136
'?.'=137
'??'=138
'%'=139
'+'=140
'?'=141
'"'=142
'f\''=143
'F\''=144
'\''=145
'~'=146
'=~'=147
'}'=148
']'=149
')'=150
';'=151
'/'=152
'_'=153
EXCEPT=27
EXTRACT=28
FINAL=29
FINALLY=30
FIRST=31
FN=32
FOLLOWING=33
FOR=34
FROM=35
FULL=36
FUN=37
GROUP=38
HAVING=39
HOUR=40
ID=41
IF=42
ILIKE=43
IN=44
INF=45
INNER=46
INTERSECT=47
INTERVAL=48
IS=49
JOIN=50
KEY=51
LAST=52
LEADING=53
LEFT=54
LET=55
LIKE=56
LIMIT=57
MINUTE=58
MONTH=59
NAN_SQL=60
NOT=61
NULL_SQL=62
NULLS=63
OFFSET=64
ON=65
OR=66
ORDER=67
OUTER=68
OVER=69
PARTITION=70
PRECEDING=71
PREWHERE=72
QUARTER=73
RANGE=74
RETURN=75
RIGHT=76
ROLLUP=77
ROW=78
ROWS=79
SAMPLE=80
SECOND=81
SELECT=82
SEMI=83
SETTINGS=84
SUBSTRING=85
THEN=86
THROW=87
TIES=88
TIMESTAMP=89
TO=90
TOP=91
TOTALS=92
TRAILING=93
TRIM=94
TRUNCATE=95
TRY=96
UNBOUNDED=97
UNION=98
USING=99
WEEK=100
WHEN=101
WHERE=102
WHILE=103
WINDOW=104
WITH=105
YEAR=106
ESCAPE_CHAR_COMMON=107
IDENTIFIER=108
FLOATING_LITERAL=109
OCTAL_LITERAL=110
DECIMAL_LITERAL=111
HEXADECIMAL_LITERAL=112
STRING_LITERAL=113
ARROW=114
ASTERISK=115
BACKQUOTE=116
BACKSLASH=117
COLON=118
COMMA=119
CONCAT=120
DASH=121
DOLLAR=122
DOT=123
EQ_DOUBLE=124
EQ_SINGLE=125
GT_EQ=126
GT=127
HASH=128
IREGEX_SINGLE=129
IREGEX_DOUBLE=130
LBRACE=131
LBRACKET=132
LPAREN=133
LT_EQ=134
LT=135
NOT_EQ=136
NOT_IREGEX=137
NOT_REGEX=138
NULL_PROPERTY=139
NULLISH=140
PERCENT=141
PLUS=142
QUERY=143
QUOTE_DOUBLE=144
QUOTE_SINGLE_TEMPLATE=145
QUOTE_SINGLE_TEMPLATE_FULL=146
QUOTE_SINGLE=147
REGEX_SINGLE=148
REGEX_DOUBLE=149
RBRACE=150
RBRACKET=151
RPAREN=152
SEMICOLON=153
SLASH=154
UNDERSCORE=155
MULTI_LINE_COMMENT=156
SINGLE_LINE_COMMENT=157
WHITESPACE=158
STRING_TEXT=159
STRING_ESCAPE_TRIGGER=160
FULL_STRING_TEXT=161
FULL_STRING_ESCAPE_TRIGGER=162
'->'=114
'*'=115
'`'=116
'\\'=117
':'=118
','=119
'||'=120
'-'=121
'$'=122
'.'=123
'=='=124
'='=125
'>='=126
'>'=127
'#'=128
'~*'=129
'=~*'=130
'{'=131
'['=132
'('=133
'<='=134
'<'=135
'!~*'=137
'!~'=138
'?.'=139
'??'=140
'%'=141
'+'=142
'?'=143
'"'=144
'f\''=145
'F\''=146
'\''=147
'~'=148
'=~'=149
'}'=150
']'=151
')'=152
';'=153
'/'=154
'_'=155

View File

@ -51,10 +51,12 @@ kvPairList: kvPair (COMMA kvPair)* COMMA?;
// SELECT statement
select: (selectUnionStmt | selectStmt | hogqlxTagElement) EOF;
select: (selectSetStmt | selectStmt | hogqlxTagElement) EOF;
selectUnionStmt: selectStmtWithParens (UNION ALL selectStmtWithParens)*;
selectStmtWithParens: selectStmt | LPAREN selectUnionStmt RPAREN | placeholder;
selectStmtWithParens: selectStmt | LPAREN selectSetStmt RPAREN | placeholder;
subsequentSelectSetClause: (EXCEPT | UNION ALL | INTERSECT) selectStmtWithParens;
selectSetStmt: selectStmtWithParens (subsequentSelectSetClause)*;
selectStmt:
with=withClause?
@ -201,7 +203,7 @@ columnExpr
| <assoc=right> columnExpr QUERY columnExpr COLON columnExpr # ColumnExprTernaryOp
| columnExpr (AS identifier | AS STRING_LITERAL) # ColumnExprAlias
| (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 columnExprList RPAREN # ColumnExprTuple
| LBRACKET columnExprList? RBRACKET # ColumnExprArray
@ -231,7 +233,7 @@ hogqlxTagAttribute
withExprList: withExpr (COMMA withExpr)* COMMA?;
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.
| columnExpr AS identifier # WithExprColumn
;
@ -246,7 +248,7 @@ nestedIdentifier: identifier (DOT identifier)*;
tableExpr
: tableIdentifier # TableExprIdentifier
| tableFunctionExpr # TableExprFunction
| LPAREN selectUnionStmt RPAREN # TableExprSubquery
| LPAREN selectSetStmt RPAREN # TableExprSubquery
| tableExpr (alias | AS identifier) # TableExprAlias
| hogqlxTagElement # TableExprTag
| 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
ELSE=25
END=26
EXTRACT=27
FINAL=28
FINALLY=29
FIRST=30
FN=31
FOLLOWING=32
FOR=33
FROM=34
FULL=35
FUN=36
GROUP=37
HAVING=38
HOUR=39
ID=40
IF=41
ILIKE=42
IN=43
INF=44
INNER=45
INTERVAL=46
IS=47
JOIN=48
KEY=49
LAST=50
LEADING=51
LEFT=52
LET=53
LIKE=54
LIMIT=55
MINUTE=56
MONTH=57
NAN_SQL=58
NOT=59
NULL_SQL=60
NULLS=61
OFFSET=62
ON=63
OR=64
ORDER=65
OUTER=66
OVER=67
PARTITION=68
PRECEDING=69
PREWHERE=70
QUARTER=71
RANGE=72
RETURN=73
RIGHT=74
ROLLUP=75
ROW=76
ROWS=77
SAMPLE=78
SECOND=79
SELECT=80
SEMI=81
SETTINGS=82
SUBSTRING=83
THEN=84
THROW=85
TIES=86
TIMESTAMP=87
TO=88
TOP=89
TOTALS=90
TRAILING=91
TRIM=92
TRUNCATE=93
TRY=94
UNBOUNDED=95
UNION=96
USING=97
WEEK=98
WHEN=99
WHERE=100
WHILE=101
WINDOW=102
WITH=103
YEAR=104
ESCAPE_CHAR_COMMON=105
IDENTIFIER=106
FLOATING_LITERAL=107
OCTAL_LITERAL=108
DECIMAL_LITERAL=109
HEXADECIMAL_LITERAL=110
STRING_LITERAL=111
ARROW=112
ASTERISK=113
BACKQUOTE=114
BACKSLASH=115
COLON=116
COMMA=117
CONCAT=118
DASH=119
DOLLAR=120
DOT=121
EQ_DOUBLE=122
EQ_SINGLE=123
GT_EQ=124
GT=125
HASH=126
IREGEX_SINGLE=127
IREGEX_DOUBLE=128
LBRACE=129
LBRACKET=130
LPAREN=131
LT_EQ=132
LT=133
NOT_EQ=134
NOT_IREGEX=135
NOT_REGEX=136
NULL_PROPERTY=137
NULLISH=138
PERCENT=139
PLUS=140
QUERY=141
QUOTE_DOUBLE=142
QUOTE_SINGLE_TEMPLATE=143
QUOTE_SINGLE_TEMPLATE_FULL=144
QUOTE_SINGLE=145
REGEX_SINGLE=146
REGEX_DOUBLE=147
RBRACE=148
RBRACKET=149
RPAREN=150
SEMICOLON=151
SLASH=152
UNDERSCORE=153
MULTI_LINE_COMMENT=154
SINGLE_LINE_COMMENT=155
WHITESPACE=156
STRING_TEXT=157
STRING_ESCAPE_TRIGGER=158
FULL_STRING_TEXT=159
FULL_STRING_ESCAPE_TRIGGER=160
'->'=112
'*'=113
'`'=114
'\\'=115
':'=116
','=117
'||'=118
'-'=119
'$'=120
'.'=121
'=='=122
'='=123
'>='=124
'>'=125
'#'=126
'~*'=127
'=~*'=128
'{'=129
'['=130
'('=131
'<='=132
'<'=133
'!~*'=135
'!~'=136
'?.'=137
'??'=138
'%'=139
'+'=140
'?'=141
'"'=142
'f\''=143
'F\''=144
'\''=145
'~'=146
'=~'=147
'}'=148
']'=149
')'=150
';'=151
'/'=152
'_'=153
EXCEPT=27
EXTRACT=28
FINAL=29
FINALLY=30
FIRST=31
FN=32
FOLLOWING=33
FOR=34
FROM=35
FULL=36
FUN=37
GROUP=38
HAVING=39
HOUR=40
ID=41
IF=42
ILIKE=43
IN=44
INF=45
INNER=46
INTERSECT=47
INTERVAL=48
IS=49
JOIN=50
KEY=51
LAST=52
LEADING=53
LEFT=54
LET=55
LIKE=56
LIMIT=57
MINUTE=58
MONTH=59
NAN_SQL=60
NOT=61
NULL_SQL=62
NULLS=63
OFFSET=64
ON=65
OR=66
ORDER=67
OUTER=68
OVER=69
PARTITION=70
PRECEDING=71
PREWHERE=72
QUARTER=73
RANGE=74
RETURN=75
RIGHT=76
ROLLUP=77
ROW=78
ROWS=79
SAMPLE=80
SECOND=81
SELECT=82
SEMI=83
SETTINGS=84
SUBSTRING=85
THEN=86
THROW=87
TIES=88
TIMESTAMP=89
TO=90
TOP=91
TOTALS=92
TRAILING=93
TRIM=94
TRUNCATE=95
TRY=96
UNBOUNDED=97
UNION=98
USING=99
WEEK=100
WHEN=101
WHERE=102
WHILE=103
WINDOW=104
WITH=105
YEAR=106
ESCAPE_CHAR_COMMON=107
IDENTIFIER=108
FLOATING_LITERAL=109
OCTAL_LITERAL=110
DECIMAL_LITERAL=111
HEXADECIMAL_LITERAL=112
STRING_LITERAL=113
ARROW=114
ASTERISK=115
BACKQUOTE=116
BACKSLASH=117
COLON=118
COMMA=119
CONCAT=120
DASH=121
DOLLAR=122
DOT=123
EQ_DOUBLE=124
EQ_SINGLE=125
GT_EQ=126
GT=127
HASH=128
IREGEX_SINGLE=129
IREGEX_DOUBLE=130
LBRACE=131
LBRACKET=132
LPAREN=133
LT_EQ=134
LT=135
NOT_EQ=136
NOT_IREGEX=137
NOT_REGEX=138
NULL_PROPERTY=139
NULLISH=140
PERCENT=141
PLUS=142
QUERY=143
QUOTE_DOUBLE=144
QUOTE_SINGLE_TEMPLATE=145
QUOTE_SINGLE_TEMPLATE_FULL=146
QUOTE_SINGLE=147
REGEX_SINGLE=148
REGEX_DOUBLE=149
RBRACE=150
RBRACKET=151
RPAREN=152
SEMICOLON=153
SLASH=154
UNDERSCORE=155
MULTI_LINE_COMMENT=156
SINGLE_LINE_COMMENT=157
WHITESPACE=158
STRING_TEXT=159
STRING_ESCAPE_TRIGGER=160
FULL_STRING_TEXT=161
FULL_STRING_ESCAPE_TRIGGER=162
'->'=114
'*'=115
'`'=116
'\\'=117
':'=118
','=119
'||'=120
'-'=121
'$'=122
'.'=123
'=='=124
'='=125
'>='=126
'>'=127
'#'=128
'~*'=129
'=~*'=130
'{'=131
'['=132
'('=133
'<='=134
'<'=135
'!~*'=137
'!~'=138
'?.'=139
'??'=140
'%'=141
'+'=142
'?'=143
'"'=144
'f\''=145
'F\''=146
'\''=147
'~'=148
'=~'=149
'}'=150
']'=151
')'=152
';'=153
'/'=154
'_'=155

View File

@ -119,13 +119,18 @@ class HogQLParserVisitor(ParseTreeVisitor):
return self.visitChildren(ctx)
# Visit a parse tree produced by HogQLParser#selectUnionStmt.
def visitSelectUnionStmt(self, ctx:HogQLParser.SelectUnionStmtContext):
# Visit a parse tree produced by HogQLParser#selectStmtWithParens.
def visitSelectStmtWithParens(self, ctx:HogQLParser.SelectStmtWithParensContext):
return self.visitChildren(ctx)
# Visit a parse tree produced by HogQLParser#selectStmtWithParens.
def visitSelectStmtWithParens(self, ctx:HogQLParser.SelectStmtWithParensContext):
# Visit a parse tree produced by HogQLParser#subsequentSelectSetClause.
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)

View File

@ -13,6 +13,7 @@ from posthog.hogql.variables import replace_variables
from posthog.hogql.visitor import clone_expr
from posthog.hogql_queries.query_runner import get_query_runner
from posthog.models import Team
from posthog.hogql.resolver_utils import extract_select_queries
from posthog.schema import HogQLMetadataResponse, HogQLMetadata, HogQLNotice, HogLanguage
from posthog.hogql import ast
@ -99,7 +100,7 @@ def get_hogql_metadata(
def process_expr_on_table(
node: ast.Expr,
context: HogQLContext,
source_query: Optional[ast.SelectQuery | ast.SelectUnionQuery] = None,
source_query: Optional[ast.SelectQuery | ast.SelectSetQuery] = None,
):
try:
if source_query is not None:
@ -114,15 +115,9 @@ def process_expr_on_table(
raise
def is_valid_view(select_query: ast.SelectQuery | ast.SelectUnionQuery) -> bool:
if isinstance(select_query, ast.SelectQuery):
for field in select_query.select:
def is_valid_view(select_query: ast.SelectQuery | ast.SelectSetQuery) -> bool:
for query in extract_select_queries(select_query):
for field in query.select:
if not isinstance(field, ast.Alias):
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

View File

@ -6,6 +6,7 @@ from antlr4.error.ErrorListener import ErrorListener
from prometheus_client import Histogram
from posthog.hogql import ast
from posthog.hogql.ast import SelectSetNode
from posthog.hogql.base import AST
from posthog.hogql.constants import RESERVED_KEYWORDS
from posthog.hogql.errors import BaseHogQLError, NotImplementedError, SyntaxError
@ -132,7 +133,7 @@ def parse_select(
timings: Optional[HogQLTimings] = None,
*,
backend: Literal["python", "cpp"] = "cpp",
) -> ast.SelectQuery | ast.SelectUnionQuery:
) -> ast.SelectQuery | ast.SelectSetQuery:
if timings is None:
timings = HogQLTimings()
with timings.measure(f"parse_select_{backend}"):
@ -327,28 +328,33 @@ class HogQLParseTreeConverter(ParseTreeVisitor):
##### HogQL rules
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):
select_queries: list[ast.SelectQuery | ast.SelectUnionQuery | ast.Placeholder] = [
self.visit(select) for select in ctx.selectStmtWithParens()
]
flattened_queries: list[ast.SelectQuery] = []
for query in select_queries:
if isinstance(query, ast.SelectQuery):
flattened_queries.append(query)
elif isinstance(query, ast.SelectUnionQuery):
flattened_queries.extend(query.select_queries)
elif isinstance(query, ast.Placeholder):
flattened_queries.append(query) # type: ignore
def visitSelectSetStmt(self, ctx: HogQLParser.SelectSetStmtContext):
select_queries: list[SelectSetNode] = []
initial_query = self.visit(ctx.selectStmtWithParens())
for subsequent in ctx.subsequentSelectSetClause():
if subsequent.UNION() and subsequent.ALL():
union_type = "UNION ALL"
elif subsequent.INTERSECT():
union_type = "INTERSECT"
elif subsequent.EXCEPT():
union_type = "EXCEPT"
else:
raise Exception(f"Unexpected query node type {type(query).__name__}")
if len(flattened_queries) == 1:
return flattened_queries[0]
return ast.SelectUnionQuery(select_queries=flattened_queries)
raise SyntaxError("Set operator must be one of UNION ALL, INTERSECT, and EXCEPT")
select_query = self.visit(subsequent.selectStmtWithParens())
select_queries.append(
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):
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):
select_query = ast.SelectQuery(
@ -673,7 +679,7 @@ class HogQLParseTreeConverter(ParseTreeVisitor):
return ast.Dict(items=self.visit(ctx.kvPairList()) if ctx.kvPairList() else [])
def visitColumnExprSubquery(self, ctx: HogQLParser.ColumnExprSubqueryContext):
return self.visit(ctx.selectUnionStmt())
return self.visit(ctx.selectSetStmt())
def visitColumnExprLiteral(self, ctx: HogQLParser.ColumnExprLiteralContext):
return self.visitChildren(ctx)
@ -958,7 +964,7 @@ class HogQLParseTreeConverter(ParseTreeVisitor):
return ctes
def visitWithExprSubquery(self, ctx: HogQLParser.WithExprSubqueryContext):
subquery = self.visit(ctx.selectUnionStmt())
subquery = self.visit(ctx.selectSetStmt())
name = self.visit(ctx.identifier())
return ast.CTE(name=name, expr=subquery, cte_type="subquery")
@ -992,7 +998,7 @@ class HogQLParseTreeConverter(ParseTreeVisitor):
return ast.Field(chain=chain)
def visitTableExprSubquery(self, ctx: HogQLParser.TableExprSubqueryContext):
return self.visit(ctx.selectUnionStmt())
return self.visit(ctx.selectSetStmt())
def visitTableExprPlaceholder(self, ctx: HogQLParser.TableExprPlaceholderContext):
return self.visit(ctx.placeholder())

View File

@ -266,7 +266,7 @@ class _Printer(Visitor):
self.stack.pop()
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")
settings = self._print_settings(self.settings)
if settings is not None:
@ -274,17 +274,25 @@ class _Printer(Visitor):
return response
def visit_select_union_query(self, node: ast.SelectUnionQuery):
def visit_select_set_query(self, node: ast.SelectSetQuery):
self._indent -= 1
queries = [self.visit(expr) for expr in node.select_queries]
ret = self.visit(node.initial_select_query)
if self.pretty:
query = f"\n{self.indent(1)}UNION ALL\n{self.indent(1)}".join([query.strip() for query in queries])
else:
query = " UNION ALL ".join(queries)
ret = ret.strip()
for expr in node.subsequent_select_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
if len(self.stack) > 1:
return f"({query.strip()})"
return query
return f"({ret.strip()})"
return ret
def visit_select_query(self, node: ast.SelectQuery):
if self.dialect == "clickhouse":
@ -293,8 +301,8 @@ class _Printer(Visitor):
if not self.context.team_id:
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
part_of_select_union = len(self.stack) >= 2 and isinstance(self.stack[-2], ast.SelectUnionQuery)
# 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.SelectSetQuery)
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
@ -507,7 +515,7 @@ class _Printer(Visitor):
elif isinstance(node.type, ast.SelectQueryType):
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))
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)
or isinstance(type.table_type, ast.SelectQueryAliasType)
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)
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.variables import replace_variables
from posthog.hogql.visitor import clone_expr
from posthog.hogql.resolver_utils import extract_select_queries
from posthog.models.team import Team
from posthog.clickhouse.query_tagging import tag_queries
from posthog.client import sync_execute
@ -35,7 +36,7 @@ from posthog.settings import HOGQL_INCREASED_MAX_EXECUTION_TIME
def execute_hogql_query(
query: Union[str, ast.SelectQuery, ast.SelectUnionQuery],
query: Union[str, ast.SelectQuery, ast.SelectSetQuery],
team: Team,
*,
query_type: str = "hogql_query",
@ -65,7 +66,7 @@ def execute_hogql_query(
metadata: Optional[HogQLMetadataResponse] = None
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
query = None
else:
@ -105,10 +106,7 @@ def execute_hogql_query(
select_query = replace_placeholders(select_query, placeholders)
with timings.measure("max_limit"):
select_queries = (
select_query.select_queries if isinstance(select_query, ast.SelectUnionQuery) else [select_query]
)
for one_query in select_queries:
for one_query in extract_select_queries(select_query):
if one_query.limit is None:
one_query.limit = ast.Constant(value=get_default_limit_for_context(limit_context))
@ -138,8 +136,8 @@ def execute_hogql_query(
)
print_columns = []
columns_query = (
select_query_hogql.select_queries[0]
if isinstance(select_query_hogql, ast.SelectUnionQuery)
next(extract_select_queries(select_query_hogql))
if isinstance(select_query_hogql, ast.SelectSetQuery)
else select_query_hogql
)
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.hogqlx import HOGQLX_COMPONENTS, convert_to_hx
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.models.utils import UUIDT
@ -123,15 +128,17 @@ class Resolver(CloningVisitor):
raise QueryError("Too many CTE expansions (50+). Probably a CTE loop.")
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
# 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:
self.scopes.append(ast.SelectQueryType(ctes=default_ctes))
node = super().visit_select_union_query(node)
node.type = ast.SelectUnionQueryType(types=[expr.type for expr in node.select_queries])
node = super().visit_select_set_query(node)
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:
self.scopes.pop()
@ -254,7 +261,7 @@ class Resolver(CloningVisitor):
database_fields = table.get_asterisk()
return [ast.Field(chain=[key]) for key in database_fields.keys()]
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.SelectQueryAliasType)
or isinstance(asterisk.table_type, ast.SelectViewType)
@ -262,7 +269,7 @@ class Resolver(CloningVisitor):
select = asterisk.table_type
while isinstance(select, ast.SelectQueryAliasType) or isinstance(select, ast.SelectViewType):
select = select.select_query_type
if isinstance(select, ast.SelectUnionQueryType):
if isinstance(select, ast.SelectSetQueryType):
select = select.types[0]
if isinstance(select, ast.SelectQueryType):
return [ast.Field(chain=[key]) for key in select.columns.keys()]
@ -369,7 +376,7 @@ class Resolver(CloningVisitor):
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))
if node.constraint and node.constraint.constraint_type == "USING":
# visit USING constraint before adding the table to avoid ambiguous names

View File

@ -1,4 +1,5 @@
from typing import Optional
from collections.abc import Generator
from posthog import schema
from posthog.hogql import ast
@ -86,3 +87,12 @@ def expand_hogqlx_query(node: ast.HogQLXTag, team_id: Optional[int]):
return query
except Exception as e:
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,
Dict,
VariableDeclaration,
SelectSetNode,
SelectSetQuery,
)
from posthog.hogql.parser import parse_program
@ -50,9 +52,9 @@ def parser_test_factory(backend: Literal["python", "cpp"]):
def _select(
self, query: str, placeholders: Optional[dict[str, ast.Expr]] = None
) -> ast.SelectQuery | ast.SelectUnionQuery | ast.HogQLXTag:
) -> ast.SelectQuery | ast.SelectSetQuery | ast.HogQLXTag:
return cast(
ast.SelectQuery | ast.SelectUnionQuery | ast.HogQLXTag,
ast.SelectQuery | ast.SelectSetQuery | ast.HogQLXTag,
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):
self.assertEqual(
self._select("select 1 union all select 2 union all select 3"),
ast.SelectUnionQuery(
select_queries=[
ast.SelectQuery(select=[ast.Constant(value=1)]),
ast.SelectQuery(select=[ast.Constant(value=2)]),
ast.SelectQuery(select=[ast.Constant(value=3)]),
]
ast.SelectSetQuery(
initial_select_query=ast.SelectQuery(select=[ast.Constant(value=1)]),
subsequent_select_queries=[
SelectSetNode(set_operator="UNION ALL", select_query=query)
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}"
)
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):
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))"
@ -1076,7 +1206,7 @@ class TestPrinter(BaseTest):
)
self.assertEqual(
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._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 posthog.hogql import ast
from posthog.hogql.ast import SelectSetNode
from posthog.hogql.base import AST, Expr
from posthog.hogql.errors import BaseHogQLError
@ -151,9 +152,10 @@ class TraversingVisitor(Visitor[None]):
for expr in (node.window_exprs or {}).values():
self.visit(expr)
def visit_select_union_query(self, node: ast.SelectUnionQuery):
for expr in node.select_queries:
self.visit(expr)
def visit_select_set_query(self, node: ast.SelectSetQuery):
self.visit(node.initial_select_query)
for expr in node.subsequent_select_queries:
self.visit(expr.select_query)
def visit_lambda_argument_type(self, node: ast.LambdaArgumentType):
pass
@ -174,7 +176,7 @@ class TraversingVisitor(Visitor[None]):
for expr in node.columns.values():
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:
self.visit(type)
@ -600,12 +602,16 @@ class CloningVisitor(Visitor[Any]):
view_name=node.view_name,
)
def visit_select_union_query(self, node: ast.SelectUnionQuery):
return ast.SelectUnionQuery(
def visit_select_set_query(self, node: ast.SelectSetQuery):
return ast.SelectSetQuery(
start=None if self.clear_locations else node.start,
end=None if self.clear_locations else node.end,
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):

View File

@ -6,6 +6,7 @@ from posthog.hogql import ast
from posthog.hogql.constants import HogQLGlobalSettings
from posthog.hogql.parser import parse_expr, parse_order_expr
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.insights.funnels.funnels_query_runner import FunnelsQueryRunner
from posthog.hogql_queries.insights.insight_actors_query_runner import InsightActorsQueryRunner
@ -142,12 +143,12 @@ class ActorsQueryRunner(QueryRunner):
return self.strategy.input_columns()
# 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
if isinstance(source_query, ast.SelectQuery):
select = source_query.select
else:
select = source_query.select_queries[0].select
select = next(extract_select_queries(source_query)).select
for column in select:
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,
)
def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
def to_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
query = ast.SelectQuery(
select=[
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,
)
def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
def to_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
query = parse_select(
"""
SELECT

View File

@ -43,7 +43,7 @@ class TeamTaxonomyQueryRunner(TaxonomyCacheMixin, QueryRunner):
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(
"""
SELECT

View File

@ -532,7 +532,7 @@ class FunnelBase(ABC):
return ast.JoinExpr(
join_type="INNER JOIN",
table=ast.SelectUnionQuery(select_queries=cohort_queries),
table=ast.SelectSetQuery.create_from_queries(cohort_queries, "UNION ALL"),
alias="cohort_join",
constraint=ast.JoinConstraint(
expr=ast.CompareOperation(

View File

@ -332,7 +332,7 @@ class FunnelCorrelationQueryRunner(QueryRunner):
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.
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()
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
if self.query.funnelCorrelationType == FunnelCorrelationResultsType.PROPERTIES:
@ -362,7 +362,7 @@ class FunnelCorrelationQueryRunner(QueryRunner):
else:
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
if not self.correlation_actors_query.funnelCorrelationPersonEntity:
@ -431,7 +431,7 @@ class FunnelCorrelationQueryRunner(QueryRunner):
def properties_actor_query(
self,
) -> ast.SelectQuery | ast.SelectUnionQuery:
) -> ast.SelectQuery | ast.SelectSetQuery:
assert self.correlation_actors_query is not None
if not self.correlation_actors_query.funnelCorrelationPropertyValues:
@ -470,7 +470,7 @@ class FunnelCorrelationQueryRunner(QueryRunner):
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()
event_join_query = self._get_events_join_query()
target_step = self.context.max_steps
@ -548,7 +548,7 @@ class FunnelCorrelationQueryRunner(QueryRunner):
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:
raise ValidationError("Event Property Correlation expects atleast one event name to run correlation on")
@ -646,7 +646,7 @@ class FunnelCorrelationQueryRunner(QueryRunner):
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:
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))
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]:
windowInterval = self.context.funnelWindowInterval

View File

@ -76,7 +76,7 @@ class FunnelsQueryRunner(QueryRunner):
def to_query(self) -> ast.SelectQuery:
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()
def calculate(self):

View File

@ -21,7 +21,7 @@ class InsightActorsQueryOptionsRunner(QueryRunner):
def source_runner(self) -> QueryRunner:
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")
def calculate(self) -> InsightActorsQueryOptionsResponse:

View File

@ -33,7 +33,7 @@ class InsightActorsQueryRunner(QueryRunner):
def source_runner(self) -> QueryRunner:
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):
trends_runner = cast(TrendsQueryRunner, self.source_runner)
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")
def to_actors_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
def to_actors_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
return self.to_query()
@property

View File

@ -33,7 +33,7 @@ class LifecycleQueryRunner(QueryRunner):
response: LifecycleQueryResponse
cached_response: CachedLifecycleQueryResponse
def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
def to_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
if self.query.samplingFactor == 0:
counts_with_sampling = ast.Constant(value=0)
elif self.query.samplingFactor is not None and self.query.samplingFactor != 1:
@ -95,7 +95,7 @@ class LifecycleQueryRunner(QueryRunner):
def to_actors_query(
self, day: Optional[str] = None, status: Optional[str] = None
) -> ast.SelectQuery | ast.SelectUnionQuery:
) -> ast.SelectQuery | ast.SelectSetQuery:
with self.timings.measure("actors_query"):
exprs = []
if day is not None:

View File

@ -750,7 +750,7 @@ class PathsQueryRunner(QueryRunner):
)
return conditions
def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
def to_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
placeholders: dict[str, ast.Expr] = {
"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]:
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
# TODO: Make sure going via self is the best way to do this
self.extra_event_fields = ["uuid", "timestamp"]

View File

@ -316,7 +316,7 @@ class RetentionQueryRunner(QueryRunner):
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"):
if self.query.retentionFilter.cumulative:
actor_query = parse_select(

View File

@ -128,8 +128,8 @@ class StickinessQueryRunner(QueryRunner):
return cast(ast.SelectQuery, select_query)
def to_query(self) -> ast.SelectUnionQuery:
return ast.SelectUnionQuery(select_queries=self.to_queries())
def to_query(self) -> ast.SelectSetQuery:
return ast.SelectSetQuery.create_from_queries(self.to_queries(), "UNION ALL")
def to_queries(self) -> list[ast.SelectQuery]:
queries = []
@ -170,7 +170,7 @@ class StickinessQueryRunner(QueryRunner):
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] = []
for series in self.series:
@ -194,7 +194,7 @@ class StickinessQueryRunner(QueryRunner):
queries.append(events_query)
return ast.SelectUnionQuery(select_queries=queries)
return ast.SelectSetQuery.create_from_queries(queries, "UNION ALL")
def calculate(self):
queries = self.to_queries()

View File

@ -187,8 +187,8 @@ class AggregationOperations(DataWarehouseInsightQueryMixin):
return f"toStartOf{self.query_date_range.interval_name.title()}"
def _actors_parent_select_query(
self, inner_query: ast.SelectQuery | ast.SelectUnionQuery
) -> ast.SelectQuery | ast.SelectUnionQuery:
self, inner_query: ast.SelectQuery | ast.SelectSetQuery
) -> ast.SelectQuery | ast.SelectSetQuery:
if self.is_count_per_actor_variant():
query = parse_select(
"SELECT total FROM {inner_query}",
@ -232,8 +232,8 @@ class AggregationOperations(DataWarehouseInsightQueryMixin):
return query
def _actors_inner_select_query(
self, cross_join_select_query: ast.SelectQuery | ast.SelectUnionQuery
) -> ast.SelectQuery | ast.SelectUnionQuery:
self, cross_join_select_query: ast.SelectQuery | ast.SelectSetQuery
) -> ast.SelectQuery | ast.SelectSetQuery:
if self.is_count_per_actor_variant():
if self.series.math == "avg_count_per_actor":
math_func = self._math_func("avg", ["total"])
@ -308,7 +308,7 @@ class AggregationOperations(DataWarehouseInsightQueryMixin):
def _actors_events_query(
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}"
if self.chart_display_type in NON_TIME_SERIES_DISPLAY_TYPES and self.series.math in (
BaseMathType.WEEKLY_ACTIVE,

View File

@ -31,7 +31,7 @@ class TrendsDisplay:
def should_wrap_inner_query(self) -> bool:
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(
"""
SELECT day_start
@ -49,7 +49,7 @@ class TrendsDisplay:
)
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:
if not self.is_total_value():
return outer_query

View File

@ -167,7 +167,7 @@ class TrendsActorsQueryBuilder:
def is_total_value(self) -> bool:
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(
select=[
ast.Field(chain=["actor_id"]),

View File

@ -60,7 +60,7 @@ class TrendsQueryBuilder(DataWarehouseInsightQueryMixin):
self.modifiers = modifiers
self.limit_context = limit_context
def build_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
def build_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
breakdown = self.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(
self, events_query: ast.SelectQuery, breakdown: Breakdown
) -> ast.SelectQuery | ast.SelectUnionQuery:
) -> ast.SelectQuery | ast.SelectSetQuery:
if not breakdown.enabled:
return events_query
@ -312,7 +312,7 @@ class TrendsQueryBuilder(DataWarehouseInsightQueryMixin):
def _outer_select_query(
self, breakdown: Breakdown, inner_query: ast.SelectQuery
) -> ast.SelectQuery | ast.SelectUnionQuery:
) -> ast.SelectQuery | ast.SelectSetQuery:
total_array = parse_expr(
"""
arrayMap(
@ -453,7 +453,7 @@ class TrendsQueryBuilder(DataWarehouseInsightQueryMixin):
) or get_breakdown_limit_for_context(self.limit_context)
def _inner_select_query(
self, breakdown: Breakdown, inner_query: ast.SelectQuery | ast.SelectUnionQuery
self, breakdown: Breakdown, inner_query: ast.SelectQuery | ast.SelectSetQuery
) -> ast.SelectQuery:
query = cast(
ast.SelectQuery,

View File

@ -110,16 +110,10 @@ class TrendsQueryRunner(QueryRunner):
return BASE_MINIMUM_INSIGHT_REFRESH_INTERVAL
def to_query(self) -> ast.SelectUnionQuery:
queries = []
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_query(self) -> ast.SelectSetQuery:
return ast.SelectSetQuery.create_from_queries(self.to_queries(), "UNION ALL")
def to_queries(self) -> list[ast.SelectQuery | ast.SelectUnionQuery]:
def to_queries(self) -> list[ast.SelectQuery | ast.SelectSetQuery]:
queries = []
with self.timings.measure("trends_to_query"):
for series in self.series:
@ -154,7 +148,7 @@ class TrendsQueryRunner(QueryRunner):
breakdown_value: Optional[str | int | list[str]] = None,
compare_value: Optional[Compare] = None,
include_recordings: Optional[bool] = None,
) -> ast.SelectQuery | ast.SelectUnionQuery:
) -> ast.SelectQuery | ast.SelectSetQuery:
with self.timings.measure("trends_to_actors_query"):
if self.query.breakdownFilter and self.query.breakdownFilter.breakdown_type == BreakdownType.COHORT:
if self.query.breakdownFilter.breakdown in ("all", ["all"]) or breakdown_value == "all":
@ -300,18 +294,16 @@ class TrendsQueryRunner(QueryRunner):
def calculate(self):
queries = self.to_queries()
if len(queries) == 1:
response_hogql_query = queries[0]
if len(queries) == 0:
response_hogql = ""
else:
response_hogql_query = ast.SelectUnionQuery(select_queries=[])
for query in queries:
if isinstance(query, ast.SelectQuery):
response_hogql_query.select_queries.append(query)
else:
response_hogql_query.select_queries.extend(query.select_queries)
if len(queries) == 1:
response_hogql_query = queries[0]
else:
response_hogql_query = ast.SelectSetQuery.create_from_queries(queries, "UNION ALL")
with self.timings.measure("printing_hogql_for_response"):
response_hogql = to_printed_hogql(response_hogql_query, self.team, self.modifiers)
with self.timings.measure("printing_hogql_for_response"):
response_hogql = to_printed_hogql(response_hogql_query, self.team, self.modifiers)
res_matrix: list[list[Any] | Any | None] = [None] * len(queries)
timings_matrix: list[list[QueryTiming] | None] = [None] * (2 + len(queries))
@ -320,7 +312,7 @@ class TrendsQueryRunner(QueryRunner):
def run(
index: int,
query: ast.SelectQuery | ast.SelectUnionQuery,
query: ast.SelectQuery | ast.SelectSetQuery,
timings: HogQLTimings,
is_parallel: bool,
query_tags: Optional[dict] = None,

View File

@ -11,7 +11,7 @@ class QueryAlternator:
_group_bys: list[ast.Expr]
_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)
self._query = query
@ -19,7 +19,7 @@ class QueryAlternator:
self._group_bys = []
self._select_from = None
def build(self) -> ast.SelectQuery | ast.SelectUnionQuery:
def build(self) -> ast.SelectQuery | ast.SelectSetQuery:
if len(self._selects) > 0:
self._query.select.extend(self._selects)

View File

@ -708,10 +708,10 @@ class QueryRunner(ABC, Generic[Q, R, CR]):
return fresh_response
@abstractmethod
def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
def to_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
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
raise NotImplementedError()

View File

@ -16,7 +16,7 @@ class WebTopClicksQueryRunner(WebAnalyticsQueryRunner):
response: WebTopClicksQueryResponse
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"):
top_sources_query = parse_select(
"""

View File

@ -24,7 +24,7 @@ class WebGoalsQueryRunner(WebAnalyticsQueryRunner):
response: WebGoalsQueryResponse
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"):
start = self.query_date_range.date_from_as_hogql()
end = self.query_date_range.date_to_as_hogql()

View File

@ -28,7 +28,7 @@ class WebOverviewQueryRunner(WebAnalyticsQueryRunner):
response: WebOverviewQueryResponse
cached_response: CachedWebOverviewQueryResponse
def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery:
def to_query(self) -> ast.SelectQuery | ast.SelectSetQuery:
return self.outer_select
def calculate(self):

View File

@ -7,6 +7,8 @@ from dateutil import parser
from django.conf import settings
from django.utils import timezone
from rest_framework.exceptions import ValidationError
from posthog.hogql.resolver_utils import extract_select_queries
from posthog.queries.util import PersonPropertiesMode
from posthog.clickhouse.client.connection import Workload
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
).to_query()
select_queries: list[ast.SelectQuery] = [query] if isinstance(query, ast.SelectQuery) else query.select_queries
for select_query in select_queries:
for select_query in extract_select_queries(query):
columns: dict[str, ast.Expr] = {}
for expr in select_query.select:
if isinstance(expr, ast.Alias):

View File

@ -328,7 +328,7 @@ class PersonsPropertiesSubQuery:
self._filter = filter
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):
return parse_select(
"""
@ -383,7 +383,7 @@ HAVING argMax(is_deleted, version) = 0 AND {cohort_predicate}
self._filter = filter
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:
return parse_select(
self.raw_cohort_to_distinct_id,
@ -433,7 +433,7 @@ class PersonsIdCompareOperation:
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:
return None
@ -529,14 +529,14 @@ class ReplayFiltersEventsSubQuery:
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
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"])))
else:
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"])]))
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.database.database import Database, create_hogql_database
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.user import User
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)
if isinstance(hogql_query, ast.SelectUnionQuery):
queries = hogql_query.select_queries
if isinstance(hogql_query, ast.SelectSetQuery):
queries = list(extract_select_queries(hogql_query))
else:
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():
ctes.add(name)
if isinstance(cte.expr, ast.SelectUnionQuery):
queries.extend(cte.expr.select_queries)
if isinstance(cte.expr, ast.SelectSetQuery):
queries.extend(list(extract_select_queries(cte.expr)))
elif isinstance(cte.expr, ast.SelectQuery):
queries.append(cte.expr)
@ -130,8 +131,8 @@ def get_parents_from_model_query(model_query: str) -> set[str]:
continue
queries.append(join.table)
elif isinstance(join.table, ast.SelectUnionQuery):
queries.extend(join.table.select_queries)
elif isinstance(join.table, ast.SelectSetQuery):
queries.extend(list(extract_select_queries(join.table)))
while join is not None:
parent_name = join.table.chain[0] # type: ignore

View File

@ -105,7 +105,7 @@ phonenumberslite==8.13.6
openai==1.51.2
tiktoken==0.8.0
nh3==0.2.14
hogql-parser==1.0.45
hogql-parser==1.0.46
zxcvbn==4.4.28
zstd==1.5.5.1
xmlsec==1.3.13 # Do not change this version - it will break SAML

View File

@ -282,7 +282,7 @@ h11==0.13.0
# wsproto
hexbytes==1.0.0
# via dlt
hogql-parser==1.0.45
hogql-parser==1.0.46
# via -r requirements.in
httpcore==1.0.2
# via httpx