]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
dont leak mutating bindparams list into AnalyzedFunction
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 12 Nov 2024 19:50:50 +0000 (14:50 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 13 Nov 2024 16:22:54 +0000 (11:22 -0500)
Fixed issue in "lambda SQL" feature where the tracking of bound parameters
could be corrupted if the same lambda were evaluated across multiple
compile phases, including when using the same lambda across multiple engine
instances or with statement caching disabled.

Fixes: #12084
Change-Id: I327aa93ce7feb2326a22113164bd834b96b6b889
(cherry picked from commit 5bbefc41b7b2695c95c9c93bcaabd8c4731e348e)

doc/build/changelog/unreleased_20/12084.rst [new file with mode: 0644]
lib/sqlalchemy/sql/lambdas.py
test/sql/test_lambdas.py

diff --git a/doc/build/changelog/unreleased_20/12084.rst b/doc/build/changelog/unreleased_20/12084.rst
new file mode 100644 (file)
index 0000000..0eef5c9
--- /dev/null
@@ -0,0 +1,9 @@
+.. change::
+    :tags: bug, sql
+    :tickets: 12084
+
+    Fixed issue in "lambda SQL" feature where the tracking of bound parameters
+    could be corrupted if the same lambda were evaluated across multiple
+    compile phases, including when using the same lambda across multiple engine
+    instances or with statement caching disabled.
+
index 7a6b7b8f776a34e2448c27d6c9dbd2df16563e5f..2657b2c243d7acb34b6dab812084ffe8023b9d4d 100644 (file)
@@ -278,7 +278,7 @@ class LambdaElement(elements.ClauseElement):
                         rec = AnalyzedFunction(
                             tracker, self, apply_propagate_attrs, fn
                         )
-                        rec.closure_bindparams = bindparams
+                        rec.closure_bindparams = list(bindparams)
                         lambda_cache[key] = rec
                     else:
                         rec = lambda_cache[key]
index 17991ea2e35158599274a15abb5f45a92587e1dc..9eb20dd4e59a5d26bd4552c6ddfda5f1daf4ed4c 100644 (file)
@@ -1889,6 +1889,47 @@ class LambdaElementTest(
                 (7, "foo"),
             )
 
+    def test_bindparam_not_cached(self, user_address_fixture, testing_engine):
+        """test #12084"""
+
+        users, addresses = user_address_fixture
+
+        engine = testing_engine(
+            share_pool=True, options={"query_cache_size": 0}
+        )
+        with engine.begin() as conn:
+            conn.execute(
+                users.insert(),
+                [{"id": 7, "name": "bar"}, {"id": 8, "name": "foo"}],
+            )
+
+        def make_query(stmt, *criteria):
+            for crit in criteria:
+                stmt += lambda s: s.where(crit)
+
+            return stmt
+
+        for i in range(2):
+            with engine.connect() as conn:
+                stmt = lambda_stmt(lambda: select(users))
+                # create a filter criterion that will never match anything
+                stmt1 = make_query(
+                    stmt,
+                    users.c.name == "bar",
+                    users.c.name == "foo",
+                )
+
+                assert len(conn.scalars(stmt1).all()) == 0
+
+                stmt2 = make_query(
+                    stmt,
+                    users.c.name == "bar",
+                    users.c.name == "bar",
+                    users.c.name == "foo",
+                )
+
+                assert len(conn.scalars(stmt2).all()) == 0
+
 
 class DeferredLambdaElementTest(
     fixtures.TestBase, testing.AssertsExecutionResults, AssertsCompiledSQL