--- /dev/null
+.. change::
+ :tags: bug, oracle
+ :tickets: 8708
+
+ Fixed issue where bound parameter names, including those automatically
+ derived from similarly-named database columns, which contained characters
+ that normally require quoting with Oracle would not be escaped when using
+ "expanding parameters" with the Oracle dialect, causing execution errors.
+ The usual "quoting" for bound parameters used by the Oracle dialect is not
+ used with the "expanding parameters" architecture, so escaping for a large
+ range of characters is used instead, now using a list of characters/escapes
+ that are specific to Oracle.
+
+
_CX_ORACLE_MAGIC_LOB_SIZE = 131072
+_ORACLE_BIND_TRANSLATE_RE = re.compile(r"[%\(\):\[\]\.\/\?]")
+
+# Oracle bind names can't start with digits or underscores.
+# currently we rely upon Oracle-specific quoting of bind names in most cases.
+# however for expanding params, the escape chars are used.
+# see #8708
+_ORACLE_BIND_TRANSLATE_CHARS = dict(zip("%():[]./?", "PAZCCCCCCC"))
+
+
class _OracleInteger(sqltypes.Integer):
def get_dbapi_type(self, dbapi):
# see https://github.com/oracle/python-cx_Oracle/issues/
quote is True
or quote is not False
and self.preparer._bindparam_requires_quotes(name)
+ # bind param quoting for Oracle doesn't work with post_compile
+ # params. For those, the default bindparam_string will escape
+ # special chars, and the appending of a number "_1" etc. will
+ # take care of reserved words
and not kw.get("post_compile", False)
):
# interesting to note about expanding parameters - since the
quoted_name = '"%s"' % name
kw["escaped_from"] = name
name = quoted_name
+ return OracleCompiler.bindparam_string(self, name, **kw)
+
+ # TODO: we could likely do away with quoting altogether for
+ # Oracle parameters and use the custom escaping here
+ escaped_from = kw.get("escaped_from", None)
+ if not escaped_from:
+
+ if _ORACLE_BIND_TRANSLATE_RE.search(name):
+ # not quite the translate use case as we want to
+ # also get a quick boolean if we even found
+ # unusual characters in the name
+ new_name = _ORACLE_BIND_TRANSLATE_RE.sub(
+ lambda m: _ORACLE_BIND_TRANSLATE_CHARS[m.group(0)],
+ name,
+ )
+ if new_name[0].isdigit():
+ new_name = "D" + new_name
+ kw["escaped_from"] = name
+ name = new_name
+ elif name[0].isdigit():
+ new_name = "D" + name
+ kw["escaped_from"] = name
+ name = new_name
return OracleCompiler.bindparam_string(self, name, **kw)
("par(ens)",),
("percent%(ens)yah",),
("col:ons",),
+ ("_starts_with_underscore",),
+ ("dot.s",),
("more :: %colons%",),
("/slashes/",),
("more/slashes",),
# name works as the key from cursor.description
eq_(row._mapping[name], "some name")
+ # use expanding IN
+ stmt = select(t.c[name]).where(
+ t.c[name].in_(["some name", "some other_name"])
+ )
+
+ row = connection.execute(stmt).first()
+
class ReturningGuardsTest(fixtures.TablesTest):
"""test that the various 'returning' flags are set appropriately"""