diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-05-29 12:07:46 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-05-29 14:36:20 -0400 |
commit | cac8de9ab2d5fe04954947d96b78ee34522f3a2a (patch) | |
tree | 9ebaceee98c0ec28d2328eabf818e447e2eb61d9 /lib/sqlalchemy/sql/compiler.py | |
parent | 21ae13765d7410228672a282fef29fc0e2b3b098 (diff) | |
download | sqlalchemy-cac8de9ab2d5fe04954947d96b78ee34522f3a2a.tar.gz |
move bindparam quote application from compiler to default
in 296c84313ab29bf9599634f3 for #5653 we generalized Oracle's
parameter escaping feature into the compiler, so that it could also
work for PostgreSQL. The compiler used quoted names within parameter
dictionaries, which then led to the complexity that all functions
which interpreted keys from the compiled_params dict had to
also quote the param names to use the dictionary. This
extra complexity was not added to the ORM peristence.py however,
which led to the versioning id feature being broken as well as
other areas where persistence.py relies on naming schemes present
in context.compiled_params. It also was not added to the
"processors" lookup which led to #8053, that added this escaping
to that part of the compiler.
To both solve the whole problem as well as simplify the compiler
quite a bit, move the actual application of the escaped names
to be as late as possible, when default.py builds the final list
of parameters. This is more similar to how it worked previously
where OracleExecutionContext would be late-applying these
escaped names. This re-establishes context.compiled_params as
deterministically named regardless of dialect in use and moves
out the complexity of the quoted param names to be only at the
cursor.execute stage.
Fixed bug, likely a regression from 1.3, where usage of column names that
require bound parameter escaping, more concretely when using Oracle with
column names that require quoting such as those that start with an
underscore, or in less common cases with some PostgreSQL drivers when using
column names that contain percent signs, would cause the ORM versioning
feature to not work correctly if the versioning column itself had such a
name, as the ORM assumes certain bound parameter naming conventions that
were being interfered with via the quotes. This issue is related to
:ticket:`8053` and essentially revises the approach towards fixing this,
revising the original issue :ticket:`5653` that created the initial
implementation for generalized bound-parameter name quoting.
Fixes: #8056
Change-Id: I57b064e8f0d070e328b65789c30076f6a0ca0fef
Diffstat (limited to 'lib/sqlalchemy/sql/compiler.py')
-rw-r--r-- | lib/sqlalchemy/sql/compiler.py | 51 |
1 files changed, 15 insertions, 36 deletions
diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 63ed45a96..12a598717 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -1143,16 +1143,11 @@ class SQLCompiler(Compiled): str, Union[_BindProcessorType[Any], Sequence[_BindProcessorType[Any]]] ]: - _escaped_bind_names = self.escaped_bind_names - has_escaped_names = bool(_escaped_bind_names) - # mypy is not able to see the two value types as the above Union, # it just sees "object". don't know how to resolve return dict( ( - _escaped_bind_names.get(key, key) - if has_escaped_names - else key, + key, value, ) # type: ignore for key, value in ( @@ -1186,8 +1181,6 @@ class SQLCompiler(Compiled): ) -> _MutableCoreSingleExecuteParams: """return a dictionary of bind parameter keys and values""" - has_escaped_names = bool(self.escaped_bind_names) - if extracted_parameters: # related the bound parameters collected in the original cache key # to those collected in the incoming cache key. They will not have @@ -1217,16 +1210,10 @@ class SQLCompiler(Compiled): if params: pd = {} for bindparam, name in self.bind_names.items(): - escaped_name = ( - self.escaped_bind_names.get(name, name) - if has_escaped_names - else name - ) - if bindparam.key in params: - pd[escaped_name] = params[bindparam.key] + pd[name] = params[bindparam.key] elif name in params: - pd[escaped_name] = params[name] + pd[name] = params[name] elif _check and bindparam.required: if _group_number: @@ -1251,19 +1238,13 @@ class SQLCompiler(Compiled): value_param = bindparam if bindparam.callable: - pd[escaped_name] = value_param.effective_value + pd[name] = value_param.effective_value else: - pd[escaped_name] = value_param.value + pd[name] = value_param.value return pd else: pd = {} for bindparam, name in self.bind_names.items(): - escaped_name = ( - self.escaped_bind_names.get(name, name) - if has_escaped_names - else name - ) - if _check and bindparam.required: if _group_number: raise exc.InvalidRequestError( @@ -1285,9 +1266,9 @@ class SQLCompiler(Compiled): value_param = bindparam if bindparam.callable: - pd[escaped_name] = value_param.effective_value + pd[name] = value_param.effective_value else: - pd[escaped_name] = value_param.value + pd[name] = value_param.value return pd @util.memoized_instancemethod @@ -1359,6 +1340,7 @@ class SQLCompiler(Compiled): N as a bound parameter. """ + if parameters is None: parameters = self.construct_params() @@ -1435,7 +1417,12 @@ class SQLCompiler(Compiled): # process it. the single name is being replaced with # individual numbered parameters for each value in the # param. - values = parameters.pop(escaped_name) + # + # note we are also inserting *escaped* parameter names + # into the given dictionary. default dialect will + # use these param names directly as they will not be + # in the escaped_bind_names dictionary. + values = parameters.pop(name) leep = self._literal_execute_expanding_parameter to_update, replacement_expr = leep( @@ -1541,15 +1528,7 @@ class SQLCompiler(Compiled): @util.memoized_property def _within_exec_param_key_getter(self) -> Callable[[Any], str]: getter = self._get_bind_name_for_col - if self.escaped_bind_names: - - def _get(obj): - key = getter(obj) - return self.escaped_bind_names.get(key, key) - - return _get - else: - return getter + return getter @util.memoized_property @util.preload_module("sqlalchemy.engine.result") |