From 45d2a35ad2b04579def93daa1aa90c9cad25b80f Mon Sep 17 00:00:00 2001 From: "Michael S. Molina" <70410625+michael-s-molina@users.noreply.github.com> Date: Tue, 10 Feb 2026 13:13:11 -0300 Subject: [PATCH] chore(deps): Upgrade sqlglot from 27.15.2 to 28.10.0 (#37841) (cherry picked from commit c41942a38ad1a4139526340042a31a6df8f67bef) --- pyproject.toml | 2 +- requirements/base.txt | 2 +- requirements/development.txt | 2 +- superset/sql/dialects/pinot.py | 2 ++ superset/sql/parse.py | 10 +++++----- tests/unit_tests/sql/parse_tests.py | 17 +++++++++++++++++ 6 files changed, 27 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3f8f6cf3e68..532b01da6db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,7 +98,7 @@ dependencies = [ "slack_sdk>=3.19.0, <4", "sqlalchemy>=1.4, <2", "sqlalchemy-utils>=0.38.3, <0.39", - "sqlglot>=27.15.2, <28", + "sqlglot>=28.10.0, <29", # newer pandas needs 0.9+ "tabulate>=0.9.0, <1.0", "typing-extensions>=4, <5", diff --git a/requirements/base.txt b/requirements/base.txt index a0de224ac75..c485801b9d7 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -396,7 +396,7 @@ sqlalchemy-utils==0.38.3 # via # apache-superset (pyproject.toml) # flask-appbuilder -sqlglot==27.15.2 +sqlglot==28.10.0 # via apache-superset (pyproject.toml) sshtunnel==0.4.0 # via apache-superset (pyproject.toml) diff --git a/requirements/development.txt b/requirements/development.txt index bad47b382c6..2ed2a054ab6 100644 --- a/requirements/development.txt +++ b/requirements/development.txt @@ -954,7 +954,7 @@ sqlalchemy-utils==0.38.3 # -c requirements/base.txt # apache-superset # flask-appbuilder -sqlglot==27.15.2 +sqlglot==28.10.0 # via # -c requirements/base.txt # apache-superset diff --git a/superset/sql/dialects/pinot.py b/superset/sql/dialects/pinot.py index e0aca308171..6344fee4a0d 100644 --- a/superset/sql/dialects/pinot.py +++ b/superset/sql/dialects/pinot.py @@ -144,6 +144,8 @@ class Pinot(MySQL): e.args.get("expression"), e.args.get("variant"), ), + # Preserve Pinot's YEAROFWEEK (sqlglot normalizes to YEAR_OF_WEEK) + exp.YearOfWeek: lambda self, e: self.func("YEAROFWEEK", e.this), } # Remove DATE_TRUNC transformation - Pinot supports standard SQL DATE_TRUNC TRANSFORMS.pop(exp.DateTrunc, None) diff --git a/superset/sql/parse.py b/superset/sql/parse.py index 72812a9c7ef..c2f3e72c436 100644 --- a/superset/sql/parse.py +++ b/superset/sql/parse.py @@ -272,7 +272,7 @@ class RLSAsSubqueryTransformer(RLSTransformer): this=exp.Select( expressions=[exp.Star()], where=exp.Where(this=predicate), - **{"from": exp.From(this=node.copy())}, + from_=exp.From(this=node.copy()), ), alias=alias, ) @@ -817,7 +817,7 @@ class SQLStatement(BaseSQLStatement[exp.Expression]): limit=exp.Limit( expression=exp.Literal(this=str(limit), is_string=False) ), - **{"from": exp.From(this=exp.Subquery(this=self._parsed.copy()))}, + from_=exp.From(this=exp.Subquery(this=self._parsed.copy())), ) else: # method == LimitMethod.FETCH_MANY pass @@ -828,7 +828,7 @@ class SQLStatement(BaseSQLStatement[exp.Expression]): :return: True if the statement has a CTE at the top level. """ - return "with" in self._parsed.args + return bool(self._parsed.args.get("with_")) def as_cte(self, alias: str = "__cte") -> SQLStatement: """ @@ -841,8 +841,8 @@ class SQLStatement(BaseSQLStatement[exp.Expression]): :param alias: The alias to use for the CTE. :return: A new SQLStatement with the CTE. """ - existing_ctes = self._parsed.args["with"].expressions if self.has_cte() else [] - self._parsed.args["with"] = None + existing_ctes = self._parsed.args["with_"].expressions if self.has_cte() else [] + self._parsed.args["with_"] = None new_cte = exp.CTE( this=self._parsed.copy(), alias=exp.TableAlias(this=exp.Identifier(this=alias)), diff --git a/tests/unit_tests/sql/parse_tests.py b/tests/unit_tests/sql/parse_tests.py index 66625f9a781..274acc01b5a 100644 --- a/tests/unit_tests/sql/parse_tests.py +++ b/tests/unit_tests/sql/parse_tests.py @@ -1867,6 +1867,23 @@ def test_as_cte(sql: str, engine: str, expected: str) -> None: assert SQLStatement(sql, engine).as_cte().format() == expected +def test_as_cte_called_twice() -> None: + """ + Test that calling as_cte() multiple times on the same instance works. + + Regression test for a bug where as_cte() sets self._parsed.args["with_"] = None + after extracting CTEs, but has_cte() only checked if the key existed, not if + the value was truthy. This caused an AttributeError on subsequent as_cte() calls. + """ + sql = "WITH cte AS (SELECT 1) SELECT * FROM cte" + stmt = SQLStatement(sql, "postgresql") + + assert stmt.has_cte() is True + stmt.as_cte() + assert stmt.has_cte() is False + stmt.as_cte() + + @pytest.mark.parametrize( "sql, rules, expected", [