From: Marat Sharafutdinov Date: Wed, 18 Dec 2019 15:39:59 +0000 (-0500) Subject: Add CTE prefixes X-Git-Tag: rel_1_3_13~28^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3647b7be1c5d14b5f84649dba5a1e12354dc49a8;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Add CTE prefixes Added support for prefixes to the :class:`.CTE` construct, to allow support for Postgresql 12 "MATERIALIZED" and "NOT MATERIALIZED" phrases. Pull request courtesy Marat Sharafutdinov. Fixes: #5040 Closes: #5043 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/5043 Pull-request-sha: d1b9059a0b6dae8dc2479ac670999b4af07908e0 Change-Id: I2e9cb5d7f85961ec98ee51965de5b3ec4a97be2f (cherry picked from commit f8c562c4f8def3b104d157d9f39d0e11f7d9fd8d) --- diff --git a/doc/build/changelog/unreleased_13/5040.rst b/doc/build/changelog/unreleased_13/5040.rst new file mode 100644 index 0000000000..b1d45f0949 --- /dev/null +++ b/doc/build/changelog/unreleased_13/5040.rst @@ -0,0 +1,11 @@ +.. change:: + :tags: usecase, postgresql + :tickets: 5040 + + Added support for prefixes to the :class:`.CTE` construct, to allow + support for Postgresql 12 "MATERIALIZED" and "NOT MATERIALIZED" phrases. + Pull request courtesy Marat Sharafutdinov. + + .. seealso:: + + :meth:`.HasCTE.cte` diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 4dff9b25b2..3ce7bbf179 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -1694,8 +1694,11 @@ class SQLCompiler(Compiled): if self.positional: kwargs["positional_names"] = self.cte_positional[cte] = [] - text += " AS \n" + cte.original._compiler_dispatch( - self, asfrom=True, **kwargs + text += " AS %s\n%s" % ( + self._generate_prefixes(cte, cte._prefixes, **kwargs), + cte.original._compiler_dispatch( + self, asfrom=True, **kwargs + ), ) if cte._suffixes: diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py index c727b03b4e..d4f636a95c 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -1502,7 +1502,7 @@ class TableSample(Alias): return functions.func.system(self.sampling) -class CTE(Generative, HasSuffixes, Alias): +class CTE(Generative, HasPrefixes, HasSuffixes, Alias): """Represent a Common Table Expression. The :class:`.CTE` object is obtained using the @@ -1531,11 +1531,14 @@ class CTE(Generative, HasSuffixes, Alias): recursive=False, _cte_alias=None, _restates=frozenset(), + _prefixes=None, _suffixes=None, ): self.recursive = recursive self._cte_alias = _cte_alias self._restates = _restates + if _prefixes: + self._prefixes = _prefixes if _suffixes: self._suffixes = _suffixes super(CTE, self)._init(selectable, name=name) @@ -1575,6 +1578,7 @@ class CTE(Generative, HasSuffixes, Alias): name=name, recursive=self.recursive, _cte_alias=self, + _prefixes=self._prefixes, _suffixes=self._suffixes, ) @@ -1584,6 +1588,7 @@ class CTE(Generative, HasSuffixes, Alias): name=self.name, recursive=self.recursive, _restates=self._restates.union([self]), + _prefixes=self._prefixes, _suffixes=self._suffixes, ) @@ -1593,6 +1598,7 @@ class CTE(Generative, HasSuffixes, Alias): name=self.name, recursive=self.recursive, _restates=self._restates.union([self]), + _prefixes=self._prefixes, _suffixes=self._suffixes, ) @@ -1619,13 +1625,20 @@ class HasCTE(object): when combined with RETURNING, as well as a consumer of CTE rows. + .. versionchanged:: 1.1 Added support for UPDATE/INSERT/DELETE as + CTE, CTEs added to UPDATE/INSERT/DELETE. + SQLAlchemy detects :class:`.CTE` objects, which are treated similarly to :class:`.Alias` objects, as special elements to be delivered to the FROM clause of the statement as well as to a WITH clause at the top of the statement. - .. versionchanged:: 1.1 Added support for UPDATE/INSERT/DELETE as - CTE, CTEs added to UPDATE/INSERT/DELETE. + For special prefixes such as PostgreSQL "MATERIALIZED" and + "NOT MATERIALIZED", the :meth:`.CTE.prefix_with` method may be + used to establish these. + + .. versionchanged:: 1.3.13 Added support for prefixes. + In particular - MATERIALIZED and NOT MATERIALIZED. :param name: name given to the common table expression. Like :meth:`._FromClause.alias`, the name can be left as ``None`` diff --git a/test/sql/test_cte.py b/test/sql/test_cte.py index 7008bc1cca..cb6fc54279 100644 --- a/test/sql/test_cte.py +++ b/test/sql/test_cte.py @@ -899,6 +899,28 @@ class CTETest(fixtures.TestBase, AssertsCompiledSQL): 'ON anon_1."order" = "order"."order"', ) + def test_prefixes(self): + orders = table("order", column("order")) + s = select([orders.c.order]).cte("regional_sales") + s = s.prefix_with("NOT MATERIALIZED", dialect="postgresql") + stmt = select([orders]).where(orders.c.order > s.c.order) + + self.assert_compile( + stmt, + 'WITH regional_sales AS (SELECT "order"."order" AS "order" ' + 'FROM "order") SELECT "order"."order" FROM "order", ' + 'regional_sales WHERE "order"."order" > regional_sales."order"', + ) + + self.assert_compile( + stmt, + "WITH regional_sales AS NOT MATERIALIZED " + '(SELECT "order"."order" AS "order" ' + 'FROM "order") SELECT "order"."order" FROM "order", ' + 'regional_sales WHERE "order"."order" > regional_sales."order"', + dialect="postgresql", + ) + def test_suffixes(self): orders = table("order", column("order")) s = select([orders.c.order]).cte("regional_sales")