diff options
Diffstat (limited to 'lib/sqlalchemy')
| -rw-r--r-- | lib/sqlalchemy/dialects/oracle/cx_oracle.py | 36 | ||||
| -rw-r--r-- | lib/sqlalchemy/testing/suite/test_dialect.py | 9 |
2 files changed, 45 insertions, 0 deletions
diff --git a/lib/sqlalchemy/dialects/oracle/cx_oracle.py b/lib/sqlalchemy/dialects/oracle/cx_oracle.py index 4571f51f7..24262c181 100644 --- a/lib/sqlalchemy/dialects/oracle/cx_oracle.py +++ b/lib/sqlalchemy/dialects/oracle/cx_oracle.py @@ -445,6 +445,15 @@ from ...sql._typing import is_sql_compiler _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/ @@ -693,6 +702,10 @@ class OracleCompiler_cx_oracle(OracleCompiler): 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 @@ -703,6 +716,29 @@ class OracleCompiler_cx_oracle(OracleCompiler): 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) diff --git a/lib/sqlalchemy/testing/suite/test_dialect.py b/lib/sqlalchemy/testing/suite/test_dialect.py index efad81930..33e395c48 100644 --- a/lib/sqlalchemy/testing/suite/test_dialect.py +++ b/lib/sqlalchemy/testing/suite/test_dialect.py @@ -379,6 +379,8 @@ class DifficultParametersTest(fixtures.TestBase): ("par(ens)",), ("percent%(ens)yah",), ("col:ons",), + ("_starts_with_underscore",), + ("dot.s",), ("more :: %colons%",), ("/slashes/",), ("more/slashes",), @@ -414,6 +416,13 @@ class DifficultParametersTest(fixtures.TestBase): # 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""" |
