From: Mike Bayer Date: Fri, 20 Dec 2019 16:40:10 +0000 (-0500) Subject: Copy bind_processors when altering for expanding IN X-Git-Tag: rel_1_3_13~26 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ba9b517e6f6a6602df43256a8dda616dd364784b;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Copy bind_processors when altering for expanding IN 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) --- diff --git a/doc/build/changelog/unreleased_13/5048.rst b/doc/build/changelog/unreleased_13/5048.rst new file mode 100644 index 0000000000..71afd2d4ad --- /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 2a5a4e0bf0..8296b2b19c 100644 --- a/lib/sqlalchemy/engine/default.py +++ b/lib/sqlalchemy/engine/default.py @@ -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 diff --git a/test/sql/test_query.py b/test/sql/test_query.py index 2726b3f3c2..70cc2393fb 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"])