Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Step:15-SP1
python-SQLAlchemy.16963
0002-maintain-compiled_params-replacement_expre...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 0002-maintain-compiled_params-replacement_expressions.patch of Package python-SQLAlchemy.16963
From e4ec7d2b22546587d7a692041c6c70d23f6de3ac Mon Sep 17 00:00:00 2001 From: Mike Bayer <mike_mp@zzzcomputing.com> Date: Fri, 21 Dec 2018 17:35:12 -0500 Subject: [PATCH] Maintain compiled_params / replacement_expressions within expanding IN URL: https://github.com/sqlalchemy/sqlalchemy/issues/4394 Fixed issue in "expanding IN" feature where using the same bound parameter name more than once in a query would lead to a KeyError within the process of rewriting the parameters in the query. Fixes: #4394 Change-Id: Ibcadce9fefbcb060266d9447c2044ee6efeccf5a (cherry picked from commit c495769751e8b19d54fb92388ced587b5d13b85d) --- doc/build/changelog/unreleased_12/4394.rst | 7 ++ lib/sqlalchemy/engine/default.py | 75 +++++++++++++--------- test/sql/test_query.py | 37 ++++++++++- 3 files changed, 87 insertions(+), 32 deletions(-) create mode 100644 doc/build/changelog/unreleased_12/4394.rst diff --git a/doc/build/changelog/unreleased_12/4394.rst b/doc/build/changelog/unreleased_12/4394.rst new file mode 100644 index 0000000000..faa3547ad0 --- /dev/null +++ b/doc/build/changelog/unreleased_12/4394.rst @@ -0,0 +1,7 @@ +.. change:: + :tag: bug, sql + :tickets: 4394 + + Fixed issue in "expanding IN" feature where using the same bound parameter + name more than once in a query would lead to a KeyError within the process + of rewriting the parameters in the query. diff --git a/lib/sqlalchemy/engine/default.py b/lib/sqlalchemy/engine/default.py index 915812a4f2..e63c5eafca 100644 --- a/lib/sqlalchemy/engine/default.py +++ b/lib/sqlalchemy/engine/default.py @@ -726,45 +726,57 @@ class DefaultExecutionContext(interfaces positiontup = None replacement_expressions = {} + to_update_sets = {} + for name in ( self.compiled.positiontup if compiled.positional else self.compiled.binds ): parameter = self.compiled.binds[name] if parameter.expanding: - values = compiled_params.pop(name) - if not values: - raise exc.InvalidRequestError( - "'expanding' parameters can't be used with an " - "empty list" - ) - elif isinstance(values[0], (tuple, list)): - to_update = [ - ("%s_%s_%s" % (name, i, j), value) - for i, tuple_element in enumerate(values, 1) - for j, value in enumerate(tuple_element, 1) - ] - replacement_expressions[name] = ", ".join( - "(%s)" % ", ".join( - self.compiled.bindtemplate % { - "name": - to_update[i * len(tuple_element) + j][0] - } - for j, value in enumerate(tuple_element) + if name in replacement_expressions: + to_update = to_update_sets[name] + else: + # we are removing the parameter from compiled_params + # because it is a list value, which is not expected by + # TypeEngine objects that would otherwise be asked to + # process it. the single name is being replaced with + # individual numbered parameters for each value in the + # param. + values = compiled_params.pop(name) + + if not values: + raise exc.InvalidRequestError( + "'expanding' parameters with an empty list not " + "supported until SQLAlchemy 1.3." ) - for i, tuple_element in enumerate(values) + elif isinstance(values[0], (tuple, list)): + to_update = to_update_sets[name] = [ + ("%s_%s_%s" % (name, i, j), value) + for i, tuple_element in enumerate(values, 1) + for j, value in enumerate(tuple_element, 1) + ] + replacement_expressions[name] = ", ".join( + "(%s)" % ", ".join( + self.compiled.bindtemplate % { + "name": + to_update[i * len(tuple_element) + j][0] + } + for j, value in enumerate(tuple_element) + ) + for i, tuple_element in enumerate(values) - ) - else: - to_update = [ - ("%s_%s" % (name, i), value) - for i, value in enumerate(values, 1) - ] - replacement_expressions[name] = ", ".join( - self.compiled.bindtemplate % { - "name": key} - for key, value in to_update - ) + ) + else: + to_update = to_update_sets[name] = [ + ("%s_%s" % (name, i), value) + for i, value in enumerate(values, 1) + ] + replacement_expressions[name] = ", ".join( + self.compiled.bindtemplate % { + "name": key} + for key, value in to_update + ) compiled_params.update(to_update) processors.update( (key, processors[name]) @@ -778,7 +790,7 @@ class DefaultExecutionContext(interfaces positiontup.append(name) def process_expanding(m): - return replacement_expressions.pop(m.group(1)) + return replacement_expressions[m.group(1)] self.statement = re.sub( r"\[EXPANDING_(\S+)\]", diff --git a/test/sql/test_query.py b/test/sql/test_query.py index 3e629fb261..d649da202a 100644 --- a/test/sql/test_query.py +++ b/test/sql/test_query.py @@ -457,7 +457,7 @@ def test_expanding_in(self): assert_raises_message( exc.StatementError, - "'expanding' parameters can't be used with an empty list", + "'expanding' parameters with an empty list not supported", conn.execute, stmt, {"uname": []} ) @@ -531,6 +531,41 @@ def test_expanding_in_multiple(self): [(8, 'fred'), (9, 'ed')] ) + def test_expanding_in_repeated(self): + testing.db.execute( + users.insert(), + [ + dict(user_id=7, user_name='jack'), + dict(user_id=8, user_name='fred'), + dict(user_id=9, user_name='ed') + ] + ) + + with testing.db.connect() as conn: + stmt = select([users]).where( + users.c.user_name.in_( + bindparam('uname', expanding=True) + ) | users.c.user_name.in_(bindparam('uname2', expanding=True)) + ).where(users.c.user_id == 8) + stmt = stmt.union( + select([users]).where( + users.c.user_name.in_( + bindparam('uname', expanding=True) + ) | users.c.user_name.in_( + bindparam('uname2', expanding=True)) + ).where(users.c.user_id == 9) + ).order_by(stmt.c.user_id) + + eq_( + conn.execute( + stmt, + { + "uname": ['jack', 'fred'], + "uname2": ['ed'], "userid": [8, 9]} + ).fetchall(), + [(8, 'fred'), (9, 'ed')] + ) + @testing.requires.tuple_in def test_expanding_in_composite(self): testing.db.execute(
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor