Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(hogql): Allow a placeholder to be used in place of a select statement #19767

Merged
merged 13 commits into from
Jan 18, 2024
Merged
1,923 changes: 967 additions & 956 deletions hogql_parser/HogQLParser.cpp

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions hogql_parser/HogQLParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ class HogQLParser : public antlr4::Parser {
antlr4::tree::TerminalNode *LPAREN();
SelectUnionStmtContext *selectUnionStmt();
antlr4::tree::TerminalNode *RPAREN();
PlaceholderContext *placeholder();


virtual std::any accept(antlr4::tree::ParseTreeVisitor *visitor) override;
Expand Down
2 changes: 1 addition & 1 deletion hogql_parser/HogQLParser.interp

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions hogql_parser/parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,12 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
if (select_stmt_ctx) {
return visit(select_stmt_ctx);
}

auto placeholder_ctx = ctx->placeholder();
if (placeholder_ctx) {
return visitAsPyObject(placeholder_ctx);
}

return visit(ctx->selectUnionStmt());
}

Expand Down Expand Up @@ -344,6 +350,9 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
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")) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as in the Python version, this would be more readable if we did || is_ast_node_instance(query, "Placeholder") in the first if () branch. (I know readability is not the C++ code's strong suit, but that makes reining it in all the more important.)

BTW is_ast_node_instance(query, "SelectQuery"); is called twice in this function, which is an oversight on my side. It's intentionally assigned to is_select_query so that we can check against the error value (-1), which is great for perfect error handling. However, two lines later repeat that call without the check…
These operations are very low risk, so I think in this case I suggest we remove lines 341 and 342. Because if were to run the same error checks for all branches in an else if fashion, we'd have to do quite some if nesting…

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);
Expand Down
2 changes: 1 addition & 1 deletion hogql_parser/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

setup(
name="hogql_parser",
version="1.0.2",
version="1.0.3",
url="https://github.com/PostHog/posthog/tree/master/hogql_parser",
author="PostHog Inc.",
author_email="[email protected]",
Expand Down
1 change: 1 addition & 0 deletions mypy-baseline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ posthog/hogql/database/schema/persons.py:0: error: Argument "chain" to "Field" h
posthog/hogql/database/schema/persons.py:0: note: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance
posthog/hogql/database/schema/persons.py:0: note: Consider using "Sequence" instead, which is covariant
posthog/hogql/parser.py:0: error: Key expression in dictionary comprehension has incompatible type "str"; expected type "Literal['expr', 'order_expr', 'select']" [misc]
posthog/hogql/parser.py:0: error: Statement is unreachable [unreachable]
posthog/hogql/parser.py:0: error: Item "None" of "list[Expr] | None" has no attribute "__iter__" (not iterable) [union-attr]
posthog/hogql/parser.py:0: error: "None" has no attribute "text" [attr-defined]
posthog/hogql/parser.py:0: error: "None" has no attribute "text" [attr-defined]
Expand Down
3 changes: 2 additions & 1 deletion posthog/hogql/grammar/HogQLParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ options {
select: (selectUnionStmt | selectStmt | hogqlxTagElement) EOF;

selectUnionStmt: selectStmtWithParens (UNION ALL selectStmtWithParens)*;
selectStmtWithParens: selectStmt | LPAREN selectUnionStmt RPAREN;
selectStmtWithParens: selectStmt | LPAREN selectUnionStmt RPAREN | placeholder;

selectStmt:
with=withClause?
SELECT DISTINCT? topClause?
Expand Down
2 changes: 1 addition & 1 deletion posthog/hogql/grammar/HogQLParser.interp

Large diffs are not rendered by default.

1,943 changes: 976 additions & 967 deletions posthog/hogql/grammar/HogQLParser.py

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion posthog/hogql/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,14 +172,16 @@ def visitSelectUnionStmt(self, ctx: HogQLParser.SelectUnionStmtContext):
flattened_queries.append(query)
elif isinstance(query, ast.SelectUnionQuery):
flattened_queries.extend(query.select_queries)
elif isinstance(query, ast.Placeholder):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can simplify this case by folding it into the first condition: just replace if isinstance(query, ast.SelectQuery): with if isinstance(query, (ast.SelectQuery, ast.Placeholder)):

flattened_queries.append(query)
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)

def visitSelectStmtWithParens(self, ctx: HogQLParser.SelectStmtWithParensContext):
return self.visit(ctx.selectStmt() or ctx.selectUnionStmt())
return self.visit(ctx.selectStmt() or ctx.selectUnionStmt() or ctx.placeholder())

def visitSelectStmt(self, ctx: HogQLParser.SelectStmtContext):
select_query = ast.SelectQuery(
Expand Down
2 changes: 1 addition & 1 deletion posthog/hogql/test/_test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1467,7 +1467,7 @@ def test_malformed_sql(self):
query = "SELEC 2"
with self.assertRaisesMessage(
SyntaxException,
"mismatched input 'SELEC' expecting {SELECT, WITH, '(', '<'}",
"mismatched input 'SELEC' expecting {SELECT, WITH, '{', '(', '<'}",
) as e:
self._select(query)
self.assertEqual(e.exception.start, 0)
Expand Down
2 changes: 1 addition & 1 deletion posthog/hogql/test/test_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def test_metadata_valid_expr_select(self):
"inputSelect": "timestamp",
"errors": [
{
"message": "mismatched input 'timestamp' expecting {SELECT, WITH, '(', '<'}",
"message": "mismatched input 'timestamp' expecting {SELECT, WITH, '{', '(', '<'}",
"start": 0,
"end": 9,
"fix": None,
Expand Down
2 changes: 1 addition & 1 deletion posthog/hogql_queries/insights/stickiness_query_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def to_query(self) -> List[ast.SelectQuery]: # type: ignore
SELECT 0 as aggregation_target, (number + 1) as num_intervals
FROM numbers(dateDiff({interval}, {date_from} - {interval_subtract}, {date_to}))
UNION ALL
SELECT * FROM ({events_query})
{events_query}
)
GROUP BY num_intervals
ORDER BY num_intervals
Expand Down
2 changes: 1 addition & 1 deletion requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -96,5 +96,5 @@ django-two-factor-auth==1.14.0
phonenumberslite==8.13.6
openai==0.27.8
nh3==0.2.14
hogql-parser==1.0.2
hogql-parser==1.0.3
urllib3[secure,socks]==1.26.18
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ h11==0.13.0
# via wsproto
hexbytes==1.0.0
# via dlt
hogql-parser==1.0.2
hogql-parser==1.0.3
# via -r requirements.in
humanize==4.9.0
# via dlt
Expand Down
Loading