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(postgres): add support for materialized CTEs #3171

Merged
merged 2 commits into from Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion sqlglot/expressions.py
Expand Up @@ -1404,7 +1404,12 @@ class WithinGroup(Expression):
# clickhouse supports scalar ctes
# https://clickhouse.com/docs/en/sql-reference/statements/select/with
class CTE(DerivedTable):
arg_types = {"this": True, "alias": True, "scalar": False}
arg_types = {
"this": True,
"alias": True,
"scalar": False,
"materialized": False,
}


class TableAlias(Expression):
Expand Down
9 changes: 8 additions & 1 deletion sqlglot/generator.py
Expand Up @@ -1049,7 +1049,14 @@ def with_sql(self, expression: exp.With) -> str:

def cte_sql(self, expression: exp.CTE) -> str:
alias = self.sql(expression, "alias")
return f"{alias} AS {self.wrap(expression)}"

materialized = expression.args.get("materialized")
if materialized is False:
materialized = "NOT MATERIALIZED "
elif materialized:
materialized = "MATERIALIZED "

return f"{alias} AS {materialized or ''}{self.wrap(expression)}"

def tablealias_sql(self, expression: exp.TableAlias) -> str:
alias = self.sql(expression, "this")
Expand Down
13 changes: 12 additions & 1 deletion sqlglot/parser.py
Expand Up @@ -2546,8 +2546,19 @@ def _parse_cte(self) -> exp.CTE:
self.raise_error("Expected CTE to have alias")

self._match(TokenType.ALIAS)

if self._match_text_seq("NOT", "MATERIALIZED"):
materialized = False
elif self._match_text_seq("MATERIALIZED"):
materialized = True
else:
materialized = None

return self.expression(
exp.CTE, this=self._parse_wrapped(self._parse_statement), alias=alias
exp.CTE,
this=self._parse_wrapped(self._parse_statement),
alias=alias,
materialized=materialized,
)

def _parse_table_alias(
Expand Down
17 changes: 10 additions & 7 deletions tests/dialects/test_postgres.py
Expand Up @@ -40,13 +40,6 @@ def test_postgres(self):
self.validate_identity("CAST(x AS DATEMULTIRANGE)")
self.validate_identity("SELECT ARRAY[1, 2, 3] @> ARRAY[1, 2]")
self.validate_identity("SELECT ARRAY[1, 2, 3] <@ ARRAY[1, 2]")
self.validate_all(
"SELECT ARRAY[1, 2, 3] && ARRAY[1, 2]",
write={
"": "SELECT ARRAY_OVERLAPS(ARRAY(1, 2, 3), ARRAY(1, 2))",
"postgres": "SELECT ARRAY[1, 2, 3] && ARRAY[1, 2]",
},
)
self.validate_identity("x$")
self.validate_identity("SELECT ARRAY[1, 2, 3]")
self.validate_identity("SELECT ARRAY(SELECT 1)")
Expand All @@ -70,6 +63,9 @@ def test_postgres(self):
self.validate_identity("EXEC AS myfunc @id = 123", check_command_warning=True)
self.validate_identity("SELECT CURRENT_USER")
self.validate_identity("SELECT * FROM ONLY t1")
self.validate_identity(
"WITH t1 AS MATERIALIZED (SELECT 1), t2 AS NOT MATERIALIZED (SELECT 2) SELECT * FROM t1, t2"
)
self.validate_identity(
"""LAST_VALUE("col1") OVER (ORDER BY "col2" RANGE BETWEEN INTERVAL '1 DAY' PRECEDING AND '1 month' FOLLOWING)"""
)
Expand Down Expand Up @@ -310,6 +306,13 @@ def test_postgres(self):
)
self.validate_identity("SELECT * FROM t1*", "SELECT * FROM t1")

self.validate_all(
"SELECT ARRAY[1, 2, 3] && ARRAY[1, 2]",
write={
"": "SELECT ARRAY_OVERLAPS(ARRAY(1, 2, 3), ARRAY(1, 2))",
"postgres": "SELECT ARRAY[1, 2, 3] && ARRAY[1, 2]",
},
)
self.validate_all(
"SELECT JSON_EXTRACT_PATH_TEXT(x, k1, k2, k3) FROM t",
read={
Expand Down