]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Add Executable traverse internals to Executable subclasses and turn tests on
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 7 Oct 2025 18:55:44 +0000 (14:55 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 8 Oct 2025 12:47:42 +0000 (08:47 -0400)
Fixed a caching issue where :func:`_orm.with_loader_criteria` would
incorrectly reuse cached bound parameter values when used with
:class:`_sql.CompoundSelect` constructs such as :func:`_sql.union`. The
issue was caused by the cache key for compound selects not including the
execution options that are part of the :class:`_sql.Executable` base class,
which :func:`_orm.with_loader_criteria` uses to apply its criteria
dynamically. The fix ensures that compound selects and other executable
constructs properly include execution options in their cache key traversal.

Fixes: #12905
Change-Id: I24bbd96233cddabe42eb716f078eea4c84b1af05
(cherry picked from commit 148059ced691da5edf3504a1d999cb1ab638dc7b)

doc/build/changelog/unreleased_20/12905.rst [new file with mode: 0644]
lib/sqlalchemy/sql/elements.py
lib/sqlalchemy/sql/functions.py
lib/sqlalchemy/sql/selectable.py
test/sql/test_compare.py

diff --git a/doc/build/changelog/unreleased_20/12905.rst b/doc/build/changelog/unreleased_20/12905.rst
new file mode 100644 (file)
index 0000000..e99a14e
--- /dev/null
@@ -0,0 +1,12 @@
+.. change::
+    :tags: bug, orm
+    :tickets: 12905
+
+    Fixed a caching issue where :func:`_orm.with_loader_criteria` would
+    incorrectly reuse cached bound parameter values when used with
+    :class:`_sql.CompoundSelect` constructs such as :func:`_sql.union`. The
+    issue was caused by the cache key for compound selects not including the
+    execution options that are part of the :class:`_sql.Executable` base class,
+    which :func:`_orm.with_loader_criteria` uses to apply its criteria
+    dynamically. The fix ensures that compound selects and other executable
+    constructs properly include execution options in their cache key traversal.
index 195ccda9ceb09159b5c5fd1a89c3afe6830a567d..a52c9b30a96e2d3b21a6c08ee4be8ac3fd4c4682 100644 (file)
@@ -2271,7 +2271,7 @@ class TextClause(
     _traverse_internals: _TraverseInternalsType = [
         ("_bindparams", InternalTraversal.dp_string_clauseelement_dict),
         ("text", InternalTraversal.dp_string),
-    ]
+    ] + Executable._executable_traverse_internals
 
     _is_text_clause = True
 
index 0540a6aa3f86016cd0108ebf8a7848f765968f32..fece73c9d8fa7250534a8b875f5644cdc074bae5 100644 (file)
@@ -137,7 +137,7 @@ class FunctionElement(Executable, ColumnElement[_T], FromClause, Generative):
         ("clause_expr", InternalTraversal.dp_clauseelement),
         ("_with_ordinality", InternalTraversal.dp_boolean),
         ("_table_value_type", InternalTraversal.dp_has_cache_key),
-    ]
+    ] + Executable._executable_traverse_internals
 
     packagenames: Tuple[str, ...] = ()
 
index b0e7d544fa0d8117b27e1764c4c4e6fc6f7547be..07f17570d74a2990de7cceb85341d5580f7f4734 100644 (file)
@@ -4534,6 +4534,7 @@ class CompoundSelect(HasCompileState, GenerativeSelect, TypedReturnsRows[_TP]):
         + SupportsCloneAnnotations._clone_annotations_traverse_internals
         + HasCTE._has_ctes_traverse_internals
         + DialectKWArgs._dialect_kwargs_traverse_internals
+        + Executable._executable_traverse_internals
     )
 
     selects: List[SelectBase]
@@ -7081,6 +7082,7 @@ class TextualSelect(SelectBase, ExecutableReturnsRows, Generative):
         ]
         + SupportsCloneAnnotations._clone_annotations_traverse_internals
         + HasCTE._has_ctes_traverse_internals
+        + Executable._executable_traverse_internals
     )
 
     _is_textual = True
index 2a58906ae0ca9393cc0e1c40684b210acdacb0be..e1dd32fe9172d788c4189fb822cac2a0803bccd5 100644 (file)
@@ -36,6 +36,7 @@ from sqlalchemy.schema import Sequence
 from sqlalchemy.sql import bindparam
 from sqlalchemy.sql import ColumnElement
 from sqlalchemy.sql import dml
+from sqlalchemy.sql import Executable
 from sqlalchemy.sql import False_
 from sqlalchemy.sql import func
 from sqlalchemy.sql import operators
@@ -1468,10 +1469,6 @@ class HasCacheKeySubclass(fixtures.TestBase):
         super_traverse = {}
         # ignore_super = self.ignore_super.get(cls.__name__, set())
         for s in cls.mro()[1:]:
-            # if s.__name__ in ignore_super:
-            #     continue
-            if s.__name__ == "Executable":
-                continue
             for attr in s.__dict__:
                 if not attr.endswith("_traverse_internals"):
                     continue
@@ -1649,6 +1646,7 @@ class HasCacheKeySubclass(fixtures.TestBase):
                 NoInit,
                 SingletonConstant,
                 DialectKWArgs,
+                Executable,
             ]
         )
     )