]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Copy bind_processors when altering for expanding IN
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 20 Dec 2019 16:40:10 +0000 (11:40 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 20 Dec 2019 16:42:02 +0000 (11:42 -0500)
Fixed issue where the collection of value processors on a
:class:`.Compiled` object would be mutated when "expanding IN" parameters
were used with a datatype that has bind value processors; in particular,
this would mean that when using statement caching and/or baked queries, the
same compiled._bind_processors collection would be mutated concurrently.
Since these processors are the same function for a given bind parameter
namespace every time, there was no actual negative effect of this issue,
however, the execution of a :class:`.Compiled` object should never be
causing any changes in its state, especially given that they are intended
to be thread-safe and reusable once fully constructed.

Fixes: #5048
Change-Id: I876d16bd7484eb05ce590397420552ac36da6e52
(cherry picked from commit ee961ce5778348fba8287e3192fe8848badf8456)

doc/build/changelog/unreleased_13/5048.rst [new file with mode: 0644]
lib/sqlalchemy/engine/default.py
test/sql/test_query.py

diff --git a/doc/build/changelog/unreleased_13/5048.rst b/doc/build/changelog/unreleased_13/5048.rst
new file mode 100644 (file)
index 0000000..71afd2d
--- /dev/null
@@ -0,0 +1,15 @@
+.. change::
+    :tags: bug, engine
+    :tickets: 5048
+
+    Fixed issue where the collection of value processors on a
+    :class:`.Compiled` object would be mutated when "expanding IN" parameters
+    were used with a datatype that has bind value processors; in particular,
+    this would mean that when using statement caching and/or baked queries, the
+    same compiled._bind_processors collection would be mutated concurrently.
+    Since these processors are the same function for a given bind parameter
+    namespace every time, there was no actual negative effect of this issue,
+    however, the execution of a :class:`.Compiled` object should never be
+    causing any changes in its state, especially given that they are intended
+    to be thread-safe and reusable once fully constructed.
+
index 2a5a4e0bf049cc5c8455dd7ecd07cde62daeabfb..8296b2b19cb0d4a6f78a6e1d2278e34368cda728 100644 (file)
@@ -783,6 +783,8 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
         processors = compiled._bind_processors
 
         if compiled.contains_expanding_parameters:
+            # copy processors for this case as they will be mutated
+            processors = dict(processors)
             positiontup = self._expand_in_parameters(compiled, processors)
         elif compiled.positional:
             positiontup = self.compiled.positiontup
index 2726b3f3c20d14c7847071fa417a6cea4106c4c4..70cc2393fbb5e04bc6207db5007f5aa5697ae0a4 100644 (file)
@@ -752,6 +752,52 @@ class QueryTest(fixtures.TestBase):
                 [(7, "jack"), (8, "fred")],
             )
 
+    def test_expanding_in_dont_alter_compiled(self):
+        """test for issue #5048 """
+
+        class NameWithProcess(TypeDecorator):
+            impl = String
+
+            def process_bind_param(self, value, dialect):
+                return value[3:]
+
+        users = Table(
+            "query_users",
+            MetaData(),
+            Column("user_id", Integer, primary_key=True),
+            Column("user_name", NameWithProcess()),
+        )
+
+        with testing.db.connect() as conn:
+            conn.execute(
+                users.insert(),
+                [
+                    dict(user_id=7, user_name="AB jack"),
+                    dict(user_id=8, user_name="BE fred"),
+                    dict(user_id=9, user_name="GP ed"),
+                ],
+            )
+
+            stmt = (
+                select([users])
+                .where(
+                    users.c.user_name.in_(bindparam("uname", expanding=True))
+                )
+                .order_by(users.c.user_id)
+            )
+
+            compiled = stmt.compile(testing.db)
+            eq_(len(compiled._bind_processors), 1)
+
+            eq_(
+                conn.execute(
+                    compiled, {"uname": ["HJ jack", "RR fred"]}
+                ).fetchall(),
+                [(7, "jack"), (8, "fred")],
+            )
+
+            eq_(len(compiled._bind_processors), 1)
+
     @testing.fails_on("firebird", "uses sql-92 rules")
     @testing.fails_on("sybase", "uses sql-92 rules")
     @testing.skip_if(["mssql"])