You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1328 lines
48KB

  1. # Copyright (C) 2005-2021 the SQLAlchemy authors and contributors
  2. # <see AUTHORS file>
  3. #
  4. # This module is part of SQLAlchemy and is released under
  5. # the MIT License: http://www.opensource.org/licenses/mit-license.php
  6. r"""
  7. .. dialect:: oracle+cx_oracle
  8. :name: cx-Oracle
  9. :dbapi: cx_oracle
  10. :connectstring: oracle+cx_oracle://user:pass@host:port/dbname[?key=value&key=value...]
  11. :url: https://oracle.github.io/python-cx_Oracle/
  12. DSN vs. Hostname connections
  13. -----------------------------
  14. The dialect will connect to a DSN if no database name portion is presented,
  15. such as::
  16. engine = create_engine("oracle+cx_oracle://scott:tiger@oracle1120/?encoding=UTF-8&nencoding=UTF-8")
  17. Above, ``oracle1120`` is passed to cx_Oracle as an Oracle datasource name.
  18. Alternatively, if a database name is present, the ``cx_Oracle.makedsn()``
  19. function is used to create an ad-hoc "datasource" name assuming host
  20. and port::
  21. engine = create_engine("oracle+cx_oracle://scott:tiger@hostname:1521/dbname?encoding=UTF-8&nencoding=UTF-8")
  22. Above, the DSN would be created as follows::
  23. >>> import cx_Oracle
  24. >>> cx_Oracle.makedsn("hostname", 1521, sid="dbname")
  25. '(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=hostname)(PORT=1521))(CONNECT_DATA=(SID=dbname)))'
  26. The ``service_name`` parameter, also consumed by ``cx_Oracle.makedsn()``, may
  27. be specified in the URL query string, e.g. ``?service_name=my_service``.
  28. Passing cx_Oracle connect arguments
  29. -----------------------------------
  30. Additional connection arguments can usually be passed via the URL
  31. query string; particular symbols like ``cx_Oracle.SYSDBA`` are intercepted
  32. and converted to the correct symbol::
  33. e = create_engine(
  34. "oracle+cx_oracle://user:pass@dsn?encoding=UTF-8&nencoding=UTF-8&mode=SYSDBA&events=true")
  35. .. versionchanged:: 1.3 the cx_oracle dialect now accepts all argument names
  36. within the URL string itself, to be passed to the cx_Oracle DBAPI. As
  37. was the case earlier but not correctly documented, the
  38. :paramref:`_sa.create_engine.connect_args` parameter also accepts all
  39. cx_Oracle DBAPI connect arguments.
  40. To pass arguments directly to ``.connect()`` without using the query
  41. string, use the :paramref:`_sa.create_engine.connect_args` dictionary.
  42. Any cx_Oracle parameter value and/or constant may be passed, such as::
  43. import cx_Oracle
  44. e = create_engine(
  45. "oracle+cx_oracle://user:pass@dsn",
  46. connect_args={
  47. "encoding": "UTF-8",
  48. "nencoding": "UTF-8",
  49. "mode": cx_Oracle.SYSDBA,
  50. "events": True
  51. }
  52. )
  53. Options consumed by the SQLAlchemy cx_Oracle dialect outside of the driver
  54. --------------------------------------------------------------------------
  55. There are also options that are consumed by the SQLAlchemy cx_oracle dialect
  56. itself. These options are always passed directly to :func:`_sa.create_engine`
  57. , such as::
  58. e = create_engine(
  59. "oracle+cx_oracle://user:pass@dsn", coerce_to_unicode=False)
  60. The parameters accepted by the cx_oracle dialect are as follows:
  61. * ``arraysize`` - set the cx_oracle.arraysize value on cursors, defaulted
  62. to 50. This setting is significant with cx_Oracle as the contents of LOB
  63. objects are only readable within a "live" row (e.g. within a batch of
  64. 50 rows).
  65. * ``auto_convert_lobs`` - defaults to True; See :ref:`cx_oracle_lob`.
  66. * ``coerce_to_unicode`` - see :ref:`cx_oracle_unicode` for detail.
  67. * ``coerce_to_decimal`` - see :ref:`cx_oracle_numeric` for detail.
  68. * ``encoding_errors`` - see :ref:`cx_oracle_unicode_encoding_errors` for detail.
  69. .. _cx_oracle_sessionpool:
  70. Using cx_Oracle SessionPool
  71. ---------------------------
  72. The cx_Oracle library provides its own connectivity services that may be
  73. used in place of SQLAlchemy's pooling functionality. This can be achieved
  74. by using the :paramref:`_sa.create_engine.creator` parameter to provide a
  75. function that returns a new connection, along with setting
  76. :paramref:`_sa.create_engine.pool_class` to ``NullPool`` to disable
  77. SQLAlchemy's pooling::
  78. import cx_Oracle
  79. from sqlalchemy import create_engine
  80. from sqlalchemy.pool import NullPool
  81. pool = cx_Oracle.SessionPool(
  82. user="scott", password="tiger", dsn="oracle1120",
  83. min=2, max=5, increment=1, threaded=True
  84. )
  85. engine = create_engine("oracle://", creator=pool.acquire, poolclass=NullPool)
  86. The above engine may then be used normally where cx_Oracle's pool handles
  87. connection pooling::
  88. with engine.connect() as conn:
  89. print(conn.scalar("select 1 FROM dual"))
  90. .. _cx_oracle_unicode:
  91. Unicode
  92. -------
  93. As is the case for all DBAPIs under Python 3, all strings are inherently
  94. Unicode strings. Under Python 2, cx_Oracle also supports Python Unicode
  95. objects directly. In all cases however, the driver requires an explicit
  96. encoding configuration.
  97. Ensuring the Correct Client Encoding
  98. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  99. The long accepted standard for establishing client encoding for nearly all
  100. Oracle related software is via the `NLS_LANG <https://www.oracle.com/database/technologies/faq-nls-lang.html>`_
  101. environment variable. cx_Oracle like most other Oracle drivers will use
  102. this environment variable as the source of its encoding configuration. The
  103. format of this variable is idiosyncratic; a typical value would be
  104. ``AMERICAN_AMERICA.AL32UTF8``.
  105. The cx_Oracle driver also supports a programmatic alternative which is to
  106. pass the ``encoding`` and ``nencoding`` parameters directly to its
  107. ``.connect()`` function. These can be present in the URL as follows::
  108. engine = create_engine("oracle+cx_oracle://scott:tiger@oracle1120/?encoding=UTF-8&nencoding=UTF-8")
  109. For the meaning of the ``encoding`` and ``nencoding`` parameters, please
  110. consult
  111. `Characters Sets and National Language Support (NLS) <https://cx-oracle.readthedocs.io/en/latest/user_guide/globalization.html#globalization>`_.
  112. .. seealso::
  113. `Characters Sets and National Language Support (NLS) <https://cx-oracle.readthedocs.io/en/latest/user_guide/globalization.html#globalization>`_
  114. - in the cx_Oracle documentation.
  115. Unicode-specific Column datatypes
  116. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  117. The Core expression language handles unicode data by use of the :class:`.Unicode`
  118. and :class:`.UnicodeText`
  119. datatypes. These types correspond to the VARCHAR2 and CLOB Oracle datatypes by
  120. default. When using these datatypes with Unicode data, it is expected that
  121. the Oracle database is configured with a Unicode-aware character set, as well
  122. as that the ``NLS_LANG`` environment variable is set appropriately, so that
  123. the VARCHAR2 and CLOB datatypes can accommodate the data.
  124. In the case that the Oracle database is not configured with a Unicode character
  125. set, the two options are to use the :class:`_types.NCHAR` and
  126. :class:`_oracle.NCLOB` datatypes explicitly, or to pass the flag
  127. ``use_nchar_for_unicode=True`` to :func:`_sa.create_engine`,
  128. which will cause the
  129. SQLAlchemy dialect to use NCHAR/NCLOB for the :class:`.Unicode` /
  130. :class:`.UnicodeText` datatypes instead of VARCHAR/CLOB.
  131. .. versionchanged:: 1.3 The :class:`.Unicode` and :class:`.UnicodeText`
  132. datatypes now correspond to the ``VARCHAR2`` and ``CLOB`` Oracle datatypes
  133. unless the ``use_nchar_for_unicode=True`` is passed to the dialect
  134. when :func:`_sa.create_engine` is called.
  135. Unicode Coercion of result rows under Python 2
  136. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  137. When result sets are fetched that include strings, under Python 3 the cx_Oracle
  138. DBAPI returns all strings as Python Unicode objects, since Python 3 only has a
  139. Unicode string type. This occurs for data fetched from datatypes such as
  140. VARCHAR2, CHAR, CLOB, NCHAR, NCLOB, etc. In order to provide cross-
  141. compatibility under Python 2, the SQLAlchemy cx_Oracle dialect will add
  142. Unicode-conversion to string data under Python 2 as well. Historically, this
  143. made use of converters that were supplied by cx_Oracle but were found to be
  144. non-performant; SQLAlchemy's own converters are used for the string to Unicode
  145. conversion under Python 2. To disable the Python 2 Unicode conversion for
  146. VARCHAR2, CHAR, and CLOB, the flag ``coerce_to_unicode=False`` can be passed to
  147. :func:`_sa.create_engine`.
  148. .. versionchanged:: 1.3 Unicode conversion is applied to all string values
  149. by default under python 2. The ``coerce_to_unicode`` now defaults to True
  150. and can be set to False to disable the Unicode coercion of strings that are
  151. delivered as VARCHAR2/CHAR/CLOB data.
  152. .. _cx_oracle_unicode_encoding_errors:
  153. Encoding Errors
  154. ^^^^^^^^^^^^^^^
  155. For the unusual case that data in the Oracle database is present with a broken
  156. encoding, the dialect accepts a parameter ``encoding_errors`` which will be
  157. passed to Unicode decoding functions in order to affect how decoding errors are
  158. handled. The value is ultimately consumed by the Python `decode
  159. <https://docs.python.org/3/library/stdtypes.html#bytes.decode>`_ function, and
  160. is passed both via cx_Oracle's ``encodingErrors`` parameter consumed by
  161. ``Cursor.var()``, as well as SQLAlchemy's own decoding function, as the
  162. cx_Oracle dialect makes use of both under different circumstances.
  163. .. versionadded:: 1.3.11
  164. .. _cx_oracle_setinputsizes:
  165. Fine grained control over cx_Oracle data binding performance with setinputsizes
  166. -------------------------------------------------------------------------------
  167. The cx_Oracle DBAPI has a deep and fundamental reliance upon the usage of the
  168. DBAPI ``setinputsizes()`` call. The purpose of this call is to establish the
  169. datatypes that are bound to a SQL statement for Python values being passed as
  170. parameters. While virtually no other DBAPI assigns any use to the
  171. ``setinputsizes()`` call, the cx_Oracle DBAPI relies upon it heavily in its
  172. interactions with the Oracle client interface, and in some scenarios it is not
  173. possible for SQLAlchemy to know exactly how data should be bound, as some
  174. settings can cause profoundly different performance characteristics, while
  175. altering the type coercion behavior at the same time.
  176. Users of the cx_Oracle dialect are **strongly encouraged** to read through
  177. cx_Oracle's list of built-in datatype symbols at
  178. http://cx-oracle.readthedocs.io/en/latest/module.html#database-types.
  179. Note that in some cases, significant performance degradation can occur when
  180. using these types vs. not, in particular when specifying ``cx_Oracle.CLOB``.
  181. On the SQLAlchemy side, the :meth:`.DialectEvents.do_setinputsizes` event can
  182. be used both for runtime visibility (e.g. logging) of the setinputsizes step as
  183. well as to fully control how ``setinputsizes()`` is used on a per-statement
  184. basis.
  185. .. versionadded:: 1.2.9 Added :meth:`.DialectEvents.setinputsizes`
  186. Example 1 - logging all setinputsizes calls
  187. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  188. The following example illustrates how to log the intermediary values from a
  189. SQLAlchemy perspective before they are converted to the raw ``setinputsizes()``
  190. parameter dictionary. The keys of the dictionary are :class:`.BindParameter`
  191. objects which have a ``.key`` and a ``.type`` attribute::
  192. from sqlalchemy import create_engine, event
  193. engine = create_engine("oracle+cx_oracle://scott:tiger@host/xe")
  194. @event.listens_for(engine, "do_setinputsizes")
  195. def _log_setinputsizes(inputsizes, cursor, statement, parameters, context):
  196. for bindparam, dbapitype in inputsizes.items():
  197. log.info(
  198. "Bound parameter name: %s SQLAlchemy type: %r "
  199. "DBAPI object: %s",
  200. bindparam.key, bindparam.type, dbapitype)
  201. Example 2 - remove all bindings to CLOB
  202. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  203. The ``CLOB`` datatype in cx_Oracle incurs a significant performance overhead,
  204. however is set by default for the ``Text`` type within the SQLAlchemy 1.2
  205. series. This setting can be modified as follows::
  206. from sqlalchemy import create_engine, event
  207. from cx_Oracle import CLOB
  208. engine = create_engine("oracle+cx_oracle://scott:tiger@host/xe")
  209. @event.listens_for(engine, "do_setinputsizes")
  210. def _remove_clob(inputsizes, cursor, statement, parameters, context):
  211. for bindparam, dbapitype in list(inputsizes.items()):
  212. if dbapitype is CLOB:
  213. del inputsizes[bindparam]
  214. .. _cx_oracle_returning:
  215. RETURNING Support
  216. -----------------
  217. The cx_Oracle dialect implements RETURNING using OUT parameters.
  218. The dialect supports RETURNING fully, however cx_Oracle 6 is recommended
  219. for complete support.
  220. .. _cx_oracle_lob:
  221. LOB Objects
  222. -----------
  223. cx_oracle returns oracle LOBs using the cx_oracle.LOB object. SQLAlchemy
  224. converts these to strings so that the interface of the Binary type is
  225. consistent with that of other backends, which takes place within a cx_Oracle
  226. outputtypehandler.
  227. cx_Oracle prior to version 6 would require that LOB objects be read before
  228. a new batch of rows would be read, as determined by the ``cursor.arraysize``.
  229. As of the 6 series, this limitation has been lifted. Nevertheless, because
  230. SQLAlchemy pre-reads these LOBs up front, this issue is avoided in any case.
  231. To disable the auto "read()" feature of the dialect, the flag
  232. ``auto_convert_lobs=False`` may be passed to :func:`_sa.create_engine`. Under
  233. the cx_Oracle 5 series, having this flag turned off means there is the chance
  234. of reading from a stale LOB object if not read as it is fetched. With
  235. cx_Oracle 6, this issue is resolved.
  236. .. versionchanged:: 1.2 the LOB handling system has been greatly simplified
  237. internally to make use of outputtypehandlers, and no longer makes use
  238. of alternate "buffered" result set objects.
  239. Two Phase Transactions Not Supported
  240. -------------------------------------
  241. Two phase transactions are **not supported** under cx_Oracle due to poor
  242. driver support. As of cx_Oracle 6.0b1, the interface for
  243. two phase transactions has been changed to be more of a direct pass-through
  244. to the underlying OCI layer with less automation. The additional logic
  245. to support this system is not implemented in SQLAlchemy.
  246. .. _cx_oracle_numeric:
  247. Precision Numerics
  248. ------------------
  249. SQLAlchemy's numeric types can handle receiving and returning values as Python
  250. ``Decimal`` objects or float objects. When a :class:`.Numeric` object, or a
  251. subclass such as :class:`.Float`, :class:`_oracle.DOUBLE_PRECISION` etc. is in
  252. use, the :paramref:`.Numeric.asdecimal` flag determines if values should be
  253. coerced to ``Decimal`` upon return, or returned as float objects. To make
  254. matters more complicated under Oracle, Oracle's ``NUMBER`` type can also
  255. represent integer values if the "scale" is zero, so the Oracle-specific
  256. :class:`_oracle.NUMBER` type takes this into account as well.
  257. The cx_Oracle dialect makes extensive use of connection- and cursor-level
  258. "outputtypehandler" callables in order to coerce numeric values as requested.
  259. These callables are specific to the specific flavor of :class:`.Numeric` in
  260. use, as well as if no SQLAlchemy typing objects are present. There are
  261. observed scenarios where Oracle may sends incomplete or ambiguous information
  262. about the numeric types being returned, such as a query where the numeric types
  263. are buried under multiple levels of subquery. The type handlers do their best
  264. to make the right decision in all cases, deferring to the underlying cx_Oracle
  265. DBAPI for all those cases where the driver can make the best decision.
  266. When no typing objects are present, as when executing plain SQL strings, a
  267. default "outputtypehandler" is present which will generally return numeric
  268. values which specify precision and scale as Python ``Decimal`` objects. To
  269. disable this coercion to decimal for performance reasons, pass the flag
  270. ``coerce_to_decimal=False`` to :func:`_sa.create_engine`::
  271. engine = create_engine("oracle+cx_oracle://dsn", coerce_to_decimal=False)
  272. The ``coerce_to_decimal`` flag only impacts the results of plain string
  273. SQL statements that are not otherwise associated with a :class:`.Numeric`
  274. SQLAlchemy type (or a subclass of such).
  275. .. versionchanged:: 1.2 The numeric handling system for cx_Oracle has been
  276. reworked to take advantage of newer cx_Oracle features as well
  277. as better integration of outputtypehandlers.
  278. """ # noqa
  279. from __future__ import absolute_import
  280. import decimal
  281. import random
  282. import re
  283. from . import base as oracle
  284. from .base import OracleCompiler
  285. from .base import OracleDialect
  286. from .base import OracleExecutionContext
  287. from ... import exc
  288. from ... import processors
  289. from ... import types as sqltypes
  290. from ... import util
  291. from ...engine import cursor as _cursor
  292. from ...util import compat
  293. class _OracleInteger(sqltypes.Integer):
  294. def get_dbapi_type(self, dbapi):
  295. # see https://github.com/oracle/python-cx_Oracle/issues/
  296. # 208#issuecomment-409715955
  297. return int
  298. def _cx_oracle_var(self, dialect, cursor):
  299. cx_Oracle = dialect.dbapi
  300. return cursor.var(
  301. cx_Oracle.STRING, 255, arraysize=cursor.arraysize, outconverter=int
  302. )
  303. def _cx_oracle_outputtypehandler(self, dialect):
  304. def handler(cursor, name, default_type, size, precision, scale):
  305. return self._cx_oracle_var(dialect, cursor)
  306. return handler
  307. class _OracleNumeric(sqltypes.Numeric):
  308. is_number = False
  309. def bind_processor(self, dialect):
  310. if self.scale == 0:
  311. return None
  312. elif self.asdecimal:
  313. processor = processors.to_decimal_processor_factory(
  314. decimal.Decimal, self._effective_decimal_return_scale
  315. )
  316. def process(value):
  317. if isinstance(value, (int, float)):
  318. return processor(value)
  319. elif value is not None and value.is_infinite():
  320. return float(value)
  321. else:
  322. return value
  323. return process
  324. else:
  325. return processors.to_float
  326. def result_processor(self, dialect, coltype):
  327. return None
  328. def _cx_oracle_outputtypehandler(self, dialect):
  329. cx_Oracle = dialect.dbapi
  330. is_cx_oracle_6 = dialect._is_cx_oracle_6
  331. def handler(cursor, name, default_type, size, precision, scale):
  332. outconverter = None
  333. if precision:
  334. if self.asdecimal:
  335. if default_type == cx_Oracle.NATIVE_FLOAT:
  336. # receiving float and doing Decimal after the fact
  337. # allows for float("inf") to be handled
  338. type_ = default_type
  339. outconverter = decimal.Decimal
  340. elif is_cx_oracle_6:
  341. type_ = decimal.Decimal
  342. else:
  343. type_ = cx_Oracle.STRING
  344. outconverter = dialect._to_decimal
  345. else:
  346. if self.is_number and scale == 0:
  347. # integer. cx_Oracle is observed to handle the widest
  348. # variety of ints when no directives are passed,
  349. # from 5.2 to 7.0. See [ticket:4457]
  350. return None
  351. else:
  352. type_ = cx_Oracle.NATIVE_FLOAT
  353. else:
  354. if self.asdecimal:
  355. if default_type == cx_Oracle.NATIVE_FLOAT:
  356. type_ = default_type
  357. outconverter = decimal.Decimal
  358. elif is_cx_oracle_6:
  359. type_ = decimal.Decimal
  360. else:
  361. type_ = cx_Oracle.STRING
  362. outconverter = dialect._to_decimal
  363. else:
  364. if self.is_number and scale == 0:
  365. # integer. cx_Oracle is observed to handle the widest
  366. # variety of ints when no directives are passed,
  367. # from 5.2 to 7.0. See [ticket:4457]
  368. return None
  369. else:
  370. type_ = cx_Oracle.NATIVE_FLOAT
  371. return cursor.var(
  372. type_,
  373. 255,
  374. arraysize=cursor.arraysize,
  375. outconverter=outconverter,
  376. )
  377. return handler
  378. class _OracleBinaryFloat(_OracleNumeric):
  379. def get_dbapi_type(self, dbapi):
  380. return dbapi.NATIVE_FLOAT
  381. class _OracleBINARY_FLOAT(_OracleBinaryFloat, oracle.BINARY_FLOAT):
  382. pass
  383. class _OracleBINARY_DOUBLE(_OracleBinaryFloat, oracle.BINARY_DOUBLE):
  384. pass
  385. class _OracleNUMBER(_OracleNumeric):
  386. is_number = True
  387. class _OracleDate(sqltypes.Date):
  388. def bind_processor(self, dialect):
  389. return None
  390. def result_processor(self, dialect, coltype):
  391. def process(value):
  392. if value is not None:
  393. return value.date()
  394. else:
  395. return value
  396. return process
  397. # TODO: the names used across CHAR / VARCHAR / NCHAR / NVARCHAR
  398. # here are inconsistent and not very good
  399. class _OracleChar(sqltypes.CHAR):
  400. def get_dbapi_type(self, dbapi):
  401. return dbapi.FIXED_CHAR
  402. class _OracleNChar(sqltypes.NCHAR):
  403. def get_dbapi_type(self, dbapi):
  404. return dbapi.FIXED_NCHAR
  405. class _OracleUnicodeStringNCHAR(oracle.NVARCHAR2):
  406. def get_dbapi_type(self, dbapi):
  407. return dbapi.NCHAR
  408. class _OracleUnicodeStringCHAR(sqltypes.Unicode):
  409. def get_dbapi_type(self, dbapi):
  410. return dbapi.LONG_STRING
  411. class _OracleUnicodeTextNCLOB(oracle.NCLOB):
  412. def get_dbapi_type(self, dbapi):
  413. return dbapi.NCLOB
  414. class _OracleUnicodeTextCLOB(sqltypes.UnicodeText):
  415. def get_dbapi_type(self, dbapi):
  416. return dbapi.CLOB
  417. class _OracleText(sqltypes.Text):
  418. def get_dbapi_type(self, dbapi):
  419. return dbapi.CLOB
  420. class _OracleLong(oracle.LONG):
  421. def get_dbapi_type(self, dbapi):
  422. return dbapi.LONG_STRING
  423. class _OracleString(sqltypes.String):
  424. pass
  425. class _OracleEnum(sqltypes.Enum):
  426. def bind_processor(self, dialect):
  427. enum_proc = sqltypes.Enum.bind_processor(self, dialect)
  428. def process(value):
  429. raw_str = enum_proc(value)
  430. return raw_str
  431. return process
  432. class _OracleBinary(sqltypes.LargeBinary):
  433. def get_dbapi_type(self, dbapi):
  434. return dbapi.BLOB
  435. def bind_processor(self, dialect):
  436. return None
  437. def result_processor(self, dialect, coltype):
  438. if not dialect.auto_convert_lobs:
  439. return None
  440. else:
  441. return super(_OracleBinary, self).result_processor(
  442. dialect, coltype
  443. )
  444. class _OracleInterval(oracle.INTERVAL):
  445. def get_dbapi_type(self, dbapi):
  446. return dbapi.INTERVAL
  447. class _OracleRaw(oracle.RAW):
  448. pass
  449. class _OracleRowid(oracle.ROWID):
  450. def get_dbapi_type(self, dbapi):
  451. return dbapi.ROWID
  452. class OracleCompiler_cx_oracle(OracleCompiler):
  453. _oracle_cx_sql_compiler = True
  454. def bindparam_string(self, name, **kw):
  455. quote = getattr(name, "quote", None)
  456. if (
  457. quote is True
  458. or quote is not False
  459. and self.preparer._bindparam_requires_quotes(name)
  460. and not kw.get("post_compile", False)
  461. ):
  462. # interesting to note about expanding parameters - since the
  463. # new parameters take the form <paramname>_<int>, at least if
  464. # they are originally formed from reserved words, they no longer
  465. # need quoting :). names that include illegal characters
  466. # won't work however.
  467. quoted_name = '"%s"' % name
  468. kw["escaped_from"] = name
  469. name = quoted_name
  470. return OracleCompiler.bindparam_string(self, name, **kw)
  471. class OracleExecutionContext_cx_oracle(OracleExecutionContext):
  472. out_parameters = None
  473. def _generate_out_parameter_vars(self):
  474. # check for has_out_parameters or RETURNING, create cx_Oracle.var
  475. # objects if so
  476. if self.compiled.returning or self.compiled.has_out_parameters:
  477. quoted_bind_names = self.compiled.escaped_bind_names
  478. for bindparam in self.compiled.binds.values():
  479. if bindparam.isoutparam:
  480. name = self.compiled.bind_names[bindparam]
  481. type_impl = bindparam.type.dialect_impl(self.dialect)
  482. if hasattr(type_impl, "_cx_oracle_var"):
  483. self.out_parameters[name] = type_impl._cx_oracle_var(
  484. self.dialect, self.cursor
  485. )
  486. else:
  487. dbtype = type_impl.get_dbapi_type(self.dialect.dbapi)
  488. cx_Oracle = self.dialect.dbapi
  489. if dbtype is None:
  490. raise exc.InvalidRequestError(
  491. "Cannot create out parameter for "
  492. "parameter "
  493. "%r - its type %r is not supported by"
  494. " cx_oracle" % (bindparam.key, bindparam.type)
  495. )
  496. if compat.py2k and dbtype in (
  497. cx_Oracle.CLOB,
  498. cx_Oracle.NCLOB,
  499. ):
  500. outconverter = (
  501. processors.to_unicode_processor_factory(
  502. self.dialect.encoding,
  503. errors=self.dialect.encoding_errors,
  504. )
  505. )
  506. self.out_parameters[name] = self.cursor.var(
  507. dbtype,
  508. outconverter=lambda value: outconverter(
  509. value.read()
  510. ),
  511. )
  512. elif dbtype in (
  513. cx_Oracle.BLOB,
  514. cx_Oracle.CLOB,
  515. cx_Oracle.NCLOB,
  516. ):
  517. self.out_parameters[name] = self.cursor.var(
  518. dbtype, outconverter=lambda value: value.read()
  519. )
  520. elif compat.py2k and isinstance(
  521. type_impl, sqltypes.Unicode
  522. ):
  523. outconverter = (
  524. processors.to_unicode_processor_factory(
  525. self.dialect.encoding,
  526. errors=self.dialect.encoding_errors,
  527. )
  528. )
  529. self.out_parameters[name] = self.cursor.var(
  530. dbtype, outconverter=outconverter
  531. )
  532. else:
  533. self.out_parameters[name] = self.cursor.var(dbtype)
  534. self.parameters[0][
  535. quoted_bind_names.get(name, name)
  536. ] = self.out_parameters[name]
  537. def _generate_cursor_outputtype_handler(self):
  538. output_handlers = {}
  539. for (keyname, name, objects, type_) in self.compiled._result_columns:
  540. handler = type_._cached_custom_processor(
  541. self.dialect,
  542. "cx_oracle_outputtypehandler",
  543. self._get_cx_oracle_type_handler,
  544. )
  545. if handler:
  546. denormalized_name = self.dialect.denormalize_name(keyname)
  547. output_handlers[denormalized_name] = handler
  548. if output_handlers:
  549. default_handler = self._dbapi_connection.outputtypehandler
  550. def output_type_handler(
  551. cursor, name, default_type, size, precision, scale
  552. ):
  553. if name in output_handlers:
  554. return output_handlers[name](
  555. cursor, name, default_type, size, precision, scale
  556. )
  557. else:
  558. return default_handler(
  559. cursor, name, default_type, size, precision, scale
  560. )
  561. self.cursor.outputtypehandler = output_type_handler
  562. def _get_cx_oracle_type_handler(self, impl):
  563. if hasattr(impl, "_cx_oracle_outputtypehandler"):
  564. return impl._cx_oracle_outputtypehandler(self.dialect)
  565. else:
  566. return None
  567. def pre_exec(self):
  568. if not getattr(self.compiled, "_oracle_cx_sql_compiler", False):
  569. return
  570. self.out_parameters = {}
  571. self._generate_out_parameter_vars()
  572. self._generate_cursor_outputtype_handler()
  573. self.include_set_input_sizes = self.dialect._include_setinputsizes
  574. def post_exec(self):
  575. if self.compiled and self.out_parameters and self.compiled.returning:
  576. # create a fake cursor result from the out parameters. unlike
  577. # get_out_parameter_values(), the result-row handlers here will be
  578. # applied at the Result level
  579. returning_params = [
  580. self.dialect._returningval(self.out_parameters["ret_%d" % i])
  581. for i in range(len(self.out_parameters))
  582. ]
  583. fetch_strategy = _cursor.FullyBufferedCursorFetchStrategy(
  584. self.cursor,
  585. [
  586. (getattr(col, "name", col._anon_name_label), None)
  587. for col in self.compiled.returning
  588. ],
  589. initial_buffer=[tuple(returning_params)],
  590. )
  591. self.cursor_fetch_strategy = fetch_strategy
  592. def create_cursor(self):
  593. c = self._dbapi_connection.cursor()
  594. if self.dialect.arraysize:
  595. c.arraysize = self.dialect.arraysize
  596. return c
  597. def get_out_parameter_values(self, out_param_names):
  598. # this method should not be called when the compiler has
  599. # RETURNING as we've turned the has_out_parameters flag set to
  600. # False.
  601. assert not self.compiled.returning
  602. return [
  603. self.dialect._paramval(self.out_parameters[name])
  604. for name in out_param_names
  605. ]
  606. class OracleDialect_cx_oracle(OracleDialect):
  607. supports_statement_cache = True
  608. execution_ctx_cls = OracleExecutionContext_cx_oracle
  609. statement_compiler = OracleCompiler_cx_oracle
  610. supports_sane_rowcount = True
  611. supports_sane_multi_rowcount = True
  612. supports_unicode_statements = True
  613. supports_unicode_binds = True
  614. use_setinputsizes = True
  615. driver = "cx_oracle"
  616. colspecs = {
  617. sqltypes.Numeric: _OracleNumeric,
  618. sqltypes.Float: _OracleNumeric,
  619. oracle.BINARY_FLOAT: _OracleBINARY_FLOAT,
  620. oracle.BINARY_DOUBLE: _OracleBINARY_DOUBLE,
  621. sqltypes.Integer: _OracleInteger,
  622. oracle.NUMBER: _OracleNUMBER,
  623. sqltypes.Date: _OracleDate,
  624. sqltypes.LargeBinary: _OracleBinary,
  625. sqltypes.Boolean: oracle._OracleBoolean,
  626. sqltypes.Interval: _OracleInterval,
  627. oracle.INTERVAL: _OracleInterval,
  628. sqltypes.Text: _OracleText,
  629. sqltypes.String: _OracleString,
  630. sqltypes.UnicodeText: _OracleUnicodeTextCLOB,
  631. sqltypes.CHAR: _OracleChar,
  632. sqltypes.NCHAR: _OracleNChar,
  633. sqltypes.Enum: _OracleEnum,
  634. oracle.LONG: _OracleLong,
  635. oracle.RAW: _OracleRaw,
  636. sqltypes.Unicode: _OracleUnicodeStringCHAR,
  637. sqltypes.NVARCHAR: _OracleUnicodeStringNCHAR,
  638. oracle.NCLOB: _OracleUnicodeTextNCLOB,
  639. oracle.ROWID: _OracleRowid,
  640. }
  641. execute_sequence_format = list
  642. _cx_oracle_threaded = None
  643. @util.deprecated_params(
  644. threaded=(
  645. "1.3",
  646. "The 'threaded' parameter to the cx_oracle dialect "
  647. "is deprecated as a dialect-level argument, and will be removed "
  648. "in a future release. As of version 1.3, it defaults to False "
  649. "rather than True. The 'threaded' option can be passed to "
  650. "cx_Oracle directly in the URL query string passed to "
  651. ":func:`_sa.create_engine`.",
  652. )
  653. )
  654. def __init__(
  655. self,
  656. auto_convert_lobs=True,
  657. coerce_to_unicode=True,
  658. coerce_to_decimal=True,
  659. arraysize=50,
  660. encoding_errors=None,
  661. threaded=None,
  662. **kwargs
  663. ):
  664. OracleDialect.__init__(self, **kwargs)
  665. self.arraysize = arraysize
  666. self.encoding_errors = encoding_errors
  667. if threaded is not None:
  668. self._cx_oracle_threaded = threaded
  669. self.auto_convert_lobs = auto_convert_lobs
  670. self.coerce_to_unicode = coerce_to_unicode
  671. self.coerce_to_decimal = coerce_to_decimal
  672. if self._use_nchar_for_unicode:
  673. self.colspecs = self.colspecs.copy()
  674. self.colspecs[sqltypes.Unicode] = _OracleUnicodeStringNCHAR
  675. self.colspecs[sqltypes.UnicodeText] = _OracleUnicodeTextNCLOB
  676. cx_Oracle = self.dbapi
  677. if cx_Oracle is None:
  678. self._include_setinputsizes = {}
  679. self.cx_oracle_ver = (0, 0, 0)
  680. else:
  681. self.cx_oracle_ver = self._parse_cx_oracle_ver(cx_Oracle.version)
  682. if self.cx_oracle_ver < (5, 2) and self.cx_oracle_ver > (0, 0, 0):
  683. raise exc.InvalidRequestError(
  684. "cx_Oracle version 5.2 and above are supported"
  685. )
  686. self._include_setinputsizes = {
  687. cx_Oracle.DATETIME,
  688. cx_Oracle.NCLOB,
  689. cx_Oracle.CLOB,
  690. cx_Oracle.LOB,
  691. cx_Oracle.NCHAR,
  692. cx_Oracle.FIXED_NCHAR,
  693. cx_Oracle.BLOB,
  694. cx_Oracle.FIXED_CHAR,
  695. cx_Oracle.TIMESTAMP,
  696. _OracleInteger,
  697. _OracleBINARY_FLOAT,
  698. _OracleBINARY_DOUBLE,
  699. }
  700. self._paramval = lambda value: value.getvalue()
  701. # https://github.com/oracle/python-cx_Oracle/issues/176#issuecomment-386821291
  702. # https://github.com/oracle/python-cx_Oracle/issues/224
  703. self._values_are_lists = self.cx_oracle_ver >= (6, 3)
  704. if self._values_are_lists:
  705. cx_Oracle.__future__.dml_ret_array_val = True
  706. def _returningval(value):
  707. try:
  708. return value.values[0][0]
  709. except IndexError:
  710. return None
  711. self._returningval = _returningval
  712. else:
  713. self._returningval = self._paramval
  714. self._is_cx_oracle_6 = self.cx_oracle_ver >= (6,)
  715. @property
  716. def _cursor_var_unicode_kwargs(self):
  717. if self.encoding_errors:
  718. if self.cx_oracle_ver >= (6, 4):
  719. return {"encodingErrors": self.encoding_errors}
  720. else:
  721. util.warn(
  722. "cx_oracle version %r does not support encodingErrors"
  723. % (self.cx_oracle_ver,)
  724. )
  725. return {}
  726. def _parse_cx_oracle_ver(self, version):
  727. m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", version)
  728. if m:
  729. return tuple(int(x) for x in m.group(1, 2, 3) if x is not None)
  730. else:
  731. return (0, 0, 0)
  732. @classmethod
  733. def dbapi(cls):
  734. import cx_Oracle
  735. return cx_Oracle
  736. def initialize(self, connection):
  737. super(OracleDialect_cx_oracle, self).initialize(connection)
  738. if self._is_oracle_8:
  739. self.supports_unicode_binds = False
  740. self._detect_decimal_char(connection)
  741. def get_isolation_level(self, connection):
  742. # sources:
  743. # general idea of transaction id, have to start one, etc.
  744. # https://stackoverflow.com/questions/10711204/how-to-check-isoloation-level
  745. # how to decode xid cols from v$transaction to match
  746. # https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:9532779900346079444
  747. # Oracle tuple comparison without using IN:
  748. # https://www.sql-workbench.eu/comparison/tuple_comparison.html
  749. with connection.cursor() as cursor:
  750. # this is the only way to ensure a transaction is started without
  751. # actually running DML. There's no way to see the configured
  752. # isolation level without getting it from v$transaction which
  753. # means transaction has to be started.
  754. outval = cursor.var(str)
  755. cursor.execute(
  756. """
  757. begin
  758. :trans_id := dbms_transaction.local_transaction_id( TRUE );
  759. end;
  760. """,
  761. {"trans_id": outval},
  762. )
  763. trans_id = outval.getvalue()
  764. xidusn, xidslot, xidsqn = trans_id.split(".", 2)
  765. cursor.execute(
  766. "SELECT CASE BITAND(t.flag, POWER(2, 28)) "
  767. "WHEN 0 THEN 'READ COMMITTED' "
  768. "ELSE 'SERIALIZABLE' END AS isolation_level "
  769. "FROM v$transaction t WHERE "
  770. "(t.xidusn, t.xidslot, t.xidsqn) = "
  771. "((:xidusn, :xidslot, :xidsqn))",
  772. {"xidusn": xidusn, "xidslot": xidslot, "xidsqn": xidsqn},
  773. )
  774. row = cursor.fetchone()
  775. if row is None:
  776. raise exc.InvalidRequestError(
  777. "could not retrieve isolation level"
  778. )
  779. result = row[0]
  780. return result
  781. def set_isolation_level(self, connection, level):
  782. if hasattr(connection, "connection"):
  783. dbapi_connection = connection.connection
  784. else:
  785. dbapi_connection = connection
  786. if level == "AUTOCOMMIT":
  787. dbapi_connection.autocommit = True
  788. else:
  789. dbapi_connection.autocommit = False
  790. connection.rollback()
  791. with connection.cursor() as cursor:
  792. cursor.execute("ALTER SESSION SET ISOLATION_LEVEL=%s" % level)
  793. def _detect_decimal_char(self, connection):
  794. # we have the option to change this setting upon connect,
  795. # or just look at what it is upon connect and convert.
  796. # to minimize the chance of interference with changes to
  797. # NLS_TERRITORY or formatting behavior of the DB, we opt
  798. # to just look at it
  799. self._decimal_char = connection.exec_driver_sql(
  800. "select value from nls_session_parameters "
  801. "where parameter = 'NLS_NUMERIC_CHARACTERS'"
  802. ).scalar()[0]
  803. if self._decimal_char != ".":
  804. _detect_decimal = self._detect_decimal
  805. _to_decimal = self._to_decimal
  806. self._detect_decimal = lambda value: _detect_decimal(
  807. value.replace(self._decimal_char, ".")
  808. )
  809. self._to_decimal = lambda value: _to_decimal(
  810. value.replace(self._decimal_char, ".")
  811. )
  812. def _detect_decimal(self, value):
  813. if "." in value:
  814. return self._to_decimal(value)
  815. else:
  816. return int(value)
  817. _to_decimal = decimal.Decimal
  818. def _generate_connection_outputtype_handler(self):
  819. """establish the default outputtypehandler established at the
  820. connection level.
  821. """
  822. dialect = self
  823. cx_Oracle = dialect.dbapi
  824. number_handler = _OracleNUMBER(
  825. asdecimal=True
  826. )._cx_oracle_outputtypehandler(dialect)
  827. float_handler = _OracleNUMBER(
  828. asdecimal=False
  829. )._cx_oracle_outputtypehandler(dialect)
  830. def output_type_handler(
  831. cursor, name, default_type, size, precision, scale
  832. ):
  833. if (
  834. default_type == cx_Oracle.NUMBER
  835. and default_type is not cx_Oracle.NATIVE_FLOAT
  836. ):
  837. if not dialect.coerce_to_decimal:
  838. return None
  839. elif precision == 0 and scale in (0, -127):
  840. # ambiguous type, this occurs when selecting
  841. # numbers from deep subqueries
  842. return cursor.var(
  843. cx_Oracle.STRING,
  844. 255,
  845. outconverter=dialect._detect_decimal,
  846. arraysize=cursor.arraysize,
  847. )
  848. elif precision and scale > 0:
  849. return number_handler(
  850. cursor, name, default_type, size, precision, scale
  851. )
  852. else:
  853. return float_handler(
  854. cursor, name, default_type, size, precision, scale
  855. )
  856. # allow all strings to come back natively as Unicode
  857. elif (
  858. dialect.coerce_to_unicode
  859. and default_type
  860. in (
  861. cx_Oracle.STRING,
  862. cx_Oracle.FIXED_CHAR,
  863. )
  864. and default_type is not cx_Oracle.CLOB
  865. and default_type is not cx_Oracle.NCLOB
  866. ):
  867. if compat.py2k:
  868. outconverter = processors.to_unicode_processor_factory(
  869. dialect.encoding, errors=dialect.encoding_errors
  870. )
  871. return cursor.var(
  872. cx_Oracle.STRING,
  873. size,
  874. cursor.arraysize,
  875. outconverter=outconverter,
  876. )
  877. else:
  878. return cursor.var(
  879. util.text_type,
  880. size,
  881. cursor.arraysize,
  882. **dialect._cursor_var_unicode_kwargs
  883. )
  884. elif dialect.auto_convert_lobs and default_type in (
  885. cx_Oracle.CLOB,
  886. cx_Oracle.NCLOB,
  887. ):
  888. if compat.py2k:
  889. outconverter = processors.to_unicode_processor_factory(
  890. dialect.encoding, errors=dialect.encoding_errors
  891. )
  892. return cursor.var(
  893. cx_Oracle.LONG_STRING,
  894. size,
  895. cursor.arraysize,
  896. outconverter=outconverter,
  897. )
  898. else:
  899. return cursor.var(
  900. cx_Oracle.LONG_STRING,
  901. size,
  902. cursor.arraysize,
  903. **dialect._cursor_var_unicode_kwargs
  904. )
  905. elif dialect.auto_convert_lobs and default_type in (
  906. cx_Oracle.BLOB,
  907. ):
  908. return cursor.var(
  909. cx_Oracle.LONG_BINARY,
  910. size,
  911. cursor.arraysize,
  912. )
  913. return output_type_handler
  914. def on_connect(self):
  915. output_type_handler = self._generate_connection_outputtype_handler()
  916. def on_connect(conn):
  917. conn.outputtypehandler = output_type_handler
  918. return on_connect
  919. def create_connect_args(self, url):
  920. opts = dict(url.query)
  921. for opt in ("use_ansi", "auto_convert_lobs"):
  922. if opt in opts:
  923. util.warn_deprecated(
  924. "cx_oracle dialect option %r should only be passed to "
  925. "create_engine directly, not within the URL string" % opt,
  926. version="1.3",
  927. )
  928. util.coerce_kw_type(opts, opt, bool)
  929. setattr(self, opt, opts.pop(opt))
  930. database = url.database
  931. service_name = opts.pop("service_name", None)
  932. if database or service_name:
  933. # if we have a database, then we have a remote host
  934. port = url.port
  935. if port:
  936. port = int(port)
  937. else:
  938. port = 1521
  939. if database and service_name:
  940. raise exc.InvalidRequestError(
  941. '"service_name" option shouldn\'t '
  942. 'be used with a "database" part of the url'
  943. )
  944. if database:
  945. makedsn_kwargs = {"sid": database}
  946. if service_name:
  947. makedsn_kwargs = {"service_name": service_name}
  948. dsn = self.dbapi.makedsn(url.host, port, **makedsn_kwargs)
  949. else:
  950. # we have a local tnsname
  951. dsn = url.host
  952. if dsn is not None:
  953. opts["dsn"] = dsn
  954. if url.password is not None:
  955. opts["password"] = url.password
  956. if url.username is not None:
  957. opts["user"] = url.username
  958. if self._cx_oracle_threaded is not None:
  959. opts.setdefault("threaded", self._cx_oracle_threaded)
  960. def convert_cx_oracle_constant(value):
  961. if isinstance(value, util.string_types):
  962. try:
  963. int_val = int(value)
  964. except ValueError:
  965. value = value.upper()
  966. return getattr(self.dbapi, value)
  967. else:
  968. return int_val
  969. else:
  970. return value
  971. util.coerce_kw_type(opts, "mode", convert_cx_oracle_constant)
  972. util.coerce_kw_type(opts, "threaded", bool)
  973. util.coerce_kw_type(opts, "events", bool)
  974. util.coerce_kw_type(opts, "purity", convert_cx_oracle_constant)
  975. return ([], opts)
  976. def _get_server_version_info(self, connection):
  977. return tuple(int(x) for x in connection.connection.version.split("."))
  978. def is_disconnect(self, e, connection, cursor):
  979. (error,) = e.args
  980. if isinstance(
  981. e, (self.dbapi.InterfaceError, self.dbapi.DatabaseError)
  982. ) and "not connected" in str(e):
  983. return True
  984. if hasattr(error, "code"):
  985. # ORA-00028: your session has been killed
  986. # ORA-03114: not connected to ORACLE
  987. # ORA-03113: end-of-file on communication channel
  988. # ORA-03135: connection lost contact
  989. # ORA-01033: ORACLE initialization or shutdown in progress
  990. # ORA-02396: exceeded maximum idle time, please connect again
  991. # TODO: Others ?
  992. return error.code in (28, 3114, 3113, 3135, 1033, 2396)
  993. else:
  994. return False
  995. def create_xid(self):
  996. """create a two-phase transaction ID.
  997. this id will be passed to do_begin_twophase(), do_rollback_twophase(),
  998. do_commit_twophase(). its format is unspecified.
  999. """
  1000. id_ = random.randint(0, 2 ** 128)
  1001. return (0x1234, "%032x" % id_, "%032x" % 9)
  1002. def do_executemany(self, cursor, statement, parameters, context=None):
  1003. if isinstance(parameters, tuple):
  1004. parameters = list(parameters)
  1005. cursor.executemany(statement, parameters)
  1006. def do_begin_twophase(self, connection, xid):
  1007. connection.connection.begin(*xid)
  1008. connection.connection.info["cx_oracle_xid"] = xid
  1009. def do_prepare_twophase(self, connection, xid):
  1010. result = connection.connection.prepare()
  1011. connection.info["cx_oracle_prepared"] = result
  1012. def do_rollback_twophase(
  1013. self, connection, xid, is_prepared=True, recover=False
  1014. ):
  1015. self.do_rollback(connection.connection)
  1016. # TODO: need to end XA state here
  1017. def do_commit_twophase(
  1018. self, connection, xid, is_prepared=True, recover=False
  1019. ):
  1020. if not is_prepared:
  1021. self.do_commit(connection.connection)
  1022. else:
  1023. if recover:
  1024. raise NotImplementedError(
  1025. "2pc recovery not implemented for cx_Oracle"
  1026. )
  1027. oci_prepared = connection.info["cx_oracle_prepared"]
  1028. if oci_prepared:
  1029. self.do_commit(connection.connection)
  1030. # TODO: need to end XA state here
  1031. def do_set_input_sizes(self, cursor, list_of_tuples, context):
  1032. if self.positional:
  1033. # not usually used, here to support if someone is modifying
  1034. # the dialect to use positional style
  1035. cursor.setinputsizes(
  1036. *[dbtype for key, dbtype, sqltype in list_of_tuples]
  1037. )
  1038. else:
  1039. collection = (
  1040. (key, dbtype)
  1041. for key, dbtype, sqltype in list_of_tuples
  1042. if dbtype
  1043. )
  1044. if not self.supports_unicode_binds:
  1045. # oracle 8 only
  1046. collection = (
  1047. (self.dialect._encoder(key)[0], dbtype)
  1048. for key, dbtype in collection
  1049. )
  1050. cursor.setinputsizes(**{key: dbtype for key, dbtype in collection})
  1051. def do_recover_twophase(self, connection):
  1052. raise NotImplementedError(
  1053. "recover two phase query for cx_Oracle not implemented"
  1054. )
  1055. dialect = OracleDialect_cx_oracle