From: Eric Masseran Date: Fri, 16 Jul 2021 17:25:02 +0000 (+0200) Subject: Merge remote-tracking branch 'origin/master' into cte-nesting-support X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=029eeb2b5a24e8360d36eb2525385d9e7676069f;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Merge remote-tracking branch 'origin/master' into cte-nesting-support * origin/master: (27 commits) reset key/name when TableValuedColumn is adapted limit None->null coercion to not occur with crud apply list() around weakkeydictionary iteration update case statement in dictlike-polymorphic Version 1.4.22 placeholder - 1.4.21 Ensure alias traversal block works when adapt_from_selectables present typo changelog updates Adjust CTE recrusive col list to accommodate dupe col names Extract format_constraint truncation rules to ON CONFLICT labeling refactor Modernize tests - union implement independent CTEs Correct docs: pg8000 supports PostgreSQL UUID represent tablesample.sampling as FunctionElement in all cases repair schema_translate_map for schema type use cases implement deferred scalarobject history load add python 2.7 to pr workflow to help catch py3 only issues Docs: fixed typo in "Cascades" ... --- 029eeb2b5a24e8360d36eb2525385d9e7676069f diff --cc lib/sqlalchemy/sql/selectable.py index 740ccc0ae7,b6cf7f55e8..f6fcffbf0f --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@@ -2115,7 -2111,80 +2116,80 @@@ class HasCTE(roles.HasCTERole) """ + _has_ctes_traverse_internals = [ + ("_independent_ctes", InternalTraversal.dp_clauseelement_list), + ] + + _independent_ctes = () + + @_generative + def add_cte(self, cte): + """Add a :class:`_sql.CTE` to this statement object that will be + independently rendered even if not referenced in the statement + otherwise. + + This feature is useful for the use case of embedding a DML statement + such as an INSERT or UPDATE as a CTE inline with a primary statement + that may draw from its results indirectly; while PostgreSQL is known + to support this usage, it may not be supported by other backends. + + E.g.:: + + from sqlalchemy import table, column, select + t = table('t', column('c1'), column('c2')) + + ins = t.insert().values({"c1": "x", "c2": "y"}).cte() + + stmt = select(t).add_cte(ins) + + Would render:: + + WITH anon_1 AS + (INSERT INTO t (c1, c2) VALUES (:param_1, :param_2)) + SELECT t.c1, t.c2 + FROM t + + Above, the "anon_1" CTE is not referred towards in the SELECT + statement, however still accomplishes the task of running an INSERT + statement. + + Similarly in a DML-related context, using the PostgreSQL + :class:`_postgresql.Insert` construct to generate an "upsert":: + + from sqlalchemy import table, column + from sqlalchemy.dialects.postgresql import insert + + t = table("t", column("c1"), column("c2")) + + delete_statement_cte = ( + t.delete().where(t.c.c1 < 1).cte("deletions") + ) + + insert_stmt = insert(t).values({"c1": 1, "c2": 2}) + update_statement = insert_stmt.on_conflict_do_update( + index_elements=[t.c.c1], + set_={ + "c1": insert_stmt.excluded.c1, + "c2": insert_stmt.excluded.c2, + }, + ).add_cte(delete_statement_cte) + + print(update_statement) + + The above statement renders as:: + + WITH deletions AS + (DELETE FROM t WHERE t.c1 < %(c1_1)s) + INSERT INTO t (c1, c2) VALUES (%(c1)s, %(c2)s) + ON CONFLICT (c1) DO UPDATE SET c1 = excluded.c1, c2 = excluded.c2 + + .. versionadded:: 1.4.21 + + """ + cte = coercions.expect(roles.IsCTERole, cte) + self._independent_ctes += (cte,) + - def cte(self, name=None, recursive=False): + def cte(self, name=None, recursive=False, nesting=False): r"""Return a new :class:`_expression.CTE`, or Common Table Expression instance. diff --cc test/sql/test_cte.py index b53a5f014b,f1d27aa8f1..e9773707ba --- a/test/sql/test_cte.py +++ b/test/sql/test_cte.py @@@ -1,5 -1,9 +1,10 @@@ +import functools + from sqlalchemy import Column from sqlalchemy import delete + from sqlalchemy import Integer + from sqlalchemy import LABEL_STYLE_TABLENAME_PLUS_COL + from sqlalchemy import MetaData + from sqlalchemy import Table from sqlalchemy import testing from sqlalchemy import text from sqlalchemy import update