From: Federico Caselli Date: Thu, 22 Jan 2026 21:59:37 +0000 (+0100) Subject: Improve migration notes for 2.1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c7509cbc5943ec9d0e0524c4760c3ebe421888d5;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Improve migration notes for 2.1 Added syntax extension and PostgreSQL persisted change in Compute Change-Id: I17d107cdc707dfd798841d9df0a703a94d201ad7 --- diff --git a/doc/build/changelog/changelog_21.rst b/doc/build/changelog/changelog_21.rst index 59b4e67adc..28e41ede1f 100644 --- a/doc/build/changelog/changelog_21.rst +++ b/doc/build/changelog/changelog_21.rst @@ -521,6 +521,8 @@ :ref:`examples_syntax_extensions` + :ref:`change_new_syntax_ext` + .. change:: :tags: sql diff --git a/doc/build/changelog/migration_21.rst b/doc/build/changelog/migration_21.rst index bfc259e62b..aeef1393c6 100644 --- a/doc/build/changelog/migration_21.rst +++ b/doc/build/changelog/migration_21.rst @@ -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 diff --git a/doc/build/core/dml.rst b/doc/build/core/dml.rst index 1724dd6985..a2591d8b0b 100644 --- a/doc/build/core/dml.rst +++ b/doc/build/core/dml.rst @@ -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: diff --git a/lib/sqlalchemy/sql/base.py b/lib/sqlalchemy/sql/base.py index bb6e98056f..8ffa489608 100644 --- a/lib/sqlalchemy/sql/base.py +++ b/lib/sqlalchemy/sql/base.py @@ -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) diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py index ba2489f3ce..46f5ae5513 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -6524,7 +6524,7 @@ class Select( :func:`_postgresql.distinct_on` - :meth:`_sql.HasSyntaxExtensions.ext` + :meth:`.ext` """ self._distinct = True if expr: