Files
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
..
2025-11-30 14:38:13 -05:00
2026-05-10 19:00:42 +02:00
2025-12-18 21:18:42 +01:00
2025-12-19 21:24:23 +01:00
2026-05-10 19:00:42 +02:00
2025-11-30 14:38:13 -05:00