]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Merge "Add DDL association to Table, CreateView support" into main
authorMichael Bayer <mike_mp@zzzcomputing.com>
Tue, 11 Nov 2025 14:48:55 +0000 (14:48 +0000)
committerGerrit Code Review <gerrit@bbpush.zzzcomputing.com>
Tue, 11 Nov 2025 14:48:55 +0000 (14:48 +0000)
1  2 
doc/build/changelog/migration_21.rst
lib/sqlalchemy/__init__.py
lib/sqlalchemy/sql/compiler.py
lib/sqlalchemy/sql/ddl.py
lib/sqlalchemy/sql/selectable.py
lib/sqlalchemy/testing/config.py
lib/sqlalchemy/testing/requirements.py
test/base/test_tutorials.py
test/requirements.py

index 8dd006798f425ceef6432a8cc38402a734a60606,ba9011c37c15fbf08d7486e4ae343515e504193f..d8e7da5cef854b69abe9919deb039f34318c1992
@@@ -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
+     <sqlalchemy.engine.cursor.CursorResult object at 0x7f573e4a4ad0>
+     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
+     <sqlalchemy.engine.cursor.CursorResult object at 0x7f573e4a4f30>
+     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
  
Simple merge
index ee13cf8da08d35365a41863279c2f7336c5440c5,8abe4da201b8daabb90892522dc0dc77bd0fe15b..d03167dbce8c042e75a9516f2f6b5412516c50d6
@@@ -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
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge