Gaurav Sharma [Wed, 3 Jun 2026 12:52:09 +0000 (08:52 -0400)]
Implement native multi-table reflection API for the mssql dialect
### Description
Adds 5 native `get_multi_*` reflection methods (columns, pk, fk, indexes, table_comment) for the MSSQL dialect, replacing the per-table loop in `_default_multi_reflect`. Single-table methods now delegate to the multi versions (PG/Oracle pattern); legacy per-table SQL is retained as `_internal_get_*` helpers, used only for tempdb reflection.
Not implemented here: `get_multi_unique_constraints`, `get_multi_check_constraints`, `get_multi_table_options` -MSSQL has no single-table counterparts to delegate from. Happy to add as a follow-up.
### Performance
Measured with `test/perf/many_table_reflection.py` against SQL Server 2022 (Docker, localhost, pyodbc + ODBC Driver 18) on a 250-table fixture, 15-50 cols, with PKs/FKs/indexes/comments:
Mike Bayer [Thu, 4 Jun 2026 14:17:06 +0000 (10:17 -0400)]
Register func.any(), func.all(), func.some() as collection aggregates
Added CollectionAggregateFunction base class that sets
_is_collection_aggregate = True, and registered any_, all_, some_
as subclasses so that func.any(), func.all(), and func.some() correctly
prevent operator flipping on negation. Previously ~(col == func.any(arr))
would incorrectly compile to col != any(arr) instead of
NOT (col = any(arr)), which has different semantics for collection
aggregate comparison modifiers.
Also extended the _construct_for_op guard to check both left and right
operands for _is_collection_aggregate, since func.any(arr) can appear
on either side of a comparison unlike the standalone any_() construct
which auto-reverses operands.
Oliver Parker [Wed, 3 Jun 2026 06:51:13 +0000 (02:51 -0400)]
Perf/conditional unique selectinload
Optimized :func:`_orm.selectinload` to skip the ``.unique()`` call on inner
result sets when no nested :func:`_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 :func:`_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.
`_SelectInLoader._load_via_parent` and `_load_via_child` currently call
`.unique()` unconditionally on the inner `Result`. The uniqueness pass is only
required when a nested `joinedload` on a collection is in effect — in that case
the `JOIN` inflates rows (one row per `(child, grandchild)` instead of one per
child) and the outer `groupby` would produce duplicated entries without dedup.
`loading.instances()` already signals exactly this condition: it sets
`Result._unique_filter_state` to a `require_unique` guard when the inner
compile state has `multi_row_eager_loaders=True`. When that flag is unset (no
nested collection `joinedload`), `_unique_filter_state` stays `None`, meaning
the inner query produces unique rows by construction:
- `omit_join` 1:N — one row per child
- `omit_join` M2O — one row per parent
- `omit_join` M2M — one row per (parent, entity)
- `load_with_join` (non-omit) — one row per (parent, child)
This PR makes the `.unique()` call conditional, via a new `_has_unique_filter`
property on `Result` that exposes this state without reaching into the private
`_unique_filter_state` attribute directly:
```python
if result._has_unique_filter:
result = result.unique()
```
Per-row hashing in `_iterator_getter` is avoided in the common leaf-load case.
On an in-memory SQLite bench (2000 parents × 5 children + M:N tags, Python
3.14, n=1000 iterations):
| Workload | before avg | after avg | delta | before sd | after sd |
|---|---|---|---|---|---|
| selectin children (1:N) | 56.0 ms | 41.4 ms | **−26%** | 5.2 ms | 1.9 ms |
| selectin tags M2M | 25.6 ms | 20.0 ms | **−22%** | 4.2 ms | 3.1 ms |
| joined children (1:N) | 42.9 ms | 37.6 ms | ~0% | — | — |
| plain parent load | 4.2 ms | 3.6 ms | ~0% | — | — |
**Behaviour change:** `yield_per` set in a `do_orm_execute` event for a
relationship load no longer raises
`InvalidRequestError("Can't use yield_per in conjunction with unique")` for
`selectinload` without a nested collection `joinedload` — because `.unique()`
is no longer called in that path. `immediateload` is unaffected (it still calls
`.unique()` unconditionally).
sebastianbreguel [Sun, 31 May 2026 20:12:19 +0000 (16:12 -0400)]
Hoist loop-invariant set intersection in _get_display_froms
Fixes #13336.
`SelectState._get_display_froms` recomputed a loop-invariant `_cloned_intersection(...)` once per FROM element in each of the three correlation comprehensions, making each branch O(N²) in the number of FROM elements. This hoists the call so it runs once, which is O(N).
`_cloned_intersection` / `_cloned_difference` are pure and return a set, and neither argument changes during the comprehension, so the result is identical. A function-level benchmark asserts `old == new` at every N (full numbers in #13336), and `test/sql/` plus the ORM compilation/query tests pass: 7442 passed, 359 skipped. Net -14 lines.
Per the issue discussion, no changelog entry is included.
### Checklist
This pull request is:
- [x] A short code fix
- Issue with a runnable demonstration: #13336
- Behavior-preserving (no logic change), so it is covered by the existing `test/sql/` and ORM compilation/query suites rather than adding new tests.
me-saurabhkohli [Fri, 29 May 2026 20:03:40 +0000 (16:03 -0400)]
Add ambiguous column support to SimpleResultMetaData
Fixed issue where :meth:`.Result.freeze` would lose track of ambiguous
column names present in the original :class:`.CursorResult`, causing
key-based access on the thawed result to silently return a value instead of
raising :class:`.InvalidRequestError`. The
:class:`.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.
Mike Bayer [Thu, 28 May 2026 14:25:39 +0000 (10:25 -0400)]
allow backref named 'metadata' to not break _metadata_for_cls
Fixed regression caused by :ticket:`8068` where a ``backref``
named ``'metadata'`` on a mapped class would cause an
``AssertionError`` when the class also used string-based
relationship references (e.g. ``secondary="some_table"``).
The ``_metadata_for_cls()`` helper now checks
``isinstance(meta, MetaData)`` as a condition rather than
asserting, falling back to ``registry.metadata`` when the
class attribute has been overwritten by a backref.
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.
cjc0013 [Mon, 25 May 2026 16:46:50 +0000 (12:46 -0400)]
Fix lambda statements with non-lambda criteria
Fixed issue where :class:`_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.
Arya Rizky [Tue, 12 May 2026 19:08:02 +0000 (15:08 -0400)]
Fix subqueryload losing .and_() criteria when combined with of_type()
Fixed issue where :func:`_orm.subqueryload` combined with
:meth:`.PropComparator.of_type` and :meth:`.PropComparator.and_` would
silently drop the additional filter criteria, causing all related objects
to be loaded instead of only those matching the filter. The
:class:`.LoaderCriteriaOption` was being constructed against the base
entity rather than the effective entity indicated by
:meth:`.PropComparator.of_type`. Pull request courtesy Arya Rizky.
bekapono [Mon, 18 May 2026 16:29:50 +0000 (12:29 -0400)]
omit_join optimization for selectinload on many-to-many relationships
The :func:`.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
:paramref:`.relationship.omit_join` to ``False``. Pull request courtesy
bekapono.
proto-atlas [Mon, 25 May 2026 19:14:40 +0000 (15:14 -0400)]
Fix Session bulk mappings typing for mapped classes
Fixes #9256.
This updates the annotations for Session.bulk_insert_mappings() and Session.bulk_update_mappings().
The docstrings and runtime behavior already allow either a mapped class or a Mapper object, but the previous annotations only accepted Mapper[Any].
This patch switches those arguments to the existing _EntityBindKey alias, which matches the inputs accepted by _class_to_mapper(): mapped classes and Mapper objects, but not AliasedClass or AliasedInsp.
I also updated the internal _bulk_save_mappings() annotation so the public methods and the private helper stay consistent. The scoped_session proxy output has been kept in sync with tools/generate_proxy_methods.py, and the generator check passes.
I added a typing regression test covering both mapped classes and Mapper objects for the two bulk mapping methods. I confirmed that the mapped-class cases fail with the old annotation and pass with this change.
I could not run the full typing suite locally because my local Python 3.12 environment does not include string.templatelib. I only skipped typed_queries.py; that file is expected to be covered by SQLAlchemy's Python 3.14 mypy CI job.
Mike Bayer [Fri, 22 May 2026 20:01:10 +0000 (16:01 -0400)]
dont produce side effects for do_orm_execute
Fixed issue where the presence of a :meth:`.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 :func:`.selectinload` and :func:`.immediateload`. The execution
options passed to the second compilation pass are now based on the original
options plus only the explicit updates made via
:meth:`.ORMExecuteState.update_execution_options` within the event hook.
Federico Caselli [Tue, 19 May 2026 21:39:49 +0000 (23:39 +0200)]
user_defined_options returns memoized options
Updated the attribute :attr:`_orm.ORMExecuteState.user_defined_options` to
include options that were added to the statement before calling
:meth:`.Select.with_only_columns` or :meth:`_orm.Query.with_entities`.
Mike Bayer [Wed, 20 May 2026 19:59:10 +0000 (15:59 -0400)]
implement _post_inspect for AliasedInsp
Fixed issue where using :func:`_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
:func:`_orm.configure_mappers` not being triggered implicitly. The fix
ensures that :class:`.AliasedInsp` participates in the ``_post_inspect``
hook, triggering mapper configuration during ORM statement compilation.
WiktorB2004 [Wed, 20 May 2026 20:05:41 +0000 (16:05 -0400)]
Fix ExcludeConstraint not forwarding info to parent constructor
Fixed issue where the :class:`.ExcludeConstraint` construct did not
correctly forward the :paramref:`.ExcludeConstraint.info` parameter to
the superclass, causing user-defined metadata to be lost. Pull request
courtesy Wiktor Byrka.
Fixed issue where using :func:`_orm.joinedload` with
:meth:`.PropComparator.of_type` targeting a joined-table subclass combined
with :meth:`.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.
OSS Contributor [Mon, 23 Mar 2026 14:44:40 +0000 (10:44 -0400)]
Fix floordiv (//) for float/numeric by int with div_is_floordiv dialects
Fixed issue where floor division (``//``) between a :class:`.Float` or
:class:`.Numeric` numerator and an :class:`.Integer` denominator would omit
the ``FLOOR()`` SQL wrapper on dialects where
:attr:`.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.
mattip [Wed, 20 May 2026 17:47:17 +0000 (13:47 -0400)]
Fix trivial PyPy failures
<!-- Provide a general summary of your proposed changes in the Title field above -->
### Description
Fixes: #13274
References: #9154
There were two relatively causes to some of the ~21 failures on PyPy:
- weakrefs may be deleted but the objects not finalized on PyPy. This manifests as `ref.obj() is None` I added a test for the `release()` case that also failed on CPython before the fix.
- a condition added in 2022 for missing sqllite3 behaviour is no longer necessary, and is now causing a failure
In order to run the changes in CI, I added PyPy to the PR CI run. Before merging I will revert that change. There are still a number of failures with PyPy around different error messages, different inspect.signatures and one sticky problem with the pure-python datetime.py that actually comes from CPython. I will continue to work on them, but they are not specific to sqlalchemy.
Note the CI run is ~6 minutes where the CPython ones are ~3 minutes. This is expected, since PyPy's JIT does not kick in on short tests, and the base compiler is about 2x slower.
### Checklist
<!-- go over following points. check them with an `x` if they do apply, (they turn into clickable checkboxes once the PR is submitted, so no need to do everything at once)
-->
This pull request is:
- [ ] A documentation / typographical / small typing error fix
- Good to go, no issue or tests are needed
- [x] A short code fix
- please include the issue number, and create an issue if none exists, which
must include a complete example of the issue. one line code fixes without an
issue and demonstration will not be accepted.
- Please include: `Fixes: #<issue number>` in the commit message
- please include tests. one line code fixes without tests will not be accepted.
- [ ] A new feature implementation
- please include the issue number, and create an issue if none exists, which must
include a complete example of how the feature would look.
- Please include: `Fixes: #<issue number>` in the commit message
- please include tests.
Karolina Surma [Tue, 19 May 2026 14:20:38 +0000 (10:20 -0400)]
Adjust TypeError message to Python 3.15
<!-- Provide a general summary of your proposed changes in the Title field above -->
### Description
The `.fromisoformat()` error message tested in `test_no_string()` changed in Python 3.15, this fixes the test.
See #13308 for the `rel_2_0` branch.
### Checklist
<!-- go over following points. check them with an `x` if they do apply, (they turn into clickable checkboxes once the PR is submitted, so no need to do everything at once)
-->
This pull request is:
- [ ] A documentation / typographical / small typing error fix
- Good to go, no issue or tests are needed
- [x] A short code fix (in a test, therefore I didn’t create an issue)
- please include the issue number, and create an issue if none exists, which
must include a complete example of the issue. one line code fixes without an
issue and demonstration will not be accepted.
- Please include: `Fixes: #<issue number>` in the commit message
- please include tests. one line code fixes without tests will not be accepted.
- [ ] A new feature implementation
- please include the issue number, and create an issue if none exists, which must
include a complete example of how the feature would look.
- Please include: `Fixes: #<issue number>` in the commit message
- please include tests.
Mike Bayer [Tue, 19 May 2026 13:40:01 +0000 (09:40 -0400)]
robustly handle reconnect param across all pymysql variants
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 which
was added as part of :ticket:`10492`.
We add a "does ping have reconnect" check for all three DBAPIs
individually. To suit asyncmy's use of cython we also needed to
adjust vendored getargspec() routines.
Mike Bayer [Thu, 30 Apr 2026 19:13:39 +0000 (15:13 -0400)]
resolve table names using MetaData.schema default in declarative registry
Also resolved class-level MetaData not being consulted by the
declarative class registry when resolving string-based table
references. The registry now uses the same metadata resolution
logic as table creation, checking for a class-specific ``metadata``
attribute before falling back to ``registry.metadata``. The
``_metadata_for_cls`` function was factored into ``orm/util.py``
for shared use by both ``decl_base.py`` and ``clsregistry.py``.
David Lord [Sun, 17 May 2026 20:09:16 +0000 (16:09 -0400)]
document postgresql_nulls_not_distinct
<!-- Provide a general summary of your proposed changes in the Title field above -->
### Description
<!-- Describe your changes in detail -->
https://github.com/sqlalchemy/sqlalchemy/issues/8240 and https://github.com/sqlalchemy/sqlalchemy/pull/9834 added support for `NULLS NOT DISTINCT` to the PostgreSQL dialect, but didn't add it to the docs (only the change log). This adds a section to the "Constraint Options" section of the PostgreSQL dialect docs.
### Checklist
<!-- go over following points. check them with an `x` if they do apply, (they turn into clickable checkboxes once the PR is submitted, so no need to do everything at once)
-->
This pull request is:
- [x] A documentation / typographical / small typing error fix
- Good to go, no issue or tests are needed
- [ ] A short code fix
- please include the issue number, and create an issue if none exists, which
must include a complete example of the issue. one line code fixes without an
issue and demonstration will not be accepted.
- Please include: `Fixes: #<issue number>` in the commit message
- please include tests. one line code fixes without tests will not be accepted.
- [ ] A new feature implementation
- please include the issue number, and create an issue if none exists, which must
include a complete example of how the feature would look.
- Please include: `Fixes: #<issue number>` in the commit message
- please include tests.
Mike Bayer [Tue, 12 May 2026 18:30:37 +0000 (14:30 -0400)]
Block Result.unique() with Result.yield_per() for ORM results
The unique() + yield_per combination was only blocked when yield_per
was set via execution_options(yield_per=N); calling these as methods
on the result (e.g. result.unique().yield_per(N)) bypassed the check
and silently produced incorrect results.
Restructured _unique_filters on SimpleResultMetaData to be a callable
_create_unique_filters that receives the Result, allowing it to check
the yield_per state regardless of how it was activated.
populate existing can be in exec option in session.get
The ``populate_existing`` execution option is now honored when passed
in the :paramref:`.Session.get.execution_options` dict by the method
:meth:`.Session.get` and analogous in other session kinds. The current
:paramref:`.Session.get.populate_existing` parameter will takes precedence
if specified, overriding the value of the execution options.
Add sqlite.JSONB type for binary JSON storage (SQLite >= 3.45.0)
Added :class:`_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 :class:`_sqlite.JSON`.
Pull request courtesy Shamil Abdulaev.
Fixes: #13260
Adds `sqlalchemy.dialects.sqlite.JSONB` — a new dialect-specific type
for SQLite's binary JSON storage format, introduced in SQLite 3.45.0.
The type:
- renders `JSONB` in DDL (`CREATE TABLE t (col JSONB)`)
- wraps bind values with `jsonb()` on write, storing data as a BLOB
- wraps column reads with `json()`, returning standard text JSON to Python
- reflects back from the database as `sqlite.JSONB`
- behaves identically to `sqlite.JSON` on the Python side
SQLite 3.45.0 introduced a native binary JSON format (JSONB) that is
more compact and faster to parse than text JSON. Users who want to opt
into this storage format had no way to do so via SQLAlchemy.
- `JSONB` inherits from `sqlite.JSON` and defines `__visit_name__ = "JSONB"`
so the DDL compiler dispatches to `visit_JSONB` instead of `visit_JSON`
- Added `JSONB: JSONB` to `colspecs` so the type is not remapped to
`_SQliteJson` via the `sqltypes.JSON` MRO entry
- `bind_expression` / `column_expression` apply `jsonb()` / `json()`
transparently; existing `result_processor` from `sqltypes.JSON` handles
deserialization without changes
- Added `sqlite_jsonb` requirement (checks for SQLite >= 3.45 and that
`jsonb()` is available, since it is a loadable extension)
`test/dialect/sqlite/test_types.py::JSONBTest` (8 tests):
- DDL renders `JSONB`
- Reflection returns `sqlite.JSONB`
- Round-trip read/write including `None`
- Sub-object extraction via `[]` indexing
- Compiled SQL shows `jsonb(?)` on INSERT and `json(col)` on SELECT
- `typeof(col)` returns `blob` confirming binary storage
Fixes: #10673: make declared_attr covariant
<!-- Provide a general summary of your proposed changes in the Title field above -->
### Description
<!-- Describe your changes in detail -->
I made declared_attr covariant as suggested in #10673. mypy didn't seem to complain. Added a regression test for the use case that was asked for. Unfortunately, it seems like using `Mapped[int | UUID]` directly in the Protocol won't work:
```python
class CompareProtocol(Protocol):
id: Mapped[int | UUID]
```
Because mypy will see this as a settable variable and not as a SQLAlchemy descriptor. Using `@property` instead seems to work and it's what I used in the test (perhaps it should be documented as the way to achieve this?):
### Checklist
<!-- go over following points. check them with an `x` if they do apply, (they turn into clickable checkboxes once the PR is submitted, so no need to do everything at once)
-->
This pull request is:
- [ ] A documentation / typographical / small typing error fix
- Good to go, no issue or tests are needed
- [X] A short code fix
- please include the issue number, and create an issue if none exists, which
must include a complete example of the issue. one line code fixes without an
issue and demonstration will not be accepted.
- Please include: `Fixes: #<issue number>` in the commit message
- please include tests. one line code fixes without tests will not be accepted.
- [ ] A new feature implementation
- please include the issue number, and create an issue if none exists, which must
include a complete example of how the feature would look.
- Please include: `Fixes: #<issue number>` in the commit message
- please include tests.
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.
Added :paramref:`.selectinload.chunksize` parameter to :func`.selectinload`
allowing users to configure the number of primary keys sent per IN clause
when loading reltaionships. Pull request courtesy bekapono.
Remove unused TypeVars and compat import from typing modules
## Summary
Removes 4 dead symbols from SQLAlchemy's typing modules. Each symbol has **zero references** across `lib/`, `test/`, `doc/`, `tools/`, and `examples/` — verified by grep for direct references, string forward references, and `getattr`-style dynamic access.
## Changes
**`lib/sqlalchemy/util/typing.py`**
- Remove `from . import compat` — unused module import; the only occurrence of `compat` in the file is in a code comment on line 221
- Remove `_KT_co = TypeVar("_KT_co", covariant=True)` — unused TypeVar
- Remove `_KT_contra = TypeVar("_KT_contra", contravariant=True)` — unused TypeVar
**`lib/sqlalchemy/orm/_typing.py`**
- Remove `_T_co = TypeVar("_T_co", bound=Any, covariant=True)` — orphaned; `orm.base` imports `_T_co` from `sql._typing` instead, and other orm modules (`interfaces.py`, `attributes.py`) define their own local `_T_co`
Total: **-7 lines**, no additions.
## Verification
For each removed symbol:
| Symbol | Repo-wide grep result |
|---|---|
| `compat` (in `util/typing.py`) | 0 references to `compat.` or `util.typing.compat` outside the import itself |
| `_KT_co` | 1 hit total (the removed definition line) |
| `_KT_contra` | 1 hit total (the removed definition line) |
| `_T_co` (in `orm/_typing.py`) | No `from sqlalchemy.orm._typing import _T_co` or `from ..orm._typing import _T_co` anywhere; all consumers import from `sql._typing` or define locally |
Additionally checked:
- No `__all__` references
- No string forward references (e.g. `"_KT_co"`)
- No `getattr(..., "_T_co")` or similar dynamic access
- No re-exports via `util/__init__.py` or any other module
- `test/base/test_typing_utils.py` (which imports `util.typing as sa_typing`) does not touch any of these names
## Test plan
- [x] `pre-commit` hooks pass (black, zimports, flake8)
- [x] `import sqlalchemy`, `sqlalchemy.orm`, `sqlalchemy.util.typing`, `sqlalchemy.orm._typing`, `sqlalchemy.sql._typing`, `sqlalchemy.dialects._typing` — all import cleanly
- [x] Broad import smoke test: `Mapped`, `DeclarativeBase`, `relationship`, `Session`, `Mapper`, `mapped_column`, `Column`, `select`, `create_engine`, `MetaData`, `QueuePool`, `hybrid_property`, `association_proxy` — all import cleanly
- [x] Full `tox` / test suite — would appreciate CI confirmation
## Not included (intentionally)
- `LiteralString` re-export in `util/typing.py:54` — has 0 in-tree consumers but is an explicit `as X as X` public re-export; kept for API stability
- `is_origin_of` rename to `_is_origin_of` — used only internally by `is_union`, but renaming is a minor breaking change not worth the churn
Add OrderByRole to acceptable arguments for order_by
Fixes: #13248
### Description
Add OrderByRole to acceptable arguments for order_by to solve typing regression
### Checklist
<!-- go over following points. check them with an `x` if they do apply, (they turn into clickable checkboxes once the PR is submitted, so no need to do everything at once)
-->
This pull request is:
- [x] A documentation / typographical / small typing error fix
- Good to go, no issue or tests are needed
- [x] A short code fix
- please include the issue number, and create an issue if none exists, which
must include a complete example of the issue. one line code fixes without an
issue and demonstration will not be accepted.
- Please include: `Fixes: #<issue number>` in the commit message
- please include tests. one line code fixes without tests will not be accepted.
- [ ] A new feature implementation
- please include the issue number, and create an issue if none exists, which must
include a complete example of how the feature would look.
- Please include: `Fixes: #<issue number>` in the commit message
- please include tests.
Mike Bayer [Mon, 20 Apr 2026 13:30:35 +0000 (09:30 -0400)]
narrow scope of _correct_for_mysql_bugs_88718_96365
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.
Mike Bayer [Fri, 17 Apr 2026 20:13:53 +0000 (16:13 -0400)]
handle asyncpg InternalClientError
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.
Add mypy ignore comments for Python 3.14 module imports
Fixes #13240
Use `sys.version_info >= (3, 14)` instead of the `py314` variable for
the py314-only imports block (`annotationlib`, `string.templatelib`).
mypy has built-in support for `sys.version_info` guards and only
type-checks the reachable branch, while a plain variable like `py314`
causes mypy to check both branches — leading to `import-not-found` on
<3.14 and `unused-ignore` on 3.14 if `type: ignore` comments are added.
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.
abdallah elhdad [Tue, 17 Mar 2026 14:33:29 +0000 (10:33 -0400)]
Add JSON type support for Oracle dialect
Added support for the :class:`_sqltypes.JSON` datatype when using the
Oracle database with the oracledb dialect. JSON values are serialized and
deserialized using configurable strategies that accommodate Oracle's native
JSON type available as of Oracle 21c. Pull request courtesy Abdallah
Alhadad.
This fix also includes new dialect-level indicators for JSON support;
some attention given to issue #13213 indicates we can close that issue.
bekapono [Tue, 24 Mar 2026 01:30:46 +0000 (21:30 -0400)]
Coercion warning feedback for unary distinct outside aggregate function
A warning is emitted when using the standalone :func:`_.sql.distinct`
function in a :func:`_sql.select` columns list outside of an aggregate
function; this function is not intended as a replacement for the use of
:meth:`.Select.distinct`. Pull request courtesy bekapono.
Mike Bayer [Sun, 29 Mar 2026 17:46:39 +0000 (13:46 -0400)]
accommodate subclass mapper in post-loader entity_isa check
Fixed issue where using chained loader options such as
:func:`_orm.selectinload` after :func:`_orm.joinedload` with
:meth:`_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.
Mike Bayer [Fri, 27 Mar 2026 18:24:10 +0000 (14:24 -0400)]
apply _path_with_polymorphic in prepend as well
Fixed issue where using :meth:`_orm.Load.options` to apply a chained loader
option such as :func:`_orm.joinedload` or :func:`_orm.selectinload` with
:meth:`_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))``.
joshuaswanson [Fri, 27 Mar 2026 16:41:11 +0000 (12:41 -0400)]
fix: Session.get() with with_for_update=False skips identity map
Fixes #13176.
`Session.get()` checks `with_for_update is None` to decide whether to look up the identity map. Passing `with_for_update=False` fails this check and always hits the database, even though `ForUpdateArg._from_argument` already treats `False` and `None` identically (both return `None`). Changed to `with_for_update in (None, False)` to match.
Federico Caselli [Tue, 24 Mar 2026 20:08:03 +0000 (21:08 +0100)]
use getattr for metadata, registry and type_annotation_map
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__``.
Mike Bayer [Mon, 23 Mar 2026 18:13:02 +0000 (14:13 -0400)]
detect and accommodate reverse condition for loader strategy
Fixed issue where chained :func:`_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
:func:`_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.