]> 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:24 +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

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 e8a830b2b46da053c428df530829415c019d938d..39614b917876dccd9c5d41a84f5fe694b89c8a83 100644 (file)
@@ -2340,7 +2340,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 02308512279f656532997698cb5796c249500c91..dda890c0b5cbabc57b388793551a9ebb8f4b11a0 100644 (file)
@@ -138,7 +138,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 347ba0e144b3fde6aeb2d8fe360975d8e58dde8f..60f062ec95e62455bce06158865fece75734f5be 100644 (file)
@@ -4513,6 +4513,7 @@ class CompoundSelect(
         + SupportsCloneAnnotations._clone_annotations_traverse_internals
         + HasCTE._has_ctes_traverse_internals
         + DialectKWArgs._dialect_kwargs_traverse_internals
+        + Executable._executable_traverse_internals
     )
 
     selects: List[SelectBase]
@@ -7166,6 +7167,7 @@ class TextualSelect(SelectBase, ExecutableReturnsRows, Generative):
         ]
         + SupportsCloneAnnotations._clone_annotations_traverse_internals
         + HasCTE._has_ctes_traverse_internals
+        + Executable._executable_traverse_internals
     )
 
     _is_textual = True
index 45cea0c46ae784ea7cc4c8f483829c6dcefc1b71..774f9792f443635ff137b31adfbd51e58daefb24 100644 (file)
@@ -36,6 +36,7 @@ from sqlalchemy.sql import aggregate_order_by
 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
@@ -1538,10 +1539,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
@@ -1724,6 +1721,7 @@ class HasCacheKeySubclass(fixtures.TestBase):
                 SingletonConstant,
                 SyntaxExtension,
                 DialectKWArgs,
+                Executable,
             ]
         )
     )