# SQLAlchemy changelog > The Python SQL toolkit and object-relational mapper. - Vendor: SQLAlchemy - Category: Frameworks & Libraries - Official site: https://www.sqlalchemy.org - Tracked by: What's New (https://whatsnew.fyi/product/sqlalchemy) - Harvested from: GitHub (sqlalchemy/sqlalchemy) - Entries below: 10 (newest first) What's New is an index, not a publisher: every entry below links to the vendor's own release notes, which are the authoritative source. Entries are labelled where they are hand-curated sample data, pre-releases, or drawn from a secondary source such as a developer blog. ## Releases ### rel_2_1_0b3 — 2.1.0b3 - Date: 2026-06-27 - Version: rel_2_1_0b3 - Original notes: https://github.com/sqlalchemy/sqlalchemy/releases/tag/rel_2_1_0b3 - Permalink: https://whatsnew.fyi/product/sqlalchemy/releases/rel-2-1-0b3 - Labels: Pre-release - **added** — Add selectinload.chunksize parameter to selectinload() allowing users to configure the number of primary keys sent per IN clause when loading relationships - **changed** — Honor populate_existing execution option when passed in Session.get.execution_options dict, with Session.get.populate_existing parameter taking precedence if specified - **changed** — Update _orm.ORMExecuteState.user_defined_options to include options added to the statement before calling Select.with_only_columns() or _orm.Query.with_entities() - **changed** — Optimize _orm.selectinload() to skip the .unique() call on inner result sets when no nested _orm.joinedload() on a collection is present - **changed** — Make Session level _orm.Session.execution_options take effect for Core level SQL emitted by unit of work operations - **changed** — Process ORM result rows as plain tuples rather than constructing Row objects for improved performance - **changed** — Improve performance of _orm.selectinload() and _orm.subqueryload() result handling by selecting primary key columns directly and converting rows to plain tuples - **changed** — Enable omit_join optimization for many-to-many non-self-referential relationships in selectinload() loader strategy - **fixed** — Fix issue where declarative class registry would not consider class-level MetaData objects set on abstract mixin classes when resolving string-based table references in relationship() configurations - **fixed** — Fix issue where _engine.Result.unique() filter was not properly validated against _engine.Result.yield_per() method - **fixed** — Emit warning when a Declarative attribute name is named metadata or registry #### 2.1.0b3 Released: June 27, 2026 ##### orm - **[orm] [feature]** Added `selectinload.chunksize` parameter to `selectinload()` allowing users to configure the number of primary keys sent per IN clause when loading relationships. Pull request courtesy bekapono. References: [#11450](https://www.sqlalchemy.org/trac/ticket/11450) - **[orm] [usecase]** The `populate_existing` execution option is now honored when passed in the `Session.get.execution_options` dict by the method `Session.get()` and analogous in other session kinds. The current `Session.get.populate_existing` parameter will takes precedence if specified, overriding the value of the execution options. References: [#10610](https://www.sqlalchemy.org/trac/ticket/10610) - **[orm] [usecase]** Updated the attribute `_orm.ORMExecuteState.user_defined_options` to include options that were added to the statement before calling `Select.with_only_columns()` or `_orm.Query.with_entities()`. References: [#13309](https://www.sqlalchemy.org/trac/ticket/13309) - **[orm] [usecase] [performance]** Optimized `_orm.selectinload()` to skip the `.unique()` call on inner result sets when no nested `_orm.joinedload()` on a collection is present. The uniquing pass is only required when a joined eager load inflates rows due to a one-to-many or many-to-many JOIN; in the common case of a leaf selectin load, rows are already unique by construction and the per-row hashing overhead can be avoided. As a side effect, `yield_per` set in a `do_orm_execute` event for a `_orm.selectinload()` relationship load no longer raises `InvalidRequestError` when no nested collection joinedload is in effect, since `.unique()` is no longer called in that path. Pull request courtesy Oliver Parker. References: [#13339](https://www.sqlalchemy.org/trac/ticket/13339) - **[orm] [usecase]** Session level `_orm.Session.execution_options` now take effect for Core level SQL emitted by unit of work operations, in addition to their existing use within ORM statement executions. This is to provide for Core options such as `_engine.Connection.execution_options.schema_translate_map` to be applicable to a `Session` overall. References: [#13346](https://www.sqlalchemy.org/trac/ticket/13346) - **[orm] [performance]** ORM result row fetching now processes rows as plain tuples rather than constructing `Row` objects, as ORM loaders use position-based access and do not require the `Row` interface. `Row` construction is still used when engine-level debug logging is enabled so that individual rows can be logged. Benchmarks show a 3-16% improvement in ORM entity load times depending on query shape. Pull request courtesy Oliver Parker. References: [#13363](https://www.sqlalchemy.org/trac/ticket/13363) - **[orm] [performance]** Improved performance of `_orm.selectinload()` and `_orm.subqueryload()` result handling: - in selectinloader, the primary key columns used to correlate related rows are now selected directly rather than being wrapped in a `Bundle`, and are read from positional slices of each result row. This removes the per-row `Row` construction that the `Bundle` introduced, including for the common single-column primary key case. - removed use of `groupby()` + `lambda` against `Row` objects in subqueryloader; rows are converted to plain tuples and the result lists are built via `append()`. - many-to-one selectinload reads foreign key values directly from the parent instance dictionary when present, falling back to attribute-level access only for expired or deferred attributes. Pull request courtesy Oliver Parker. References: [#13363](https://www.sqlalchemy.org/trac/ticket/ _[Truncated at 4000 characters — full notes: https://github.com/sqlalchemy/sqlalchemy/releases/tag/rel_2_1_0b3]_ ### rel_2_0_51 — 2.0.51 - Date: 2026-06-15 - Version: rel_2_0_51 - Original notes: https://github.com/sqlalchemy/sqlalchemy/releases/tag/rel_2_0_51 - Permalink: https://whatsnew.fyi/product/sqlalchemy/releases/rel-2-0-51 - **fixed** — Fixed issue where subqueryload() combined with PropComparator.of_type() and PropComparator.and_() would silently drop the additional filter criteria, causing all related objects to be loaded instead of only those matching the filter - **fixed** — Fixed bug where a failure during tpc_prepare() within Session.commit() for a two-phase session would raise IllegalStateChangeError instead of the original database exception - **fixed** — Fixed issue where Result.freeze() would lose track of ambiguous column names present in the original CursorResult, causing key-based access on the thawed result to silently return a value instead of raising InvalidRequestError - **fixed** — Fixed issue where StatementLambdaElement would proxy attribute access through the cached expected expression rather than the resolved expression, causing stale closure-bound parameter values to be used when a lambda statement was extended with non-lambda criteria - **fixed** — Fixed bug where two-phase transaction recovery would not return the correct transaction identifier when generating identifiers using the xid() method of the psycopg connection - **fixed** — Fixed regular expression in the pure Python hstore result processor which could hang on malformed hstore text containing unterminated quoted segments with backslashes #### 2.0.51 Released: June 15, 2026 ##### orm - **[orm] [bug]** Fixed issue where `_orm.subqueryload()` combined with `PropComparator.of_type()` and `PropComparator.and_()` would silently drop the additional filter criteria, causing all related objects to be loaded instead of only those matching the filter. The `LoaderCriteriaOption` was being constructed against the base entity rather than the effective entity indicated by `PropComparator.of_type()`. Pull request courtesy Arya Rizky. References: [#13207](https://www.sqlalchemy.org/trac/ticket/13207) - **[orm] [bug]** Fixed bug where a failure during `tpc_prepare()` within `_orm.Session.commit()` for a two-phase session would raise `IllegalStateChangeError` instead of the original database exception. The internal `_prepare_impl()` method's error handler was unable to invoke `_orm.SessionTransaction.rollback()` due to a state-change guard, preventing proper cleanup and masking the underlying error. References: [#13356](https://www.sqlalchemy.org/trac/ticket/13356) ##### engine - **[engine] [bug]** Fixed issue where `Result.freeze()` would lose track of ambiguous column names present in the original `CursorResult`, causing key-based access on the thawed result to silently return a value instead of raising `InvalidRequestError`. The `SimpleResultMetaData` now accepts and propagates ambiguous key information so that frozen, thawed, and pickled results raise consistently for duplicate column names. Pull request courtesy Saurabh Kohli. References: [#9427](https://www.sqlalchemy.org/trac/ticket/9427) ##### sql - **[sql] [bug]** Fixed issue where `_sql.StatementLambdaElement` would proxy attribute access through the cached "expected" expression rather than the resolved expression, causing stale closure-bound parameter values to be used when a lambda statement was extended with non-lambda criteria such as an additional `.where()` clause. Courtesy cjc0013. References: [#10827](https://www.sqlalchemy.org/trac/ticket/10827) ##### postgresql - **[postgresql] [bug]** Repaired bug introduced in [#13229](https://www.sqlalchemy.org/trac/ticket/13229) where a two-phase transaction recovery would not return the correct transaction identifier when generating the identifiers using the `xid()` method of the psycopg connection. References: [#13355](https://www.sqlalchemy.org/trac/ticket/13355) - **[postgresql] [bug]** Fixed regular expression in the pure Python hstore result processor, used when `use_native_hstore=False` is set, which could hang on malformed hstore text containing unterminated quoted segments with backslashes. Pull request courtesy dxbjavid. References: [#13370](https://www.sqlalchemy.org/trac/ticket/13370) ### rel_2_0_50 — 2.0.50 - Date: 2026-05-24 - Version: rel_2_0_50 - Original notes: https://github.com/sqlalchemy/sqlalchemy/releases/tag/rel_2_0_50 - Permalink: https://whatsnew.fyi/product/sqlalchemy/releases/rel-2-0-50 - **fixed** — Fixed issue where using joinedload() with PropComparator.of_type() targeting a joined-table subclass combined with PropComparator.and_() referencing a column on that subclass would generate invalid SQL where the subclass column was not adapted to the subquery alias - **fixed** — Fixed issue where the presence of a SessionEvents.do_orm_execute() event hook would cause internal execution options such as yield_per and loader-specific state from the first orm_pre_session_exec pass to leak into the second pass, leading to errors when using relationship loaders such as selectinload() and immediateload() - **fixed** — Fixed issue where using with_polymorphic() on a leaf class or a non-inherited class would fail with an AttributeError when used in an ORM statement due to configure_mappers() not being triggered implicitly - **fixed** — Fixed issue where floor division (//) between a Float or Numeric numerator and an Integer denominator would omit the FLOOR() SQL wrapper on dialects where Dialect.div_is_floordiv is True, so that expressions such as float_col // int_col render as FLOOR(float_col / int_col) - **changed** — Improved handling of two phase transaction identifiers for PostgreSQL when the identifier is provided by the user, with the psycopg dialect updated to use the DBAPI two phase transaction API instead of executing SQL directly - **fixed** — Fixed issue where the asyncpg driver could throw an insufficiently-handled exception InternalClientError under some circumstances, leading to connections not being properly marked as invalidated - **fixed** — Fixed issue where the ExcludeConstraint construct did not correctly forward the ExcludeConstraint.info parameter to the superclass, causing user-defined metadata to be lost - **fixed** — Narrowed the scope of the internal workaround for MySQL bugs 88718 and 96365 so that it is only applied where needed: MySQL 8.0.1 through 8.0.13 where bug 88718 is present, and on systems with lower_case_table_names=2 where bug 96365 applies - **fixed** — Fixed issue in aiomysql and asyncmy dialects that appears as of using pymysql 1.2.0; the dialects were not properly taking into account logic that detects the argument signature of pymysql's ping() method - **fixed** — Escape key and pragma values when utilizing the pysqlcipher dialect #### 2.0.50 Released: May 24, 2026 ##### orm - **[orm] [bug]** Fixed issue where using `_orm.joinedload()` with `PropComparator.of_type()` targeting a joined-table subclass combined with `PropComparator.and_()` referencing a column on that subclass would generate invalid SQL, where the subclass column was not adapted to the subquery alias. Pull request courtesy Joaquin Hui Gomez. References: [#13203](https://www.sqlalchemy.org/trac/ticket/13203) - **[orm] [bug]** Fixed issue where the presence of a `SessionEvents.do_orm_execute()` event hook would cause internal execution options such as `yield_per` and loader-specific state from the first `orm_pre_session_exec` pass to leak into the second pass, leading to errors when using relationship loaders such as `selectinload()` and `immediateload()`. The execution options passed to the second compilation pass are now based on the original options plus only the explicit updates made via `ORMExecuteState.update_execution_options()` within the event hook. References: [#13301](https://www.sqlalchemy.org/trac/ticket/13301) - **[orm] [bug]** Fixed issue where using `_orm.with_polymorphic()` on a leaf class (a subclass with no further descendants) or a non-inherited class would fail with an `AttributeError` when used in an ORM statement, due to `_orm.configure_mappers()` not being triggered implicitly. The fix ensures that `AliasedInsp` participates in the `_post_inspect` hook, triggering mapper configuration during ORM statement compilation. References: [#13319](https://www.sqlalchemy.org/trac/ticket/13319) ##### sql - **[sql] [bug]** Fixed issue where floor division (`//`) between a `Float` or `Numeric` numerator and an `Integer` denominator would omit the `FLOOR()` SQL wrapper on dialects where `Dialect.div_is_floordiv` is `True` (the default, including PostgreSQL and SQLite). `FLOOR()` is now applied if either the denominator or the numerator is a non-integer, so that expressions such as `float_col // int_col` render as `FLOOR(float_col / int_col)` instead of the incorrect `float_col / int_col`. Pull request courtesy r266-tech. References: [#10528](https://www.sqlalchemy.org/trac/ticket/10528) ##### postgresql - **[postgresql] [bug]** Improve handling of two phase transaction identifiers for PostgreSQL when the identifier is provided by the user. As part of this change the psycopg dialect was updated to use the DBAPI two phase transaction API instead of executing the SQL directly. References: [#13229](https://www.sqlalchemy.org/trac/ticket/13229) - **[postgresql] [bug]** Fixed issue where the asyncpg driver could throw an insufficiently-handled exception `InternalClientError` under some circumstances, leading to connections not being properly marked as invalidated. References: [#13241](https://www.sqlalchemy.org/trac/ticket/13241) - **[postgresql] [bug]** Fixed issue where the `ExcludeConstraint` construct did not correctly forward the `ExcludeConstraint.info` parameter to the superclass, causing user-defined metadata to be lost. Pull request courtesy Wiktor Byrka. References: [#13317](https://www.sqlalchemy.org/trac/ticket/13317) ##### mysql - **[mysql] [bug] [reflection]** Narrowed the scope of the internal workaround for MySQL bugs [#88718](https://bugs.mysql.com/bug.php?id=88718) and [#96365](https://bugs.mysql.com/bug.php?id=96365) so that it is only applied where needed: MySQL 8.0.1 through 8.0.13 (where bug 88718 is present), and on systems with `lower_case_table_names=2` (where bug 96365 applies, typically macOS). Previously the workaround was applied unconditionally for all MySQL 8.0+ versions, which caused a `KeyError` during foreign key reflection when the database user lacked SELECT privileges on referred tables. References _[Truncated at 4000 characters — full notes: https://github.com/sqlalchemy/sqlalchemy/releases/tag/rel_2_0_50]_ ### rel_2_1_0b2 — 2.1.0b2 - Date: 2026-04-16 - Version: rel_2_1_0b2 - Original notes: https://github.com/sqlalchemy/sqlalchemy/releases/tag/rel_2_1_0b2 - Permalink: https://whatsnew.fyi/product/sqlalchemy/releases/rel-2-1-0b2 - Labels: Pre-release - **added** — The metadata, type_annotation_map, or registry can now be set up in a declarative base via a mixin class in addition to directly setting them on the subclass - **added** — Added new parameter exclude to over() and related methods, enabling SQL standard frame exclusion clauses EXCLUDE CURRENT ROW, EXCLUDE GROUP, EXCLUDE TIES, EXCLUDE NO OTHERS in window functions - **changed** — ColumnCollection class hierarchy has been refactored with mutation operations moved to WriteableColumnCollection and DedupeColumnCollection subclasses, allowing column names such as add, remove, update, extend, and clear to be used without conflicts - **fixed** — A warning is now emitted when using the standalone distinct() function in a select() columns list outside of an aggregate function - **fixed** — Improved the ability for TypeDecorator to produce a correct repr() for schema types such as Enum and Boolean - **added** — Most FromClause subclasses are now generic on TypedColumns subclasses, that can be used to type their c collection - **fixed** — Amended the repr() output for Enum so that the MetaData is not shown in the output - **fixed** — Fixed issue in PEP 646 support for result sets where scalar methods including Connection.scalar(), Result.scalar(), and Session.scalar() were not applying the correct type to the scalar result value when columns were typed as Any - **changed** — Improved typing of JSON as well as dialect specific variants like postgresql.JSON to include generic capabilities for parameterization - **added** — Added support for the mssql-python driver, Microsoft's official Python driver for SQL Server - **added** — Added support for the JSON datatype when using the Oracle database with the oracledb dialect #### 2.1.0b2 Released: April 16, 2026 ##### orm - **[orm] [usecase]** The `metadata`, `type_annotation_map`, or `registry` can now be set up in a declarative base also via a mixin class, not only by directly setting them on the subclass like before. The declarative class setup now uses `getattr()` to look for these attributes, instead of relying only on the class `__dict__`. References: [#13198](https://www.sqlalchemy.org/trac/ticket/13198) ##### sql - **[sql] [usecase]** Added new parameter `_sql.over.exclude` to `_sql.over()` and related methods, enabling SQL standard frame exclusion clauses `EXCLUDE CURRENT ROW`, `EXCLUDE GROUP`, `EXCLUDE TIES`, `EXCLUDE NO OTHERS` in window functions. Pull request courtesy of Varun Chawla. References: [#11671](https://www.sqlalchemy.org/trac/ticket/11671) - **[sql] [usecase]** The `ColumnCollection` class hierarchy has been refactored to allow column names such as `add`, `remove`, `update`, `extend`, and `clear` to be used without conflicts. `ColumnCollection` is now an abstract base class, with mutation operations moved to `WriteableColumnCollection` and `DedupeColumnCollection` subclasses. The `ReadOnlyColumnCollection` exposed as attributes such as `Table.c` no longer includes mutation methods that raised `NotImplementedError`, allowing these common column names to be accessed naturally, e.g. `table.c.add`, `table.c.remove`, `table.c.update`, etc. - **[sql] [bug]** A warning is emitted when using the standalone `_sql.distinct()` function in a `_sql.select()` columns list outside of an aggregate function; this function is not intended as a replacement for the use of `Select.distinct()`. Pull request courtesy bekapono. References: [#11526](https://www.sqlalchemy.org/trac/ticket/11526) - **[sql] [bug]** Improved the ability for `TypeDecorator` to produce a correct `repr()` for "schema" types such as `Enum` and `Boolean`. This is mostly to support the Alembic autogenerate use case so that custom types render with relevant arguments present. Improved the architecture used by `TypeEngine` to produce `repr()` strings to be more modular for compound types like `TypeDecorator`. References: [#13140](https://www.sqlalchemy.org/trac/ticket/13140) ##### schema - **[schema] [usecase]** Most `_sql.FromClause` subclasses are now generic on `_schema.TypedColumns` subclasses, that can be used to type their `_sql.FromClause.c` collection. This applied to `_schema.Table`, `_sql.Join`, `_sql.Subquery`, `_sql.CTE` and more. References: [#13085](https://www.sqlalchemy.org/trac/ticket/13085) - **[schema] [bug]** Amended the `repr()` output for `Enum` so that the `MetaData` is not shown in the output, as this interferes with Alembic-autogenerated forms of this type which should be inheriting the `MetaData` of the parent table in the migration script. References: [#10604](https://www.sqlalchemy.org/trac/ticket/10604) ##### typing - **[typing] [bug]** Fixed issue in new [PEP 646](https://peps.python.org/pep-0646) support for result sets where an issue in the mypy type checker prevented "scalar" methods including `Connection.scalar()`, `Result.scalar()`, `_orm.Session.scalar()`, as well as async versions of these methods from applying the correct type to the scalar result value, when the columns in the originating `_sql.select()` were typed as `Any`. Pull request courtesy Yurii Karabas. References: [#13091](https://www.sqlalchemy.org/trac/ticket/13091) - **[typing] [bug]** Improved typing of `_sqltypes.JSON` as well as dialect specific variants like `_postgresql.JSON` to include generic capabilities, so that the types may be parameterized to indicate any specific type of contents expected, e.g. `JSONB[list[str]]()`. References: [#13131](https://www.s _[Truncated at 4000 characters — full notes: https://github.com/sqlalchemy/sqlalchemy/releases/tag/rel_2_1_0b2]_ ### rel_2_0_49 — 2.0.49 - Date: 2026-04-03 - Version: rel_2_0_49 - Original notes: https://github.com/sqlalchemy/sqlalchemy/releases/tag/rel_2_0_49 - Permalink: https://whatsnew.fyi/product/sqlalchemy/releases/rel-2-0-49 - **fixed** — Fixed issue where Session.get() would bypass the identity map and emit unnecessary SQL when with_for_update=False was passed, rather than treating it equivalently to the default of None - **fixed** — Fixed issue where chained joinedload() options would not be applied correctly when the final relationship in the chain is declared on a base mapper and accessed through a subclass mapper in a with_polymorphic() query - **fixed** — Fixed issue where using Load.options() to apply a chained loader option such as joinedload() or selectinload() with PropComparator.of_type() for a polymorphic relationship would not generate the necessary clauses for the polymorphic subclasses - **fixed** — Fixed issue where using chained loader options such as selectinload() after joinedload() with PropComparator.of_type() for a polymorphic relationship would not properly apply the chained loader option - **fixed** — Fixed a typing issue where the typed members of func would return the appropriate class of the same name but created an issue for typecheckers by adding differently-named type aliases for these return types - **fixed** — Fixed regular expression used when reflecting foreign keys in PostgreSQL to support escaped quotes in table names - **changed** — Enhanced the aioodbc dialect to expose the fast_executemany attribute of the pyodbc cursor to allow the fast_executemany parameter to work with the mssql+aioodbc dialect - **removed** — Remove warning for SQL Server dialect when a new version is detected - **fixed** — Fixed regression from version 2.0.42 where the updated column reflection query would receive SQL Server type alias names for special types such as sysname, leading to warnings and resulting in NullType - **fixed** — Fixed issue in Oracle dialect where the RAW datatype would not reflect the length parameter #### 2.0.49 Released: April 3, 2026 ##### orm - **[orm] [bug]** Fixed issue where `_orm.Session.get()` would bypass the identity map and emit unnecessary SQL when `with_for_update=False` was passed, rather than treating it equivalently to the default of `None`. Pull request courtesy of Joshua Swanson. References: [#13176](https://www.sqlalchemy.org/trac/ticket/13176) - **[orm] [bug]** Fixed issue where chained `_orm.joinedload()` options would not be applied correctly when the final relationship in the chain is declared on a base mapper and accessed through a subclass mapper in a `_orm.with_polymorphic()` query. The path registry now correctly computes the natural path when a property declared on a base class is accessed through a path containing a subclass mapper, ensuring the loader option can be located during query compilation. References: [#13193](https://www.sqlalchemy.org/trac/ticket/13193) - **[orm] [bug] [inheritance]** Fixed issue where using `_orm.Load.options()` to apply a chained loader option such as `_orm.joinedload()` or `_orm.selectinload()` with `_orm.PropComparator.of_type()` for a polymorphic relationship would not generate the necessary clauses for the polymorphic subclasses. The polymorphic loading strategy is now correctly propagated when using a call such as `joinedload(A.b).options(joinedload(B.c.of_type(poly)))` to match the behavior of direct chaining e.g. `joinedload(A.b).joinedload(B.c.of_type(poly))`. References: [#13202](https://www.sqlalchemy.org/trac/ticket/13202) - **[orm] [bug] [inheritance]** Fixed issue where using chained loader options such as `_orm.selectinload()` after `_orm.joinedload()` with `_orm.PropComparator.of_type()` for a polymorphic relationship would not properly apply the chained loader option. The loader option is now correctly applied when using a call such as `joinedload(A.b.of_type(poly)).selectinload(poly.SubClass.c)` to eagerly load related objects. References: [#13209](https://www.sqlalchemy.org/trac/ticket/13209) ##### typing - **[typing] [bug]** Fixed a typing issue where the typed members of :data:`.func` would return the appropriate class of the same name, however this creates an issue for typecheckers such as Zuban and pyrefly that assume [PEP 749](https://peps.python.org/pep-0749) style typechecking even if the file states that it's a [PEP 563](https://peps.python.org/pep-0563) file; they see the returned name as indicating the method object and not the class object. These typecheckers are actually following along with an upcoming test harness that insists on [PEP 749](https://peps.python.org/pep-0749) style name resolution for this case unconditionally. Since [PEP 749](https://peps.python.org/pep-0749) is the way of the future regardless, differently-named type aliases have been added for these return types. Unknown interpreted text role "data". References: [#13167](https://www.sqlalchemy.org/trac/ticket/13167) ##### postgresql - **[postgresql] [bug]** Fixed regular expression used when reflecting foreign keys in PostgreSQL to support escaped quotes in table names. Pull request courtesy of Austin Graham References: [#10902](https://www.sqlalchemy.org/trac/ticket/10902) ##### mssql - **[mssql] [usecase]** Enhanced the `aioodbc` dialect to expose the `fast_executemany` attribute of the pyodbc cursor. This allows the `fast_executemany` parameter to work with the `mssql+aioodbc` dialect. Pull request courtesy Georg Sieber. References: [#13152](https://www.sqlalchemy.org/trac/ticket/13152) - **[mssql] [usecase]** Remove warning for SQL Server dialect when a new version is detected. The warning was originally added more than 15 years ago due to an unexpected value returned when using an old version of FreeTDS. _[Truncated at 4000 characters — full notes: https://github.com/sqlalchemy/sqlalchemy/releases/tag/rel_2_0_49]_ ### rel_2_0_48 — 2.0.48 - Date: 2026-03-02 - Version: rel_2_0_48 - Original notes: https://github.com/sqlalchemy/sqlalchemy/releases/tag/rel_2_0_48 - Permalink: https://whatsnew.fyi/product/sqlalchemy/releases/rel-2-0-48 - **fixed** — Fixed a critical issue in Engine where connections created with DialectEvents.do_connect() event listeners would receive shared, mutable collections for the connection arguments, leading to unlimited growth of the argument list and elements within the parameter dictionary being shared among concurrent connection calls #### 2.0.48 Released: March 2, 2026 ##### engine - **[engine] [bug]** Fixed a critical issue in `Engine` where connections created in conjunction with the `DialectEvents.do_connect()` event listeners would receive shared, mutable collections for the connection arguments, leading to a variety of potential issues including unlimited growth of the argument list as well as elements within the parameter dictionary being shared among concurrent connection calls. In particular this could impact do_connect routines making use of complex mutable authentication structures. References: [#13144](https://www.sqlalchemy.org/trac/ticket/13144) ### rel_2_0_47 — 2.0.47 - Date: 2026-02-24 - Version: rel_2_0_47 - Original notes: https://github.com/sqlalchemy/sqlalchemy/releases/tag/rel_2_0_47 - Permalink: https://whatsnew.fyi/product/sqlalchemy/releases/rel-2-0-47 - **fixed** — Fixed ORM mappings with Python 3.14's PEP 649 feature where introspection of the __init__ method would fail with non-present identifiers in annotations by amending the vendored getfullargspec() method to use Format.FORWARDREF - **added** — The connection object returned by Engine.raw_connection() now supports the context manager protocol, automatically returning the connection to the pool when exiting the context - **fixed** — Fixed PostgreSQL dialect foreign key constraint reflection that incorrectly swapped or failed to capture onupdate and ondelete values when these clauses appeared in different order in the constraint definition - **fixed** — Fixed issue in PostgreSQL's engine_insertmanyvalues feature where using ON CONFLICT clause with Insert.returning.sort_by_parameter_order enabled would generate invalid SQL with implicit sentinel primary keys - **fixed** — Fixed issue where Insert.on_conflict_do_update() parameters were not respecting compilation options such as literal_binds=True in PostgreSQL - **fixed** — Fixed issue where Insert.on_conflict_do_update() using parametrized bound parameters in the set_ clause would fail when used with executemany batching in PostgreSQL - **changed** — DDL compilation options are now registered under the actual dialect name instead of the hard-coded mysql name, with fallback support for options that do not exist for that dialect - **deprecated** — MariaDB dialect now emits deprecation warning when using mysql_with_parser or mysql_using options without specifying corresponding mariadb_ prefixed options - **fixed** — Fixed issue where Insert.on_conflict_do_update() parameters were not respecting compilation options such as literal_binds=True in SQLite - **fixed** — Fixed issue where Insert.on_conflict_do_update() using parametrized bound parameters in the set_ clause would fail when used with executemany batching in SQLite #### 2.0.47 Released: February 24, 2026 ##### orm - **[orm] [bug]** Fixed issue when using ORM mappings with Python 3.14's [PEP 649](https://peps.python.org/pep-0649) feature that no longer requires "future annotations", where the ORM's introspection of the `__init__` method of mapped classes would fail if non-present identifiers in annotations were present. The vendored `getfullargspec()` method has been amended to use `Format.FORWARDREF` under Python 3.14 to prevent resolution of names that aren't present. References: [#13104](https://www.sqlalchemy.org/trac/ticket/13104) ##### engine - **[engine] [usecase]** The connection object returned by `_engine.Engine.raw_connection()` now supports the context manager protocol, automatically returning the connection to the pool when exiting the context. References: [#13116](https://www.sqlalchemy.org/trac/ticket/13116) ##### postgresql - **[postgresql] [bug]** Fixed an issue in the PostgreSQL dialect where foreign key constraint reflection would incorrectly swap or fail to capture `onupdate` and `ondelete` values when these clauses appeared in a different order than expected in the constraint definition. This issue primarily affected PostgreSQL-compatible databases such as CockroachDB, which may return `ON DELETE` before `ON UPDATE` in the constraint definition string. The reflection logic now correctly parses both clauses regardless of their ordering. References: [#13105](https://www.sqlalchemy.org/trac/ticket/13105) - **[postgresql] [bug]** Fixed issue in the `engine_insertmanyvalues` feature where using PostgreSQL's `ON CONFLICT` clause with `_dml.Insert.returning.sort_by_parameter_order` enabled would generate invalid SQL when the insert used an implicit sentinel (server-side autoincrement primary key). The generated SQL would incorrectly declare a sentinel counter column in the `imp_sen` table alias without providing corresponding values in the `VALUES` clause, leading to a `ProgrammingError` indicating column count mismatch. The fix allows batch execution mode when `embed_values_counter` is active, as the embedded counter provides the ordering capability needed even with upsert behaviors, rather than unnecessarily downgrading to row-at-a-time execution. References: [#13107](https://www.sqlalchemy.org/trac/ticket/13107) - **[postgresql] [bug]** Fixed issue where `_postgresql.Insert.on_conflict_do_update()` parameters were not respecting compilation options such as `literal_binds=True`. Pull request courtesy Loïc Simon. References: [#13110](https://www.sqlalchemy.org/trac/ticket/13110) - **[postgresql] [bug]** Fixed issue where `_postgresql.Insert.on_conflict_do_update()` using parametrized bound parameters in the `set_` clause would fail when used with executemany batching. For dialects that use the `use_insertmanyvalues_wo_returning` optimization (psycopg2), insertmanyvalues is now disabled when there is an ON CONFLICT clause. For cases with RETURNING, row-at-a-time mode is used when the SET clause contains parametrized bindparams (bindparams that receive values from the parameters dict), ensuring each row's parameters are correctly applied. ON CONFLICT statements using expressions like `excluded.` continue to batch normally. References: [#13130](https://www.sqlalchemy.org/trac/ticket/13130) ##### mysql - **[mysql] [bug]** Fixed issue where DDL compilation options were registered to the hard-coded dialect name `mysql`. This made it awkward for MySQL-derived dialects like MariaDB, StarRocks, etc. to work with such options when different sets of options exist for different platforms. Options are now registered under the actual dialect name, and a fallback was added to help avoid errors when an option does not exist for that dialect. _[Truncated at 4000 characters — full notes: https://github.com/sqlalchemy/sqlalchemy/releases/tag/rel_2_0_47]_ ### rel_2_1_0b1 — 2.1.0b1 - Date: 2026-01-21 - Version: rel_2_1_0b1 - Original notes: https://github.com/sqlalchemy/sqlalchemy/releases/tag/rel_2_1_0b1 - Permalink: https://whatsnew.fyi/product/sqlalchemy/releases/rel-2-1-0b1 - Labels: Pre-release - **added** — Free-threaded Python versions are now supported in wheels released on PyPI - **changed** — The greenlet dependency used for asyncio support no longer installs by default; use the sqlalchemy[asyncio] install target to include this dependency - **changed** — Updated the setup manifest definition to use PEP 621-compliant pyproject.toml - **changed** — Python 3.10 or above is now required; support for Python 3.9, 3.8 and 3.7 is dropped - **added** — The back_populates argument to relationship() may now be passed as a Python callable - **added** — Added new hybrid method hybrid_property.bulk_dml() which works similar to hybrid_property.update_expression() for bulk ORM operations - **added** — Added new parameter composite.return_none_on to composite() which allows control over if and when the composite attribute should resolve to None - **added** — Added support for per-session execution options that are merged into all queries executed within that session - **changed** — Session autoflush behavior has been simplified to unconditionally flush the session each time an execution takes place - **added** — Added RegistryEvents event class that allows event listeners to be established on a registry object - **deprecated** — The Session.flush.objects parameter is now deprecated - **added** — Added the utility method Session.merge_all() and Session.delete_all() that operate on a collection #### 2.1.0b1 Released: January 21, 2026 ##### platform - **[platform] [feature]** Free-threaded Python versions are now supported in wheels released on Pypi. This integrates with overall free-threaded support added as part of [#12881](https://www.sqlalchemy.org/trac/ticket/12881) for the 2.0 and 2.1 series, which includes new test suites as well as a few improvements to race conditions observed under freethreading. References: [#12881](https://www.sqlalchemy.org/trac/ticket/12881) - **[platform] [change]** The `greenlet` dependency used for asyncio support no longer installs by default. This dependency does not publish wheel files for every architecture and is not needed for applications that aren't using asyncio features. Use the `sqlalchemy[asyncio]` install target to include this dependency. References: [#10197](https://www.sqlalchemy.org/trac/ticket/10197) - **[platform] [change]** Updated the setup manifest definition to use PEP 621-compliant pyproject.toml. Also updated the extra install dependency to comply with PEP-685. Thanks for the help of Matt Oberle and KOLANICH on this change. - **[platform] [change]** Python 3.10 or above is now required; support for Python 3.9, 3.8 and 3.7 is dropped as these versions are EOL. References: [#10357](https://www.sqlalchemy.org/trac/ticket/10357), [#12029](https://www.sqlalchemy.org/trac/ticket/12029), [#12819](https://www.sqlalchemy.org/trac/ticket/12819) ##### orm - **[orm] [feature]** The `_orm.relationship.back_populates` argument to `_orm.relationship()` may now be passed as a Python callable, which resolves to either the direct linked ORM attribute, or a string value as before. ORM attributes are also accepted directly by `_orm.relationship.back_populates`. This change allows type checkers and IDEs to confirm the argument for `_orm.relationship.back_populates` is valid. Thanks to Priyanshu Parikh for the help on suggesting and helping to implement this feature. References: [#10050](https://www.sqlalchemy.org/trac/ticket/10050) - **[orm] [feature]** Added new hybrid method `hybrid_property.bulk_dml()` which works in a similar way as `hybrid_property.update_expression()` for bulk ORM operations. A user-defined class method can now populate a bulk insert mapping dictionary using the desired hybrid mechanics. New documentation is added showing how both of these methods can be used including in combination with the new `_sql.from_dml_column()` construct. References: [#12496](https://www.sqlalchemy.org/trac/ticket/12496) - **[orm] [feature]** Added new parameter `_orm.composite.return_none_on` to `_orm.composite()`, which allows control over if and when this composite attribute should resolve to `None` when queried or retrieved from the object directly. By default, a composite object is always present on the attribute, including for a pending object which is a behavioral change since 2.0. When `_orm.composite.return_none_on` is specified, a callable is passed that returns True or False to indicate if the given arguments indicate the composite should be returned as None. This parameter may also be set automatically when ORM Annotated Declarative is used; if the annotation is given as `Mapped[SomeClass|None]`, a `_orm.composite.return_none_on` rule is applied that will return `None` if all contained columns are themselves `None`. References: [#12570](https://www.sqlalchemy.org/trac/ticket/12570) - **[orm] [feature]** Added support for per-session execution options that are merged into all queries executed within that session. The `_orm.Session`, `_orm.sessionmaker`, `_orm.scoped_session`, `_ext.asyncio.AsyncSession`, and `_ext.asyncio.async_sessionmaker` constructors now accept an `_orm.Session.execution_options` parameter that will be appl _[Truncated at 4000 characters — full notes: https://github.com/sqlalchemy/sqlalchemy/releases/tag/rel_2_1_0b1]_ ### rel_2_0_46 — 2.0.46 - Date: 2026-01-21 - Version: rel_2_0_46 - Original notes: https://github.com/sqlalchemy/sqlalchemy/releases/tag/rel_2_0_46 - Permalink: https://whatsnew.fyi/product/sqlalchemy/releases/rel-2-0-46 - **fixed** — Fixed typing issues where ORM mapped classes and aliased entities could not be used as keys in result row mappings or as join targets in select statements - **fixed** — Fixed issue where PostgreSQL JSONB operators path_match() and path_exists() were applying incorrect VARCHAR casts to the right-hand side operand when used with newer PostgreSQL drivers - **fixed** — Fixed regression in PostgreSQL dialect where JSONB subscription syntax would generate incorrect SQL for cast() expressions returning JSONB by properly wrapping cast expressions in parentheses - **fixed** — Improved the foreign key reflection regular expression pattern used by the PostgreSQL dialect to correctly handle unicode characters in table and column names - **fixed** — Fixed the SQL compilation for the mariadb sequence NOCYCLE keyword when the Sequence.cycle parameter is set to False - **fixed** — Fixed issue in the aiosqlite driver where SQLAlchemy's setting of aiosqlite's worker thread to daemon stopped working due to architecture changes in aiosqlite version 0.22.0 - **added** — Added support for the IF EXISTS clause when dropping indexes on SQL Server 2016 and later versions #### 2.0.46 Released: January 21, 2026 ##### typing - **[typing] [bug]** Fixed typing issues where ORM mapped classes and aliased entities could not be used as keys in result row mappings or as join targets in select statements. Patterns such as `row._mapping[User]`, `row._mapping[aliased(User)]`, `row._mapping[with_polymorphic(...)]` (rejected by both mypy and Pylance), and `.join(aliased(User))` (rejected by Pylance) are documented and fully supported at runtime but were previously rejected by type checkers. The type definitions for `_KeyType` and `_FromClauseArgument` have been updated to accept these ORM entity types. References: [#13075](https://www.sqlalchemy.org/trac/ticket/13075) ##### postgresql - **[postgresql] [bug]** Fixed issue where PostgreSQL JSONB operators `_postgresql.JSONB.Comparator.path_match()` and `_postgresql.JSONB.Comparator.path_exists()` were applying incorrect `VARCHAR` casts to the right-hand side operand when used with newer PostgreSQL drivers such as psycopg. The operators now indicate the right-hand type as `JSONPATH`, which currently results in no casting taking place, but is also compatible with explicit casts if the implementation were require it at a later point. References: [#13059](https://www.sqlalchemy.org/trac/ticket/13059) - **[postgresql] [bug]** Fixed regression in PostgreSQL dialect where JSONB subscription syntax would generate incorrect SQL for `cast()` expressions returning JSONB, causing syntax errors. The dialect now properly wraps cast expressions in parentheses when using the `[]` subscription syntax, generating `(CAST(...))[index]` instead of `CAST(...)[index]` to comply with PostgreSQL syntax requirements. This extends the fix from [#12778](https://www.sqlalchemy.org/trac/ticket/12778) which addressed the same issue for function calls. References: [#13067](https://www.sqlalchemy.org/trac/ticket/13067) - **[postgresql] [bug]** Improved the foreign key reflection regular expression pattern used by the PostgreSQL dialect to be more permissive in matching identifier characters, allowing it to correctly handle unicode characters in table and column names. This change improves compatibility with PostgreSQL variants such as CockroachDB that may use different quoting patterns in combination with unicode characters in their identifiers. Pull request courtesy Gord Thompson. ##### mariadb - **[mariadb] [bug]** Fixed the SQL compilation for the mariadb sequence "NOCYCLE" keyword that is to be emitted when the `Sequence.cycle` parameter is set to False on a `Sequence`. Pull request courtesy Diego Dupin. References: [#13070](https://www.sqlalchemy.org/trac/ticket/13070) ##### sqlite - **[sqlite] [bug]** Fixed issue in the aiosqlite driver where SQLAlchemy's setting of aiosqlite's worker thread to "daemon" stopped working because the aiosqlite architecture moved the location of the worker thread in version 0.22.0. This "daemon" flag is necessary so that a program is able to exit if the SQLite connection itself was not explicitly closed, which is particularly likely with SQLAlchemy as it maintains SQLite connections in a connection pool. While it's perfectly fine to call `AsyncEngine.dispose()` before program exit, this is not historically or technically necessary for any driver of any known backend, since a primary feature of relational databases is durability. The change also implements support for "terminate" with aiosqlite when using version version 0.22.1 or greater, which implements a sync `.stop()` method. References: [#13039](https://www.sqlalchemy.org/trac/ticket/13039) ##### mssql - **[mssql] [usecase]** Added support for the `IF EXISTS` clause when dropping indexes on SQL Server 2016 (13.x) and later versions. The `DropIndex.if_exists` _[Truncated at 4000 characters — full notes: https://github.com/sqlalchemy/sqlalchemy/releases/tag/rel_2_0_46]_ ### rel_2_0_45 — 2.0.45 - Date: 2025-12-09 - Version: rel_2_0_45 - Original notes: https://github.com/sqlalchemy/sqlalchemy/releases/tag/rel_2_0_45 - Permalink: https://whatsnew.fyi/product/sqlalchemy/releases/rel-2-0-45 - **fixed** — Fixed issue where calling Mapper.add_property() within mapper event hooks would raise an AttributeError because the mapper's internal property collections were not yet initialized - **fixed** — Fixed issue in Python 3.14 where dataclass transformation would fail when a mapped class using MappedAsDataclass included a relationship() referencing a class not available at runtime with PEP 649 deferred annotations - **fixed** — Fixed the short_selects performance example where the cache was being used in all examples, making performance comparison impossible - **fixed** — Fixed issue where using the ColumnOperators.in_() operator with a nested CompoundSelect statement would raise a NotImplementedError - **fixed** — Fixed typing issue where Select.with_for_update() would not support lists of ORM entities or other FROM clauses in the of parameter - **fixed** — Fixed typing issue where coalesce would not return the correct return type when a nullable form of an argument were passed - **added** — Added support for reflection of collation in types for PostgreSQL - **fixed** — Fixed issue where PostgreSQL dialect options such as postgresql_include on PrimaryKeyConstraint and UniqueConstraint were rendered in the wrong position when combined with constraint deferrability options - **fixed** — Fixed the structure of the SQL string used for the engine_insertmanyvalues feature when an explicit sequence with nextval() is used in PostgreSQL - **added** — Added support for MySQL 8.0.1 + FOR SHARE to be emitted for the Select.with_for_update() method - **fixed** — Improved reflection of CHECK constraints on SQLite to correctly handle table names containing CHECK or CONSTRAINT, support all four SQLite identifier quoting styles, and accurately parse CHECK constraint expressions containing parentheses within string literals #### 2.0.45 Released: December 9, 2025 ##### orm - **[orm] [bug]** Fixed issue where calling `Mapper.add_property()` within mapper event hooks such as `MapperEvents.instrument_class()`, `MapperEvents.after_mapper_constructed()`, or `MapperEvents.before_mapper_configured()` would raise an `AttributeError` because the mapper's internal property collections were not yet initialized. The `Mapper.add_property()` method now handles early-stage property additions correctly, allowing properties including column properties, deferred columns, and relationships to be added during mapper initialization events. Pull request courtesy G Allajmi. References: [#12858](https://www.sqlalchemy.org/trac/ticket/12858) - **[orm] [bug]** Fixed issue in Python 3.14 where dataclass transformation would fail when a mapped class using `MappedAsDataclass` included a `relationship()` referencing a class that was not available at runtime (e.g., within a `TYPE_CHECKING` block). This occurred when using Python 3.14's [PEP 649](https://peps.python.org/pep-0649) deferred annotations feature, which is the default behavior without a `from __future__ import annotations` directive. References: [#12952](https://www.sqlalchemy.org/trac/ticket/12952) ##### examples - **[examples] [bug]** Fixed the "short_selects" performance example where the cache was being used in all the examples, making it impossible to compare performance with and without the cache. Less important comparisons like "lambdas" and "baked queries" have been removed. ##### sql - **[sql] [bug]** Some improvements to the `_sql.ClauseElement.params()` method to replace bound parameters in a query were made, however the ultimate issue in [#12915](https://www.sqlalchemy.org/trac/ticket/12915) involving ORM `_orm.aliased()` cannot be fixed fully until 2.1, where the method is being rewritten to work without relying on Core cloned traversal. References: [#12915](https://www.sqlalchemy.org/trac/ticket/12915) - **[sql] [bug]** Fixed issue where using the `ColumnOperators.in_()` operator with a nested `CompoundSelect` statement (e.g. an `INTERSECT` of `UNION` queries) would raise a `NotImplementedError` when the nested compound select was the first argument to the outer compound select. The `_scalar_type()` internal method now properly handles nested compound selects. References: [#12987](https://www.sqlalchemy.org/trac/ticket/12987) ##### typing - **[typing] [bug]** Fixed typing issue where `Select.with_for_update()` would not support lists of ORM entities or other FROM clauses in the `Select.with_for_update.of` parameter. Pull request courtesy Shamil. References: [#12730](https://www.sqlalchemy.org/trac/ticket/12730) - **[typing] [bug]** Fixed typing issue where `coalesce` would not return the correct return type when a nullable form of that argument were passed, even though this function is meant to select the non-null entry among possibly null arguments. Pull request courtesy Yannick PÉROUX. ##### postgresql - **[postgresql] [usecase]** Added support for reflection of collation in types for PostgreSQL. The `collation` will be set only if different from the default one for the type. Pull request courtesy Denis Laxalde. References: [#6511](https://www.sqlalchemy.org/trac/ticket/6511) - **[postgresql] [bug]** Fixed issue where PostgreSQL dialect options such as `postgresql_include` on `PrimaryKeyConstraint` and `UniqueConstraint` were rendered in the wrong position when combined with constraint deferrability options like `deferrable=True`. Pull request courtesy G Allajmi. References: [#12867](https://www.sqlalchemy.org/trac/ticket/12867) - **[postgresql] [bug]** Fixed t _[Truncated at 4000 characters — full notes: https://github.com/sqlalchemy/sqlalchemy/releases/tag/rel_2_0_45]_