]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Improve migration notes for 2.1
authorFederico Caselli <cfederico87@gmail.com>
Thu, 22 Jan 2026 21:59:37 +0000 (22:59 +0100)
committerFederico Caselli <cfederico87@gmail.com>
Wed, 28 Jan 2026 20:19:36 +0000 (21:19 +0100)
Added syntax extension and PostgreSQL persisted change in Compute

Change-Id: I17d107cdc707dfd798841d9df0a703a94d201ad7

doc/build/changelog/changelog_21.rst
doc/build/changelog/migration_21.rst
doc/build/core/dml.rst
lib/sqlalchemy/sql/base.py
lib/sqlalchemy/sql/selectable.py

index 59b4e67adc3e60800f9589cfa6d44b6a14f94791..28e41ede1f8e8494dfc854e8f1deaba9664134ed 100644 (file)
 
             :ref:`examples_syntax_extensions`
 
+            :ref:`change_new_syntax_ext`
+
 
     .. change::
         :tags: sql
index bfc259e62b085764a7f6b83f74299772af111c38..aeef1393c67ba8cbdffb9f298e71029fee78d801 100644 (file)
@@ -827,6 +827,85 @@ It's hoped that in most cases, this change will make
 
 :ticket:`8601`
 
+.. _change_new_syntax_ext:
+
+New Syntax Extension Feature for Core
+-------------------------------------
+
+Added the ability to create custom SQL constructs that can define new
+clauses within SELECT, INSERT, UPDATE, and DELETE statements without
+needing to modify the construction or compilation code of
+:class:`.Select`, :class:`_dml.Insert`, :class:`.Update`, or :class:`.Delete`
+directly.
+
+Custom extension can be created by subclassing the class
+:class:`sqlalchemy.sql.SyntaxExtension`.
+For example, to create render the ``INTO OUTFILE`` clause of select
+supported by MariaDB and MySQL, can be implimented using syntax extensions
+as follows::
+
+    from sqlalchemy.ext.compiler import compiles
+    from sqlalchemy.sql import ClauseElement, Select, SyntaxExtension, visitors
+
+
+    def into_outfile(name: str) -> "IntoOutFile":
+        """Return a INTO OUTFILE construct"""
+        return IntoOutFile(name)
+
+
+    class IntoOutFile(SyntaxExtension, ClauseElement):
+        """Define the INTO OUTFILE class."""
+
+        _traverse_internals = [("name", visitors.InternalTraversal.dp_string)]
+        """Structure that defines how SQLAlchemy can cache this element.
+        Specify ``inherit_cache=False`` to turn off caching.
+        """
+        name: str
+
+        def __init__(self, name: str):
+            self.name = name
+
+        def apply_to_select(self, select_stmt: Select) -> None:
+            """Called when the :meth:`.Select.ext` method is called."""
+            select_stmt.apply_syntax_extension_point(
+                self.append_replacing_same_type, "post_body"
+            )
+
+
+    @compiles(IntoOutFile)
+    def _compile_into_outfile(element: IntoOutFile, compiler, **kw):
+        """a compiles extension that compiles to SQL IntoOutFile"""
+        name = element.name.replace("'", "''")
+        return f"INTO OUTFILE '{name}'"
+
+This can then be used in a select using the :meth:`.Select.ext` method:
+
+.. sourcecode:: pycon+sql
+
+    >>> import sqlalchemy as sa
+
+    >>> stmt = (
+    ...     sa.select(sa.column("a"))
+    ...     .select_from(sa.table("tbl"))
+    ...     .ext(into_outfile("myfile.txt"))
+    ... )
+    >>> print(sql)
+    {printsql}SELECT a
+    FROM tbl INTO OUTFILE 'myfile.txt'{stop}
+
+Several SQLAlchemy features custom to a single backend have been
+re-implemented using this new system, including PostgreSQL
+:func:`_postgresql.distinct_on` and MySQL :func:`_mysql.limit` functions
+that supersede the previous implementations.
+
+.. seealso::
+
+    :ref:`examples_syntax_extensions` - A fully documented example of a
+    ``QUALIFY`` clause implemented using this new feature.
+
+:ticket:`12195`
+:ticket:`12342`
+
 
 .. _change_11234:
 
@@ -1393,6 +1472,33 @@ used by the :class:`.MetaData` is not what's desired.
 
 :ticket:`10594`
 
+Support for ``VIRTUAL`` computed columns
+----------------------------------------
+
+The behaviour of :paramref:`.Computed.persisted` has change in SQLAlchemy 2.1
+to no longer indicate ``STORED`` computed columns by default in PostgreSQL..
+
+This change aligns SQLAlchemy with PostgreSQL 18+, which has introduced
+support for ``VIRTUAL`` computed columns, and has made them the default
+type if no qualifier is specified.
+
+Migration Path
+^^^^^^^^^^^^^^
+
+To maintain the previous behaviour of ``STORED`` computed columns,
+:paramref:`.Computed.persisted` should be set to ``True`` explicitly::
+
+    from sqlalchemy import Table, Column, MetaData, Computed, Integer
+
+    metadata = MetaData()
+
+    t = Table(
+        "t",
+        metadata,
+        Column("x", Integer),
+        Column("x^2", Integer, Computed("x * x", persisted=True)),
+    )
+
 .. _change_10556:
 
 Addition of ``BitString`` subclass for handling postgresql ``BIT`` columns
index 1724dd6985c613d87daf4a8d87c1fea9a283b0d8..a2591d8b0b700743396a4f07f8d4ddacfaa2912a 100644 (file)
@@ -36,6 +36,10 @@ Class documentation for the constructors listed at
 
    .. automethod:: Delete.returning
 
+   .. automethod:: Delete.ext
+
+   .. automethod:: Delete.apply_syntax_extension_point
+
 .. autoclass:: Insert
    :members:
 
@@ -45,6 +49,10 @@ Class documentation for the constructors listed at
 
    .. automethod:: Insert.returning
 
+   .. automethod:: Insert.ext
+
+   .. automethod:: Insert.apply_syntax_extension_point
+
 .. autoclass:: Update
    :members:
 
@@ -56,6 +64,10 @@ Class documentation for the constructors listed at
 
    .. automethod:: Update.values
 
+   .. automethod:: Update.ext
+
+   .. automethod:: Update.apply_syntax_extension_point
+
 .. autoclass:: sqlalchemy.sql.expression.UpdateBase
    :members:
 
index bb6e98056fb684898be0412aa84ac8e03d922a10..8ffa489608d3e136d056033bf4e8f2132f6bc37f 100644 (file)
@@ -1132,6 +1132,8 @@ class HasSyntaxExtensions(Generic[_L]):
 
             :ref:`examples_syntax_extensions`
 
+            :meth:`.ext`
+
 
         """  # noqa: E501
 
@@ -1194,13 +1196,13 @@ class SyntaxExtension(roles.SyntaxExtensionRole):
         self, existing: Sequence[ClauseElement]
     ) -> Sequence[ClauseElement]:
         """Utility function that can be used as
-        :paramref:`_sql.HasSyntaxExtensions.apply_extension_point.apply_fn`
+        :paramref:`_sql.Select.apply_syntax_extension_point.apply_fn`
         to remove any other element of the same type in existing and appending
         ``self`` to the list.
 
         This is equivalent to::
 
-            stmt.apply_extension_point(
+            stmt.apply_syntax_extension_point(
                 lambda existing: [
                     *(e for e in existing if not isinstance(e, ReplaceOfTypeExt)),
                     self,
@@ -1212,7 +1214,8 @@ class SyntaxExtension(roles.SyntaxExtensionRole):
 
             :ref:`examples_syntax_extensions`
 
-            :meth:`_sql.HasSyntaxExtensions.apply_syntax_extension_point`
+            :meth:`_sql.Select.apply_syntax_extension_point` and equivalents
+            in :class:`_dml.Insert`, :class:`_dml.Delete`, :class:`_dml.Update`
 
         """  # noqa: E501
         cls = type(self)
index ba2489f3cef7c037276e9aa51b7ac0e2dab254e1..46f5ae5513f46d152a25ec17782764e5bdf7f233 100644 (file)
@@ -6524,7 +6524,7 @@ class Select(
 
             :func:`_postgresql.distinct_on`
 
-            :meth:`_sql.HasSyntaxExtensions.ext`
+            :meth:`.ext`
         """
         self._distinct = True
         if expr: