18119 Commits

Author SHA1 Message Date
Mike Bayer ddf3b6589f Merge branch 'dependabot/github_actions/actions/checkout-7'
Change-Id: I46dff7c91fd6abef23ef8c76a5ee072176618b99
2026-06-18 14:12:36 -04:00
Michael Bayer 3ad610e105 Merge "Improve performance of selectinload result handling by up to ~30%" into main 2026-06-18 17:25:40 +00:00
dependabot[bot] 700510e719 Bump actions/checkout from 6 to 7
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-18 15:12:33 +00:00
Mike Bayer 7a823b5f81 Fix is_pep695 misidentifying Annotated[TypeAliasType] as PEP 695
The is_pep695() function incorrectly identified
Annotated[TypeAliasType, ...] as a PEP 695 type alias because
Annotated's __origin__ attribute returns the first type argument
(the TypeAliasType) rather than Annotated itself.  This caused
_init_column_for_annotation to crash with AttributeError when
attempting to access __value__ on the Annotated wrapper.

Added a check for is_pep593() before recursing through __origin__
in is_pep695(), so Annotated types are correctly excluded.

Fixes: #13386
Change-Id: I36ef83ebbab5abc08bed0131efb552c3fc001911
2026-06-17 18:06:56 -04:00
Oliver Parker 89565f125f Improve performance of selectinload result handling by up to ~30%
* in selectinloader, dont use Bundle() to represent the PK portion 
* use more efficient mapper._state_ident_getter() method in selectinloader
  which pre-resolves keys and only calls upon _get_state_attr_by_column when 
  an attribute is not locally present
* removed use of groupby() + lambda against Row objects in subqueryloader; converts 
  to tuple and builds lists via append()



Adds tests pinning behavior of the rewritten result handling: the uselist=False multiple-rows warning, and many-to-one loads where the foreign key value matches no row or is NULL.

Benchmarked on an in-memory SQLite database (median of 30 runs, ms):

case | baseline | branch | Δ
-- | -- | -- | --
selectin_m2m | 9.747 | 6.958 | -28.6%
selectin_m2o | 13.761 | 12.538 | -8.9%
selectin_nested | 14.670 | 11.473 | -21.8%
selectin_o2m | 14.686 | 11.823 | -19.5%
selectin_o2m_few_big | 25.771 | 19.787 | -23.2%
subquery_o2m | 15.946 | 15.736 | -1.3%


Fixes: #13363
Closes: #13364
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/13364
Pull-request-sha: 3b858fc54b

Change-Id: I2e772c7ee9fa9e1b50026cab8c7997b861f1acda
2026-06-17 21:50:36 +00:00
Mike Bayer a2e79928a9 Merge branch 'dependabot/github_actions/pypa/cibuildwheel-4.1.0'
Change-Id: I263869dff37838f9e34e4b6c6641c26aec714374
2026-06-15 16:29:26 -04:00
Michael Bayer f9f262826b Merge "Fixes: 13363 Process ORM result rows as plain tuples without Row construction" into main 2026-06-15 20:28:20 +00:00
Oliver Parker 0dcfa5e9ea Fixes: 13363 Process ORM result rows as plain tuples without Row construction
ORM result row fetching now processes rows as plain tuples rather than
constructing :class:`.Row` objects, as ORM loaders use position-based
access and do not require the :class:`.Row` interface. :class:`.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.

Adds Result._all_interim_rows(), which returns the remaining rows as processed plain tuples, applying result processors and tuple filters but skipping Row object construction.  ORM loading uses this for its row fetch; its row getters are position-based itemgetters that accept any tuple-like row.  Results that require row logging or have scalar sources fall back to Row construction.

Adds tests covering the new behavior: rows are plain tuples with result processors applied, and Row construction still occurs when engine-level row logging is enabled, at both the Result and ORM loading level.

Benchmarked on an in-memory SQLite database with this change alone (median of 30 runs, ms); this path is used by all ORM entity loads:

    plain_small (500 rows x 5 cols)    2.43 ->  2.28    -6%
    plain_wide (2000 rows x 25 cols)   8.17 ->  7.24   -11%
    joined_o2m (500 x 10)             18.56 -> 16.95    -9%
    selectin_o2m (500 x 10)           18.45 -> 17.79    -4%
    selectin_nested (50 x 10 x 10)    18.67 -> 17.33    -7%
    selectin_m2m (500 x 10)           12.70 -> 11.53    -9%
    selectin_m2o (5000 -> 200)        15.83 -> 15.29    -3%
    selectin_o2m_few_big (20 x 500)   32.96 -> 31.29    -5%
    subquery_o2m (500 x 10)           21.51 -> 18.16   -16%

Fixes: #13363
Closes: #13365
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/13365
Pull-request-sha: 46f4125ead

Change-Id: Ia91a89c8cac78d4790391bc5b171b76d4aaca71b
2026-06-15 16:01:49 -04:00
Mike Bayer 3c809dc77c cherry-pick changelog update for 2.0.52 2026-06-15 11:41:38 -04:00
Mike Bayer d33bd47665 cherry-pick changelog from 2.0.51 2026-06-15 11:41:37 -04:00
dependabot[bot] 3fc6b9cac9 Bump pypa/cibuildwheel from 4.0.0 to 4.1.0
Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/pypa/cibuildwheel/releases)
- [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md)
- [Commits](https://github.com/pypa/cibuildwheel/compare/v4.0.0...v4.1.0)

---
updated-dependencies:
- dependency-name: pypa/cibuildwheel
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-15 15:21:14 +00:00
dxbjavid f55d694e21 fix backtracking hang in hstore literal parser
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.

Fixes: #13370
Closes: #13371
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/13371
Pull-request-sha: f5eddae11c

Change-Id: I0d2d7565dc88f56a73b41e2ad20ca1c5a6f738bb
2026-06-15 14:28:56 +00:00
Mike Bayer bfe559a7e4 add row logging tests
ensure all the main result methods as well as the ORM-used
_raw_all_rows() perform equivalent row logging.

Change-Id: Iee0dc3c75a9c71f6d1a73fa058c5edf0b16348ec
2026-06-12 18:14:54 +00:00
Mike Bayer 5ed0294ae2 bump mssql-python to 1.9.0
1.9.0 seems to fix the picklable statement exception fail,
so fix for that test and pin to lower version

Change-Id: Ic561b1a6b688f38b9f5189508d9203bca47fef79
2026-06-12 14:13:50 -04:00
Michael Bayer 988939423f Merge "factor single-table reflection wrappers into common mixin" into main 2026-06-12 16:12:57 +00:00
lphuc2250gma 41caa189ae chore: improve sqlalchemy maintenance path (#13351)
Co-authored-by: Noa Levi <275430404+lphuc2250gma@users.noreply.github.com>
2026-06-11 23:19:57 +02:00
CaoRongkai ea1a30f460 Document quoted_name use for PostgreSQL INHERITS (#13342) 2026-06-11 23:19:16 +02:00
dependabot[bot] 18370a0910 Bump pypa/cibuildwheel from 3.4.1 to 4.0.0 (#13358)
Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 3.4.1 to 4.0.0.
- [Release notes](https://github.com/pypa/cibuildwheel/releases)
- [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md)
- [Commits](https://github.com/pypa/cibuildwheel/compare/v3.4.1...v4.0.0)

---
updated-dependencies:
- dependency-name: pypa/cibuildwheel
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-11 22:57:54 +02:00
Mike Bayer eeedfc4d52 factor single-table reflection wrappers into common mixin
Introduced _BackendsMultiReflection mixin in engine/default.py
that provides the get_columns(), get_pk_constraint(),
get_foreign_keys(), get_indexes(), get_unique_constraints(),
get_check_constraints(), get_table_comment(), and
get_table_options() single-table methods, each delegating to
the corresponding get_multi_* method with
filter_names=[table_name].

PostgreSQL, Oracle, and MSSQL dialects now inherit from this
mixin instead of duplicating the wrapper pattern.  Oracle
retains its _value_or_raise() override which applies
normalize_name() for case-folding.  MSSQL overrides
get_unique_constraints(), get_check_constraints(), and
get_table_options() with NotImplementedError since it has no
native get_multi_* for those yet.

Change-Id: If68bbf94b3956348fc7ae8179ff093d63dcdfbe2
2026-06-10 15:21:28 -04:00
Michael Bayer 4e97e0abaf Merge "allow rollback within _prepare_impl on twophase prepare failure" into main 2026-06-10 13:08:43 +00:00
Michael Bayer 1a93c951bc Merge "repair xid in psycopg" into main 2026-06-10 13:08:10 +00:00
Mike Bayer 30d75f9a30 allow rollback within _prepare_impl on twophase prepare failure
When tpc_prepare() raised during SessionTransaction._prepare_impl(),
the error handler's call to self.rollback() was blocked by the
@declare_states decorator, which had set _next_state to
CHANGE_IN_PROGRESS. This caused IllegalStateChangeError to be raised
instead of the original database exception, masking the real error
and preventing proper cleanup.

Used _expect_state(SessionTransactionState.CLOSED) to temporarily
allow the rollback state transition, matching the existing pattern
used in commit() for the close() call.

Fixes: #13356
Change-Id: Ie8212d5b6f8515340cf9d83c56dcbfa5a7415812
2026-06-09 14:47:46 -04:00
Michael Bayer 52191f42c9 Merge "send execution options to connection also" into main 2026-06-09 18:34:29 +00:00
Michael Bayer c39455f62c Merge "remove legacy include_columns in index reflection" into main 2026-06-09 14:23:36 +00:00
Federico Caselli 75cb5aec65 repair xid in psycopg
Repaired bug introduced in 🎫`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.

Fixes: #13355
Change-Id: Iffe68c1701afaa678fa7b598559dd396d3f8db41
2026-06-08 22:17:57 +02:00
Mike Bayer 24a9c35bed use trusted publishing for PyPI wheel uploads
Replace token-based PyPI authentication with OIDC trusted publishing.
Add workflow-level id-token: write permission, generate PEP 740
attestations using pypi-attestations, and upload with
twine --attestations.  Removes the pypi_token secret dependency.

Closes: #13324
Change-Id: I75d8eab7ade7be61ed86d773ea2403cd484c81dd
2026-06-05 12:35:35 -04:00
Michael Bayer 0798e6cbe1 Merge "minor code improvement to the mssql multi reflection" into main 2026-06-05 14:25:26 +00:00
Michael Bayer 3b7d40ff75 Merge "Register func.any(), func.all(), func.some() as collection aggregates" into main 2026-06-05 14:08:03 +00:00
Federico Caselli 7d410c1264 minor code improvement to the mssql multi reflection
improve caching and remove redundant flag

Change-Id: I46ee752c908227667a281bd4d436fac2b43b0d8e
2026-06-05 07:13:19 +00:00
Federico Caselli 7543c167eb remove legacy include_columns in index 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``.

Fixes: #13350
Change-Id: I9535690350d97e19477f7e9919dc4994b876af47
2026-06-04 23:27:49 +02:00
Federico Caselli f8191bfbc2 fix docs lint warning
Change-Id: I6c8e38dc663bc0b53ab1259daab50617583ccdbb
2026-06-04 23:26:13 +02:00
Federico Caselli f055b4a49a Merge "Implement native multi-table reflection API for the mssql dialect" into main 2026-06-04 21:25:40 +00:00
Gaurav Sharma c84c7b2ffc 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:

| | single | multi | speedup |
|---|---|---|---|
| `get_columns` | 1.33s | 0.35s | 3.8x |
| `get_pk_constraint` | 1.41s | 0.08s | 18x |
| `get_foreign_keys` | 7.07s | 0.19s | 37x |
| `get_indexes` | 0.80s | 0.09s | 8.6x |
| `get_table_comment` | 0.71s | 0.05s | 14x |
| **MetaData.reflect** | **12.62s** | **1.15s** | **11x** |

### Checklist

This pull request is:

- [x] A new feature implementation
	- Fixes: #8430
	- Tests added in `test/dialect/mssql/test_reflection.py`
	- Changelog entry: `doc/build/changelog/unreleased_21/8430.rst`

Closes: #13297
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/13297
Pull-request-sha: 2c6c69f159

