]> 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 17:50:06 +0000 (12:50 -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

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 d1253f678cf76ced836c7aceafdd5f41db421f50..9eacf05273db3e870b711745b3bddfe0d7867c1e 100644 (file)
@@ -773,6 +773,8 @@ class DefaultExecutionContext(interfaces.ExecutionContext):
         processors = compiled._bind_processors
 
         if compiled.literal_execute_params:
+            # copy processors for this case as they will be mutated
+            processors = dict(processors)
             positiontup = self._literal_execute_parameters(
                 compiled, processors
             )
index ba878cfbc70f65ec8311993e1f5cd624f9374db9..3b5e7dff2ae07f4d0e8e2767896cc902178e9c45 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"])