summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/dialects/oracle/cx_oracle.py36
-rw-r--r--lib/sqlalchemy/testing/suite/test_dialect.py9
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"""