Change-Id: I525c60fc5ece94dd250f376b05b64b09e65ca0d7
2026-06-04 23:04:34 +02:00
Bradley Davis 563146468a Removed erroneous debug print (#13349) 2026-06-04 21:51:32 +02:00
Federico Caselli fc913aac9a Merge "Perf/conditional unique selectinload" into main 2026-06-04 19:50:15 +00:00
Federico Caselli 35e0d85b33 improve typed column migration docs
Change-Id: I402580004d1c0d8583ebc9e2852f38d687ba6ae8
2026-06-04 21:39:13 +02:00
Mike Bayer 0119422938 send execution options to connection also
Session level :paramref:`_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
:paramref:`_engine.Connection.execution_options.schema_translate_map`
to be applicable to a :class:`.Session` overall.

Fixes: #13346
Change-Id: Icb453b7925f1bc38f30538d7726d222842bc65bd
2026-06-04 13:08:24 -04:00
Mike Bayer b14415fa43 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.

Fixes: #13343
Change-Id: Id4774938876dc7f1f38cf143dccfe3c8ddba464d
2026-06-04 10:25:33 -04:00
Oliver Parker efa87e2500 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).

Fixes: #13339
Closes: #13341
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/13341
Pull-request-sha: 980af0383b

Change-Id: I12e993f104d354ae894f81f9b09fbcf9a95b0027
2026-06-03 13:49:17 -04:00
sebastianbreguel abfe6cc47c 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.

Closes: #13337
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/13337
Pull-request-sha: beba43e77a

Change-Id: I95b02ffa66ef709c16bf5275924ec03244c19ecb
2026-05-31 16:12:19 -04:00
me-saurabhkohli 4fb459aaf0 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.

Fixes: #9427
Closes: #13335
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/13335
Pull-request-sha: c03904ece2

Change-Id: Ia184f77b442b069e6f9a4f94a967ead41a1704b6
2026-05-29 18:20:14 -04:00
Michael Bayer 685817d83a Merge "allow backref named 'metadata' to not break _metadata_for_cls" into main 2026-05-28 16:37:00 +00:00
Mike Bayer 96f98a9119 allow backref named 'metadata' to not break _metadata_for_cls
Fixed regression caused by 🎫`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.

References: https://bugs.launchpad.net/nova/+bug/2154165
References: https://github.com/sqlalchemy/sqlalchemy/discussions/8619

Fixes: #13333
Change-Id: I0f3173bce9c8c8881bd6b8ea75102bb8ec92be8b
2026-05-28 11:03:34 -04:00
Michael Bayer 18f41b29d7 Merge "Fix subqueryload losing .and_() criteria when combined with of_type()" into main 2026-05-28 14:16:47 +00:00
Michael Bayer 7babdfbd51 Merge "Fix lambda statements with non-lambda criteria" into main 2026-05-28 14:16:16 +00:00
cjc0013 c41f25b170 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.

Fixes: #10827
Closes: #13327
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/13327
Pull-request-sha: ec3e6735bf

Change-Id: I8a32c11f3da63109cf37c39541df8ebfee52b8c5
2026-05-27 18:39:11 -04:00
Arya Rizky cf3cfa307f 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.

Fixes: #13207

Closes: #13290
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/13290
Pull-request-sha: b7a8617cde

Change-Id: I2c24652ec112511deaf39dbb9d6197e2097904ed
2026-05-27 18:06:55 -04:00
bekapono 808fd28297 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.

Fixes: #5987
Closes: #13278
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/13278
Pull-request-sha: fdd9847e7d

Change-Id: Ib68f8e2be1399222383cdd7b55793fe88402212c
2026-05-27 13:23:10 -04:00
proto-atlas e00937ec54 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.

Checked locally:

python -m pytest -m mypy test/typing/test_mypy.py -k "session.py" -q
python -m mypy ./lib/sqlalchemy
python tools/generate_proxy_methods.py --check
python -m pytest test/orm/dml/test_bulk.py -q
python -m pytest -m mypy test/typing/test_mypy.py -k "not typed_queries.py" -q

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.

Closes: #13322
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/13322
Pull-request-sha: bb730f3427

Change-Id: I4c5d516b3933b4e7fae9c844881a61f557a8bb5e
2026-05-25 21:18:00 +02:00
Mike Bayer 092391036c cherry-pick changelog update for 2.0.51 2026-05-24 15:20:50 -04:00