SQLAlchemy rel_2_1_0b3

rel_2_1_0b3Pre-release

2.1.0b3

Added 1
  • Add selectinload.chunksize parameter to selectinload() allowing users to configure the number of primary keys sent per IN clause when loading relationships
Changed 7
  • Honor populate_existing execution option when passed in Session.get.execution_options dict, with Session.get.populate_existing parameter taking precedence if specified
  • Update _orm.ORMExecuteState.user_defined_options to include options added to the statement before calling Select.with_only_columns() or _orm.Query.with_entities()
  • Optimize _orm.selectinload() to skip the .unique() call on inner result sets when no nested _orm.joinedload() on a collection is present
  • Make Session level _orm.Session.execution_options take effect for Core level SQL emitted by unit of work operations
  • Process ORM result rows as plain tuples rather than constructing Row objects for improved performance
  • Improve performance of _orm.selectinload() and _orm.subqueryload() result handling by selecting primary key columns directly and converting rows to plain tuples
  • Enable omit_join optimization for many-to-many non-self-referential relationships in selectinload() loader strategy
Fixed 3
  • 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
  • Fix issue where _engine.Result.unique() filter was not properly validated against _engine.Result.yield_per() method
  • 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

  • [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

  • [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

  • [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

  • [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

  • [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

  • [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

  • [orm] [performance] The selectinload() loader strategy now selects the omit_join optimization for many-to-many non-self-referential relationships, reducing the number of joins in the secondary SELECT by selecting from the secondary table directly rather than joining back to the parent entity. omit_join is enabled automatically when the join condition determines that the secondary table's foreign keys fully cover the parent's primary key. As always, omit_join can be disabled by setting relationship.omit_join to False. Pull request courtesy bekapono.

    References: #5987

  • [orm] [bug] Fixed issue where the declarative class registry would not consider class-level MetaData objects set on abstract mixin classes when resolving string-based table references in relationship() configurations. The registry now uses the same metadata resolution logic as table creation, first checking for a class-specific metadata attribute before falling back to registry.metadata.

    References: #13291

  • [orm] [bug] Fixed issue where the _engine.Result.unique() filter was not properly validated against the _engine.Result.yield_per() method when both were called as methods on the result object, such as result.unique().yield_per(N) or result.yield_per(N).unique(). The uniquing filter was previously only checked when yield_per was set via _engine.Connection.execution_options.yield_per. Since these two features are fundamentally incompatible for ORM results, an InvalidRequestError is now raised in all cases.

    References: #13293

  • [orm] [bug] A warning is now emitted when a Declarative attribute name is named metadata or registry. Previously, no warning was emitted for registry, and using the name metadata would raise an InvalidRequestError. Since these names can be used for attributes that are mapped as backrefs or using imperative mappings, usage under Declarative has been relaxed for metadata but also warns for both names as they may have unintended interactions with the Declarative reserved names.

    References: #13333

  • [orm] [bug] Fixed issue where the declarative class resolver would not consider the MetaData.schema default schema when resolving a string table name for the relationship.secondary parameter as well as within string-based relationship.primaryjoin and relationship.secondaryjoin expressions. The resolution now matches the behavior of ForeignKey, where an unqualified table name is implicitly resolved under the default schema. A deprecation warning is emitted when an unqualified name resolves to a :data:.BLANK_SCHEMA table in a MetaData that has a default schema set, as this implicit resolution will change in a future version.

    Unknown interpreted text role "data".

    References: #8068

engine
  • [engine] [bug] Expanded try/except error handling to encompass the _events.ConnectionEvents.before_cursor_execute() and _events.ConnectionEvents.after_cursor_execute() event hooks, so that exceptions raised within these hooks, including BaseException subclasses such as asyncio.CancelledError, are properly handled via the error handling path used for DBAPI errors. This ensures proper connection invalidation and pool notification when exit-type exceptions are raised in event hooks. As part of this change, DBAPI errors raised from within these event hooks will now be wrapped as SQLAlchemy exceptions.

    References: #13381

  • [engine] [reflection] Removed the legacy include_columns key from the dictionary returned by the index reflection methods of some dialects. This information is now part of the dialect_options dictionary under the key {dialect_name}_include, such as postgresql_include or mssql_include.

    References: #13350

sql
  • [sql] [usecase] Added _sql.Delete.using(), allowing explicit FROM expressions such as joins to be rendered in backend-specific multiple-table DELETE forms including MySQL/MariaDB DELETE .. USING. Pull request courtesy cjc0013.

    References: #8130

  • [sql] [bug] Fixed issue where negation of comparison expressions involving func.any(), func.all(), and func.some() SQL functions would incorrectly flip the comparison operator (e.g. = to !=) rather than wrapping the expression with NOT. These functions are now registered as collection aggregate functions that prevent operator flipping on negation, consistent with the behavior of the standalone _expression.any_() and _expression.all_() constructs.

    References: #13343

postgresql
  • [postgresql] [usecase] Changed the default backslash escape value in the PostgreSQL dialect to False to align it with the default value of standard_conforming_strings=on. This change should not affect most users since the value is set at driver initialization on first connect.

    References: #13268

mysql
  • [mysql] [bug] Improved the regular expression used to parse index COMMENT clauses in MySQL SHOW CREATE TABLE reflection to use an unambiguous single-quoted-string pattern; the previous pattern was theoretically subject to backtracking on malformed input, though such input is not producible by MySQL itself. Fix courtesy of Javid Khan.

    References: #13393

sqlite
  • [sqlite] [feature] Added _sqlite.JSONB type for SQLite's binary JSON storage format, available as of SQLite version 3.45.0. Values are stored via the jsonb() SQL function and retrieved via json(), while the Python-side behavior remains identical to _sqlite.JSON. Pull request courtesy Shamil Abdulaev.

    References: #13260

mssql
  • [mssql] [performance] [reflection] Implemented native multi-table reflection methods for the SQL Server dialect, providing MSDialect.get_multi_columns(), MSDialect.get_multi_pk_constraint(), MSDialect.get_multi_foreign_keys(), MSDialect.get_multi_indexes() and MSDialect.get_multi_table_comment(). Previously the SQL Server dialect relied on the default dialect default implementation which calls the per-table methods in a loop; the new implementations issue a single bulk query per object type against the sys.* catalog views, avoiding the per-table round trips. The single-table reflection methods are now thin wrappers over the multi-table ones, matching the pattern used by the PostgreSQL and Oracle dialects. Pull request courtesy Gaurav Sharma.

    References: #8430

View original

Upgraded? How did it go?

Discussion