From: Michael Bayer Date: Tue, 11 Nov 2025 14:48:55 +0000 (+0000) Subject: Merge "Add DDL association to Table, CreateView support" into main X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=ebd118a1a24596c12bf32af0d2852877c2842103;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Merge "Add DDL association to Table, CreateView support" into main --- ebd118a1a24596c12bf32af0d2852877c2842103 diff --cc doc/build/changelog/migration_21.rst index 8dd006798f,ba9011c37c..d8e7da5cef --- a/doc/build/changelog/migration_21.rst +++ b/doc/build/changelog/migration_21.rst @@@ -617,89 -618,122 +617,167 @@@ not the database portion: :ticket:`11234` +.. _change_7066: + +Improved ``params()`` implementation for executable statements +-------------------------------------------------------------- + +The :meth:`_sql.ClauseElement.params` and :meth:`_sql.ClauseElement.unique_params` +methods have been deprecated in favor of a new implementation on executable +statements that provides improved performance and better integration with +ORM-enabled statements. + +Executable statement objects like :class:`_sql.Select`, :class:`_sql.CompoundSelect`, +and :class:`_sql.TextClause` now provide an improved :meth:`_sql.ExecutableStatement.params` +method that avoids a full cloned traversal of the statement tree. Instead, parameters +are stored directly on the statement object and efficiently merged during compilation +and/or cache key traversal. + +The new implementation provides several benefits: + +* **Better performance** - Parameters are stored in a simple dictionary rather than + requiring a full statement tree traversal with cloning +* **Proper caching integration** - Parameters are correctly integrated into SQLAlchemy's + cache key system via ``_generate_cache_key()`` +* **ORM statement compatibility** - Works correctly with ORM-enabled statements, including + ORM entities used with :func:`_orm.aliased`, subqueries, CTEs, etc. + +Use of :meth:`_sql.ExecutableStatement.params` is unchanged, provided the given +object is a statement object such as :func:`_sql.select`:: + + stmt = select(table).where(table.c.data == bindparam("x")) + + # Execute with parameter value + result = connection.execute(stmt.params(x=5)) + + # Can be chained and used in subqueries + stmt2 = stmt.params(x=6).subquery().select() + result = connection.execute(stmt2.params(x=7)) # Uses x=7 + +The deprecated :meth:`_sql.ClauseElement.params` and :meth:`_sql.ClauseElement.unique_params` +methods on non-executable elements like :class:`_sql.ColumnElement` and general +:class:`_sql.ClauseElement` instances will continue to work during the deprecation +period but will emit deprecation warnings. + +:ticket:`7066` + + .. _change_4950: - CREATE TABLE AS SELECT Support - ------------------------------- + CREATE VIEW and CREATE TABLE AS SELECT Support + ---------------------------------------------- + + SQLAlchemy 2.1 adds support for the SQL ``CREATE VIEW`` and + ``CREATE TABLE ... AS SELECT`` constructs, as well as the ``SELECT ... INTO`` + variant for selected backends. Both DDL statements generate a table + or table-like construct based on the structure and rows represented by a + SELECT statement. The constructs are available via the :class:`.CreateView` + and :class:`_schema.CreateTableAs` DDL classes, as well as the + :meth:`_sql.SelectBase.into` convenience method. + + Both constructs work in exactly the same way, including that a :class:`.Table` + object is automatically generated from a given :class:`.Select`. DDL + can then be emitted by executing the construct directly or by allowing the + :meth:`.MetaData.create_all` or :meth:`.Table.create` sequences to emit the + correct DDL. + + E.g. using :class:`.CreateView`:: + + >>> from sqlalchemy import Table, Column, Integer, String, MetaData + >>> from sqlalchemy import CreateView, select + >>> + >>> metadata_obj = MetaData() + >>> user_table = Table( + ... "user_account", + ... metadata_obj, + ... Column("id", Integer, primary_key=True), + ... Column("name", String(30)), + ... Column("fullname", String), + ... ) + >>> view = CreateView( + ... select(user_table).where(user_table.c.name.like("%spongebob%")), + ... "spongebob_view", + ... metadata=metadata_obj, + ... ) + + + The above ``CreateView`` construct will emit CREATE VIEW when executed directly, + or when a DDL create operation is run. When using :meth:`.MetaData.create_all`, + the view is created after all dependent tables have been created: + + .. sourcecode:: pycon+sql + + >>> from sqlalchemy import create_engine + >>> e = create_engine("sqlite://", echo=True) + >>> metadata_obj.create_all(e) + {opensql}BEGIN (implicit) + + CREATE TABLE user_account ( + id INTEGER NOT NULL, + name VARCHAR(30), + fullname VARCHAR, + PRIMARY KEY (id) + ) + + CREATE VIEW spongebob_view AS + SELECT user_account.id, user_account.name, user_account.fullname + FROM user_account + WHERE user_account.name LIKE '%spongebob%' + + COMMIT + + The view is usable in SQL expressions via the :attr:`.CreateView.table` attribute: + + .. sourcecode:: pycon+sql + + >>> with e.connect() as conn: + ... conn.execute(select(view.table)) + {opensql}BEGIN (implicit) + SELECT spongebob_view.id, spongebob_view.name, spongebob_view.fullname + FROM spongebob_view + + ROLLBACK + + :class:`_schema.CreateTableAs` works in the same way, emitting ``CREATE TABLE AS``:: + + >>> from sqlalchemy import CreateTableAs + >>> select_stmt = select(users.c.id, users.c.name).where(users.c.name == "squidward") + >>> create_table_as = CreateTableAs(select_stmt, "squidward_users") + + In this case, :class:`.CreateTableAs` was not given a :class:`.MetaData` collection. + While a :class:`.MetaData` collection will be created automatically in this case, + the actual ``CREATE TABLE AS`` statement can also be generated by directly + executing the object: + + .. sourcecode:: pycon+sql + + >>> with e.begin() as conn: + ... conn.execute(create_table_as) + {opensql}BEGIN (implicit) + CREATE TABLE squidward_users AS SELECT user_account.id, user_account.name + FROM user_account + WHERE user_account.name = 'squidward' + COMMIT + + Like before, the :class:`.Table` is accessible from :attr:`.CreateTableAs.table`: + + .. sourcecode:: pycon+sql + + >>> with e.connect() as conn: + ... conn.execute(select(create_table_as.table)) + {opensql}BEGIN (implicit) + SELECT squidward_users.id, squidward_users.name + FROM squidward_users + + ROLLBACK - SQLAlchemy 2.1 adds support for the SQL ``CREATE TABLE ... AS SELECT`` - construct as well as the ``SELECT ... INTO`` variant for selected backends, - which creates a new table directly from the results of a SELECT - statement. This is available via the new :class:`_schema.CreateTableAs` DDL - construct and the :meth:`_sql.SelectBase.into` convenience method. - - The :class:`_schema.CreateTableAs` construct can be used to create a new table - from any SELECT statement:: - - >>> from sqlalchemy import select, CreateTableAs - >>> select_stmt = select(users.c.id, users.c.name).where(users.c.status == "active") - >>> create_table_as = CreateTableAs(select_stmt, "active_users") - - The above construct renders as a ``CREATE TABLE AS`` statement:: - - >>> print(create_table_as) - CREATE TABLE active_users AS SELECT users.id, users.name - FROM users - WHERE users.status = 'active' - - The construct can be executed to emit the above DDL, and the table may then - be accessed using the :attr:`.CreateTableAs.table` attribute which - supplies a :class:`.Table`:: - - >>> print(select(create_table_as.table)) - SELECT users.id, users.name - FROM active_users + .. seealso:: - See :ref:`tutorial_create_table_as` for a tutorial. + :ref:`metadata_create_view` - in :ref:`metadata_toplevel` - .. seealso:: + :ref:`metadata_create_table_as` - in :ref:`metadata_toplevel` - :ref:`tutorial_create_table_as` - in the :ref:`unified_tutorial` + :class:`_schema.CreateView` - DDL construct for CREATE VIEW :class:`_schema.CreateTableAs` - DDL construct for CREATE TABLE AS diff --cc lib/sqlalchemy/sql/compiler.py index ee13cf8da0,8abe4da201..d03167dbce --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@@ -94,9 -93,10 +94,11 @@@ if typing.TYPE_CHECKING from .base import _AmbiguousTableNameMap from .base import CompileState from .base import Executable + from .base import ExecutableStatement from .cache_key import CacheKey + from .ddl import _TableViaSelect from .ddl import CreateTableAs + from .ddl import CreateView from .ddl import ExecutableDDLElement from .dml import Delete from .dml import Insert