summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/unreleased_13/5048.rst15
-rw-r--r--lib/sqlalchemy/engine/default.py2
-rw-r--r--test/sql/test_query.py46
3 files changed, 63 insertions, 0 deletions
diff --git a/doc/build/changelog/unreleased_13/5048.rst b/doc/build/changelog/unreleased_13/5048.rst
new file mode 100644
index 000000000..71afd2d4a
--- /dev/null
+++ b/doc/build/changelog/unreleased_13/5048.rst
@@ -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.
+
diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py
index d1253f678..9eacf0527 100644
--- a/lib/sqlalchemy/engine/default.py
+++ b/lib/sqlalchemy/engine/default.py
@@ -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
)
diff --git a/test/sql/test_query.py b/test/sql/test_query.py
index ba878cfbc..3b5e7dff2 100644
--- a/test/sql/test_query.py
+++ b/test/sql/test_query.py
@@ -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"])