mirror of
https://github.com/BillyOutlast/posthog.git
synced 2026-02-04 03:01:23 +01:00
feat(hogql): Update antlr grammar to allow nested table identifiers (#30406)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
@@ -573,7 +573,7 @@ void hogqlparserParserInitialize() {
|
||||
3,146,73,0,1227,1226,1,0,0,0,1227,1228,1,0,0,0,1228,1229,1,0,0,0,1229,
|
||||
1230,5,152,0,0,1230,143,1,0,0,0,1231,1232,3,148,74,0,1232,1233,5,123,
|
||||
0,0,1233,1235,1,0,0,0,1234,1231,1,0,0,0,1234,1235,1,0,0,0,1235,1236,1,
|
||||
0,0,0,1236,1237,3,164,82,0,1237,145,1,0,0,0,1238,1243,3,122,61,0,1239,
|
||||
0,0,0,1236,1237,3,138,69,0,1237,145,1,0,0,0,1238,1243,3,122,61,0,1239,
|
||||
1240,5,119,0,0,1240,1242,3,122,61,0,1241,1239,1,0,0,0,1242,1245,1,0,0,
|
||||
0,1243,1241,1,0,0,0,1243,1244,1,0,0,0,1244,1247,1,0,0,0,1245,1243,1,0,
|
||||
0,0,1246,1248,5,119,0,0,1247,1246,1,0,0,0,1247,1248,1,0,0,0,1248,147,
|
||||
@@ -10549,8 +10549,8 @@ HogQLParser::TableIdentifierContext::TableIdentifierContext(ParserRuleContext *p
|
||||
: ParserRuleContext(parent, invokingState) {
|
||||
}
|
||||
|
||||
HogQLParser::IdentifierContext* HogQLParser::TableIdentifierContext::identifier() {
|
||||
return getRuleContext<HogQLParser::IdentifierContext>(0);
|
||||
HogQLParser::NestedIdentifierContext* HogQLParser::TableIdentifierContext::nestedIdentifier() {
|
||||
return getRuleContext<HogQLParser::NestedIdentifierContext>(0);
|
||||
}
|
||||
|
||||
HogQLParser::DatabaseIdentifierContext* HogQLParser::TableIdentifierContext::databaseIdentifier() {
|
||||
@@ -10603,7 +10603,7 @@ HogQLParser::TableIdentifierContext* HogQLParser::tableIdentifier() {
|
||||
break;
|
||||
}
|
||||
setState(1236);
|
||||
identifier();
|
||||
nestedIdentifier();
|
||||
|
||||
}
|
||||
catch (RecognitionException &e) {
|
||||
|
||||
@@ -2222,7 +2222,7 @@ public:
|
||||
public:
|
||||
TableIdentifierContext(antlr4::ParserRuleContext *parent, size_t invokingState);
|
||||
virtual size_t getRuleIndex() const override;
|
||||
IdentifierContext *identifier();
|
||||
NestedIdentifierContext *nestedIdentifier();
|
||||
DatabaseIdentifierContext *databaseIdentifier();
|
||||
antlr4::tree::TerminalNode *DOT();
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2528,12 +2528,17 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
|
||||
}
|
||||
|
||||
VISIT(TableIdentifier) {
|
||||
string text = visitAsString(ctx->identifier());
|
||||
auto nested_identifier_ctx = ctx->nestedIdentifier();
|
||||
vector<string> nested =
|
||||
nested_identifier_ctx ? any_cast<vector<string>>(visit(nested_identifier_ctx)) : vector<string>();
|
||||
|
||||
auto database_identifier_ctx = ctx->databaseIdentifier();
|
||||
if (database_identifier_ctx) {
|
||||
return vector<string>{visitAsString(database_identifier_ctx), text};
|
||||
vector<string> database_plus_nested = vector<string>{visitAsString(database_identifier_ctx)};
|
||||
database_plus_nested.insert(database_plus_nested.end(), nested.begin(), nested.end());
|
||||
return database_plus_nested;
|
||||
}
|
||||
return vector<string>{text};
|
||||
return nested;
|
||||
}
|
||||
|
||||
VISIT(TableArgList) { return visitPyListOfObjects(ctx->columnExpr()); }
|
||||
|
||||
@@ -32,7 +32,7 @@ module = Extension(
|
||||
|
||||
setup(
|
||||
name="hogql_parser",
|
||||
version="1.1.0",
|
||||
version="1.1.1",
|
||||
url="https://github.com/PostHog/posthog/tree/master/common/hogql_parser",
|
||||
description="HogQL parser for internal PostHog use",
|
||||
author="PostHog Inc.",
|
||||
|
||||
@@ -259,7 +259,7 @@ tableExpr
|
||||
| placeholder # TableExprPlaceholder
|
||||
;
|
||||
tableFunctionExpr: identifier LPAREN tableArgList? RPAREN;
|
||||
tableIdentifier: (databaseIdentifier DOT)? identifier;
|
||||
tableIdentifier: (databaseIdentifier DOT)? nestedIdentifier;
|
||||
tableArgList: columnExpr (COMMA columnExpr)* COMMA?;
|
||||
|
||||
// Databases
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -504,7 +504,7 @@ def serializedATN():
|
||||
1227,1228,1,0,0,0,1228,1229,1,0,0,0,1229,1230,5,152,0,0,1230,143,
|
||||
1,0,0,0,1231,1232,3,148,74,0,1232,1233,5,123,0,0,1233,1235,1,0,0,
|
||||
0,1234,1231,1,0,0,0,1234,1235,1,0,0,0,1235,1236,1,0,0,0,1236,1237,
|
||||
3,164,82,0,1237,145,1,0,0,0,1238,1243,3,122,61,0,1239,1240,5,119,
|
||||
3,138,69,0,1237,145,1,0,0,0,1238,1243,3,122,61,0,1239,1240,5,119,
|
||||
0,0,1240,1242,3,122,61,0,1241,1239,1,0,0,0,1242,1245,1,0,0,0,1243,
|
||||
1241,1,0,0,0,1243,1244,1,0,0,0,1244,1247,1,0,0,0,1245,1243,1,0,0,
|
||||
0,1246,1248,5,119,0,0,1247,1246,1,0,0,0,1247,1248,1,0,0,0,1248,147,
|
||||
@@ -9000,8 +9000,8 @@ class HogQLParser ( Parser ):
|
||||
super().__init__(parent, invokingState)
|
||||
self.parser = parser
|
||||
|
||||
def identifier(self):
|
||||
return self.getTypedRuleContext(HogQLParser.IdentifierContext,0)
|
||||
def nestedIdentifier(self):
|
||||
return self.getTypedRuleContext(HogQLParser.NestedIdentifierContext,0)
|
||||
|
||||
|
||||
def databaseIdentifier(self):
|
||||
@@ -9040,7 +9040,7 @@ class HogQLParser ( Parser ):
|
||||
|
||||
|
||||
self.state = 1236
|
||||
self.identifier()
|
||||
self.nestedIdentifier()
|
||||
except RecognitionException as re:
|
||||
localctx.exception = re
|
||||
self._errHandler.reportError(self, re)
|
||||
|
||||
@@ -1090,10 +1090,12 @@ class HogQLParseTreeConverter(ParseTreeVisitor):
|
||||
return ast.JoinExpr(table=ast.Field(chain=[name]), table_args=args)
|
||||
|
||||
def visitTableIdentifier(self, ctx: HogQLParser.TableIdentifierContext):
|
||||
text = self.visit(ctx.identifier())
|
||||
nested = self.visit(ctx.nestedIdentifier()) if ctx.nestedIdentifier() else []
|
||||
|
||||
if ctx.databaseIdentifier():
|
||||
return [self.visit(ctx.databaseIdentifier()), text]
|
||||
return [text]
|
||||
return [self.visit(ctx.databaseIdentifier()), *nested]
|
||||
|
||||
return nested
|
||||
|
||||
def visitTableArgList(self, ctx: HogQLParser.TableArgListContext):
|
||||
return [self.visit(arg) for arg in ctx.columnExpr()]
|
||||
|
||||
@@ -311,12 +311,14 @@ class Resolver(CloningVisitor):
|
||||
return response
|
||||
|
||||
if isinstance(node.table, ast.Field):
|
||||
table_name = str(node.table.chain[0])
|
||||
table_alias = node.alias or table_name
|
||||
table_name_chain = [str(n) for n in node.table.chain]
|
||||
table_name_dot_notation = ".".join(table_name_chain)
|
||||
table_name_alias = "__".join(table_name_chain)
|
||||
table_alias: str = node.alias or table_name_alias
|
||||
if table_alias in scope.tables:
|
||||
raise QueryError(f'Already have joined a table called "{table_alias}". Can\'t redefine.')
|
||||
|
||||
database_table = self.database.get_table(table_name)
|
||||
database_table = self.database.get_table(table_name_dot_notation)
|
||||
|
||||
if isinstance(database_table, SavedQuery):
|
||||
self.current_view_depth += 1
|
||||
@@ -343,7 +345,7 @@ class Resolver(CloningVisitor):
|
||||
|
||||
# Always add an alias for function call tables. This way `select table.* from table` is replaced with
|
||||
# `select table.* from something() as table`, and not with `select something().* from something()`.
|
||||
if table_alias != table_name or isinstance(database_table, FunctionCallTable):
|
||||
if table_alias != table_name_alias or isinstance(database_table, FunctionCallTable):
|
||||
node_type = ast.TableAliasType(alias=table_alias, table_type=node_table_type)
|
||||
else:
|
||||
node_type = node_table_type
|
||||
|
||||
@@ -18,6 +18,7 @@ from posthog.hogql.database.models import (
|
||||
StringDatabaseField,
|
||||
StringJSONDatabaseField,
|
||||
)
|
||||
from posthog.hogql.database.schema.events import EventsTable
|
||||
from posthog.hogql.errors import QueryError
|
||||
from posthog.hogql.parser import parse_select
|
||||
from posthog.hogql.printer import print_ast, print_prepared_ast
|
||||
@@ -712,3 +713,13 @@ class TestResolver(BaseTest):
|
||||
):
|
||||
node: ast.SelectQuery = self._select(query)
|
||||
resolve_types(node, context, dialect="clickhouse")
|
||||
|
||||
def test_nested_table_name(self):
|
||||
self.database.__setattr__("nested.events", EventsTable())
|
||||
query = "SELECT * FROM nested.events"
|
||||
resolve_types(self._select(query), self.context, dialect="hogql")
|
||||
|
||||
def test_deeply_nested_table_name(self):
|
||||
self.database.__setattr__("nested.events.some.other.table", EventsTable())
|
||||
query = "SELECT * FROM nested.events.some.other.table"
|
||||
resolve_types(self._select(query), self.context, dialect="hogql")
|
||||
|
||||
@@ -116,7 +116,7 @@ phonenumberslite==8.13.6
|
||||
openai==1.66.3
|
||||
tiktoken==0.9.*
|
||||
nh3==0.2.14
|
||||
hogql-parser==1.1.0
|
||||
hogql-parser==1.1.1
|
||||
zxcvbn==4.4.28
|
||||
zstd==1.5.5.1
|
||||
xmlsec==1.3.14
|
||||
|
||||
@@ -364,7 +364,7 @@ h11==0.13.0
|
||||
# wsproto
|
||||
hexbytes==1.0.0
|
||||
# via dlt
|
||||
hogql-parser==1.1.0
|
||||
hogql-parser==1.1.1
|
||||
# via -r requirements.in
|
||||
httpcore==1.0.2
|
||||
# via httpx
|
||||
|
||||
Reference in New Issue
Block a user