Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/sqlalchemy/dialects/oracle/cx_oracle.py: 30%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

512 statements  

1# dialects/oracle/cx_oracle.py 

2# Copyright (C) 2005-2026 the SQLAlchemy authors and contributors 

3# <see AUTHORS file> 

4# 

5# This module is part of SQLAlchemy and is released under 

6# the MIT License: https://www.opensource.org/licenses/mit-license.php 

7# mypy: ignore-errors 

8 

9 

10r""".. dialect:: oracle+cx_oracle 

11 :name: cx-Oracle 

12 :dbapi: cx_oracle 

13 :connectstring: oracle+cx_oracle://user:pass@hostname:port[/dbname][?service_name=<service>[&key=value&key=value...]] 

14 :url: https://oracle.github.io/python-cx_Oracle/ 

15 

16Description 

17----------- 

18 

19cx_Oracle was the original driver for Oracle Database. It was superseded by 

20python-oracledb which should be used instead. 

21 

22DSN vs. Hostname connections 

23----------------------------- 

24 

25cx_Oracle provides several methods of indicating the target database. The 

26dialect translates from a series of different URL forms. 

27 

28Hostname Connections with Easy Connect Syntax 

29^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

30 

31Given a hostname, port and service name of the target database, for example 

32from Oracle Database's Easy Connect syntax then connect in SQLAlchemy using the 

33``service_name`` query string parameter:: 

34 

35 engine = create_engine( 

36 "oracle+cx_oracle://scott:tiger@hostname:port?service_name=myservice&encoding=UTF-8&nencoding=UTF-8" 

37 ) 

38 

39Note that the default driver value for encoding and nencoding was changed to 

40“UTF-8” in cx_Oracle 8.0 so these parameters can be omitted when using that 

41version, or later. 

42 

43To use a full Easy Connect string, pass it as the ``dsn`` key value in a 

44:paramref:`_sa.create_engine.connect_args` dictionary:: 

45 

46 import cx_Oracle 

47 

48 e = create_engine( 

49 "oracle+cx_oracle://@", 

50 connect_args={ 

51 "user": "scott", 

52 "password": "tiger", 

53 "dsn": "hostname:port/myservice?transport_connect_timeout=30&expire_time=60", 

54 }, 

55 ) 

56 

57Connections with tnsnames.ora or to Oracle Autonomous Database 

58^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

59 

60Alternatively, if no port, database name, or service name is provided, the 

61dialect will use an Oracle Database DSN "connection string". This takes the 

62"hostname" portion of the URL as the data source name. For example, if the 

63``tnsnames.ora`` file contains a TNS Alias of ``myalias`` as below: 

64 

65.. sourcecode:: text 

66 

67 myalias = 

68 (DESCRIPTION = 

69 (ADDRESS = (PROTOCOL = TCP)(HOST = mymachine.example.com)(PORT = 1521)) 

70 (CONNECT_DATA = 

71 (SERVER = DEDICATED) 

72 (SERVICE_NAME = orclpdb1) 

73 ) 

74 ) 

75 

76The cx_Oracle dialect connects to this database service when ``myalias`` is the 

77hostname portion of the URL, without specifying a port, database name or 

78``service_name``:: 

79 

80 engine = create_engine("oracle+cx_oracle://scott:tiger@myalias") 

81 

82Users of Oracle Autonomous Database should use this syntax. If the database is 

83configured for mutural TLS ("mTLS"), then you must also configure the cloud 

84wallet as shown in cx_Oracle documentation `Connecting to Autononmous Databases 

85<https://cx-oracle.readthedocs.io/en/latest/user_guide/connection_handling.html#autonomousdb>`_. 

86 

87SID Connections 

88^^^^^^^^^^^^^^^ 

89 

90To use Oracle Database's obsolete System Identifier connection syntax, the SID 

91can be passed in a "database name" portion of the URL:: 

92 

93 engine = create_engine( 

94 "oracle+cx_oracle://scott:tiger@hostname:port/dbname" 

95 ) 

96 

97Above, the DSN passed to cx_Oracle is created by ``cx_Oracle.makedsn()`` as 

98follows:: 

99 

100 >>> import cx_Oracle 

101 >>> cx_Oracle.makedsn("hostname", 1521, sid="dbname") 

102 '(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=hostname)(PORT=1521))(CONNECT_DATA=(SID=dbname)))' 

103 

104Note that although the SQLAlchemy syntax ``hostname:port/dbname`` looks like 

105Oracle's Easy Connect syntax it is different. It uses a SID in place of the 

106service name required by Easy Connect. The Easy Connect syntax does not 

107support SIDs. 

108 

109Passing cx_Oracle connect arguments 

110----------------------------------- 

111 

112Additional connection arguments can usually be passed via the URL query string; 

113particular symbols like ``SYSDBA`` are intercepted and converted to the correct 

114symbol:: 

115 

116 e = create_engine( 

117 "oracle+cx_oracle://user:pass@dsn?encoding=UTF-8&nencoding=UTF-8&mode=SYSDBA&events=true" 

118 ) 

119 

120To pass arguments directly to ``.connect()`` without using the query 

121string, use the :paramref:`_sa.create_engine.connect_args` dictionary. 

122Any cx_Oracle parameter value and/or constant may be passed, such as:: 

123 

124 import cx_Oracle 

125 

126 e = create_engine( 

127 "oracle+cx_oracle://user:pass@dsn", 

128 connect_args={ 

129 "encoding": "UTF-8", 

130 "nencoding": "UTF-8", 

131 "mode": cx_Oracle.SYSDBA, 

132 "events": True, 

133 }, 

134 ) 

135 

136Note that the default driver value for ``encoding`` and ``nencoding`` was 

137changed to "UTF-8" in cx_Oracle 8.0 so these parameters can be omitted when 

138using that version, or later. 

139 

140Options consumed by the SQLAlchemy cx_Oracle dialect outside of the driver 

141-------------------------------------------------------------------------- 

142 

143There are also options that are consumed by the SQLAlchemy cx_oracle dialect 

144itself. These options are always passed directly to :func:`_sa.create_engine` 

145, such as:: 

146 

147 e = create_engine( 

148 "oracle+cx_oracle://user:pass@dsn", coerce_to_decimal=False 

149 ) 

150 

151The parameters accepted by the cx_oracle dialect are as follows: 

152 

153* ``arraysize`` - set the cx_oracle.arraysize value on cursors; defaults 

154 to ``None``, indicating that the driver default should be used (typically 

155 the value is 100). This setting controls how many rows are buffered when 

156 fetching rows, and can have a significant effect on performance when 

157 modified. 

158 

159 .. versionchanged:: 2.0.26 - changed the default value from 50 to None, 

160 to use the default value of the driver itself. 

161 

162* ``auto_convert_lobs`` - defaults to True; See :ref:`cx_oracle_lob`. 

163 

164* ``coerce_to_decimal`` - see :ref:`cx_oracle_numeric` for detail. 

165 

166* ``encoding_errors`` - see :ref:`cx_oracle_unicode_encoding_errors` for detail. 

167 

168.. _cx_oracle_sessionpool: 

169 

170Using cx_Oracle SessionPool 

171--------------------------- 

172 

173The cx_Oracle driver provides its own connection pool implementation that may 

174be used in place of SQLAlchemy's pooling functionality. The driver pool 

175supports Oracle Database features such dead connection detection, connection 

176draining for planned database downtime, support for Oracle Application 

177Continuity and Transparent Application Continuity, and gives support for 

178Database Resident Connection Pooling (DRCP). 

179 

180Using the driver pool can be achieved by using the 

181:paramref:`_sa.create_engine.creator` parameter to provide a function that 

182returns a new connection, along with setting 

183:paramref:`_sa.create_engine.pool_class` to ``NullPool`` to disable 

184SQLAlchemy's pooling:: 

185 

186 import cx_Oracle 

187 from sqlalchemy import create_engine 

188 from sqlalchemy.pool import NullPool 

189 

190 pool = cx_Oracle.SessionPool( 

191 user="scott", 

192 password="tiger", 

193 dsn="orclpdb", 

194 min=1, 

195 max=4, 

196 increment=1, 

197 threaded=True, 

198 encoding="UTF-8", 

199 nencoding="UTF-8", 

200 ) 

201 

202 engine = create_engine( 

203 "oracle+cx_oracle://", creator=pool.acquire, poolclass=NullPool 

204 ) 

205 

206The above engine may then be used normally where cx_Oracle's pool handles 

207connection pooling:: 

208 

209 with engine.connect() as conn: 

210 print(conn.scalar("select 1 from dual")) 

211 

212As well as providing a scalable solution for multi-user applications, the 

213cx_Oracle session pool supports some Oracle features such as DRCP and 

214`Application Continuity 

215<https://cx-oracle.readthedocs.io/en/latest/user_guide/ha.html#application-continuity-ac>`_. 

216 

217Note that the pool creation parameters ``threaded``, ``encoding`` and 

218``nencoding`` were deprecated in later cx_Oracle releases. 

219 

220Using Oracle Database Resident Connection Pooling (DRCP) 

221-------------------------------------------------------- 

222 

223When using Oracle Database's DRCP, the best practice is to pass a connection 

224class and "purity" when acquiring a connection from the SessionPool. Refer to 

225the `cx_Oracle DRCP documentation 

226<https://cx-oracle.readthedocs.io/en/latest/user_guide/connection_handling.html#database-resident-connection-pooling-drcp>`_. 

227 

228This can be achieved by wrapping ``pool.acquire()``:: 

229 

230 import cx_Oracle 

231 from sqlalchemy import create_engine 

232 from sqlalchemy.pool import NullPool 

233 

234 pool = cx_Oracle.SessionPool( 

235 user="scott", 

236 password="tiger", 

237 dsn="orclpdb", 

238 min=2, 

239 max=5, 

240 increment=1, 

241 threaded=True, 

242 encoding="UTF-8", 

243 nencoding="UTF-8", 

244 ) 

245 

246 

247 def creator(): 

248 return pool.acquire( 

249 cclass="MYCLASS", purity=cx_Oracle.ATTR_PURITY_SELF 

250 ) 

251 

252 

253 engine = create_engine( 

254 "oracle+cx_oracle://", creator=creator, poolclass=NullPool 

255 ) 

256 

257The above engine may then be used normally where cx_Oracle handles session 

258pooling and Oracle Database additionally uses DRCP:: 

259 

260 with engine.connect() as conn: 

261 print(conn.scalar("select 1 from dual")) 

262 

263.. _cx_oracle_unicode: 

264 

265Unicode 

266------- 

267 

268As is the case for all DBAPIs under Python 3, all strings are inherently 

269Unicode strings. In all cases however, the driver requires an explicit 

270encoding configuration. 

271 

272Ensuring the Correct Client Encoding 

273^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

274 

275The long accepted standard for establishing client encoding for nearly all 

276Oracle Database related software is via the `NLS_LANG 

277<https://www.oracle.com/database/technologies/faq-nls-lang.html>`_ environment 

278variable. Older versions of cx_Oracle use this environment variable as the 

279source of its encoding configuration. The format of this variable is 

280Territory_Country.CharacterSet; a typical value would be 

281``AMERICAN_AMERICA.AL32UTF8``. cx_Oracle version 8 and later use the character 

282set "UTF-8" by default, and ignore the character set component of NLS_LANG. 

283 

284The cx_Oracle driver also supported a programmatic alternative which is to pass 

285the ``encoding`` and ``nencoding`` parameters directly to its ``.connect()`` 

286function. These can be present in the URL as follows:: 

287 

288 engine = create_engine( 

289 "oracle+cx_oracle://scott:tiger@tnsalias?encoding=UTF-8&nencoding=UTF-8" 

290 ) 

291 

292For the meaning of the ``encoding`` and ``nencoding`` parameters, please 

293consult 

294`Characters Sets and National Language Support (NLS) <https://cx-oracle.readthedocs.io/en/latest/user_guide/globalization.html#globalization>`_. 

295 

296.. seealso:: 

297 

298 `Characters Sets and National Language Support (NLS) <https://cx-oracle.readthedocs.io/en/latest/user_guide/globalization.html#globalization>`_ 

299 - in the cx_Oracle documentation. 

300 

301 

302Unicode-specific Column datatypes 

303^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

304 

305The Core expression language handles unicode data by use of the 

306:class:`.Unicode` and :class:`.UnicodeText` datatypes. These types correspond 

307to the VARCHAR2 and CLOB Oracle Database datatypes by default. When using 

308these datatypes with Unicode data, it is expected that the database is 

309configured with a Unicode-aware character set, as well as that the ``NLS_LANG`` 

310environment variable is set appropriately (this applies to older versions of 

311cx_Oracle), so that the VARCHAR2 and CLOB datatypes can accommodate the data. 

312 

313In the case that Oracle Database is not configured with a Unicode character 

314set, the two options are to use the :class:`_types.NCHAR` and 

315:class:`_oracle.NCLOB` datatypes explicitly, or to pass the flag 

316``use_nchar_for_unicode=True`` to :func:`_sa.create_engine`, which will cause 

317the SQLAlchemy dialect to use NCHAR/NCLOB for the :class:`.Unicode` / 

318:class:`.UnicodeText` datatypes instead of VARCHAR/CLOB. 

319 

320.. _cx_oracle_unicode_encoding_errors: 

321 

322Encoding Errors 

323^^^^^^^^^^^^^^^ 

324 

325For the unusual case that data in Oracle Database is present with a broken 

326encoding, the dialect accepts a parameter ``encoding_errors`` which will be 

327passed to Unicode decoding functions in order to affect how decoding errors are 

328handled. The value is ultimately consumed by the Python `decode 

329<https://docs.python.org/3/library/stdtypes.html#bytes.decode>`_ function, and 

330is passed both via cx_Oracle's ``encodingErrors`` parameter consumed by 

331``Cursor.var()``, as well as SQLAlchemy's own decoding function, as the 

332cx_Oracle dialect makes use of both under different circumstances. 

333 

334.. _cx_oracle_setinputsizes: 

335 

336Fine grained control over cx_Oracle data binding performance with setinputsizes 

337------------------------------------------------------------------------------- 

338 

339The cx_Oracle DBAPI has a deep and fundamental reliance upon the usage of the 

340DBAPI ``setinputsizes()`` call. The purpose of this call is to establish the 

341datatypes that are bound to a SQL statement for Python values being passed as 

342parameters. While virtually no other DBAPI assigns any use to the 

343``setinputsizes()`` call, the cx_Oracle DBAPI relies upon it heavily in its 

344interactions with the Oracle Database client interface, and in some scenarios 

345it is not possible for SQLAlchemy to know exactly how data should be bound, as 

346some settings can cause profoundly different performance characteristics, while 

347altering the type coercion behavior at the same time. 

348 

349Users of the cx_Oracle dialect are **strongly encouraged** to read through 

350cx_Oracle's list of built-in datatype symbols at 

351https://cx-oracle.readthedocs.io/en/latest/api_manual/module.html#database-types. 

352Note that in some cases, significant performance degradation can occur when 

353using these types vs. not, in particular when specifying ``cx_Oracle.CLOB``. 

354 

355On the SQLAlchemy side, the :meth:`.DialectEvents.do_setinputsizes` event can 

356be used both for runtime visibility (e.g. logging) of the setinputsizes step as 

357well as to fully control how ``setinputsizes()`` is used on a per-statement 

358basis. 

359 

360Example 1 - logging all setinputsizes calls 

361^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

362 

363The following example illustrates how to log the intermediary values from a 

364SQLAlchemy perspective before they are converted to the raw ``setinputsizes()`` 

365parameter dictionary. The keys of the dictionary are :class:`.BindParameter` 

366objects which have a ``.key`` and a ``.type`` attribute:: 

367 

368 from sqlalchemy import create_engine, event 

369 

370 engine = create_engine("oracle+cx_oracle://scott:tiger@host/xe") 

371 

372 

373 @event.listens_for(engine, "do_setinputsizes") 

374 def _log_setinputsizes(inputsizes, cursor, statement, parameters, context): 

375 for bindparam, dbapitype in inputsizes.items(): 

376 log.info( 

377 "Bound parameter name: %s SQLAlchemy type: %r DBAPI object: %s", 

378 bindparam.key, 

379 bindparam.type, 

380 dbapitype, 

381 ) 

382 

383Example 2 - remove all bindings to CLOB 

384^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

385 

386The ``CLOB`` datatype in cx_Oracle incurs a significant performance overhead, 

387however is set by default for the ``Text`` type within the SQLAlchemy 1.2 

388series. This setting can be modified as follows:: 

389 

390 from sqlalchemy import create_engine, event 

391 from cx_Oracle import CLOB 

392 

393 engine = create_engine("oracle+cx_oracle://scott:tiger@host/xe") 

394 

395 

396 @event.listens_for(engine, "do_setinputsizes") 

397 def _remove_clob(inputsizes, cursor, statement, parameters, context): 

398 for bindparam, dbapitype in list(inputsizes.items()): 

399 if dbapitype is CLOB: 

400 del inputsizes[bindparam] 

401 

402.. _cx_oracle_lob: 

403 

404LOB Datatypes 

405-------------- 

406 

407LOB datatypes refer to the "large object" datatypes such as CLOB, NCLOB and 

408BLOB. Modern versions of cx_Oracle is optimized for these datatypes to be 

409delivered as a single buffer. As such, SQLAlchemy makes use of these newer type 

410handlers by default. 

411 

412To disable the use of newer type handlers and deliver LOB objects as classic 

413buffered objects with a ``read()`` method, the parameter 

414``auto_convert_lobs=False`` may be passed to :func:`_sa.create_engine`, 

415which takes place only engine-wide. 

416 

417.. _cx_oracle_returning: 

418 

419RETURNING Support 

420----------------- 

421 

422The cx_Oracle dialect implements RETURNING using OUT parameters. 

423The dialect supports RETURNING fully. 

424 

425Two Phase Transactions Not Supported 

426------------------------------------ 

427 

428Two phase transactions are **not supported** under cx_Oracle due to poor driver 

429support. The newer :ref:`oracledb` dialect however **does** support two phase 

430transactions. 

431 

432.. _cx_oracle_numeric: 

433 

434Precision Numerics 

435------------------ 

436 

437SQLAlchemy's numeric types can handle receiving and returning values as Python 

438``Decimal`` objects or float objects. When a :class:`.Numeric` object, or a 

439subclass such as :class:`.Float`, :class:`_oracle.DOUBLE_PRECISION` etc. is in 

440use, the :paramref:`.Numeric.asdecimal` flag determines if values should be 

441coerced to ``Decimal`` upon return, or returned as float objects. To make 

442matters more complicated under Oracle Database, the ``NUMBER`` type can also 

443represent integer values if the "scale" is zero, so the Oracle 

444Database-specific :class:`_oracle.NUMBER` type takes this into account as well. 

445 

446The cx_Oracle dialect makes extensive use of connection- and cursor-level 

447"outputtypehandler" callables in order to coerce numeric values as requested. 

448These callables are specific to the specific flavor of :class:`.Numeric` in 

449use, as well as if no SQLAlchemy typing objects are present. There are 

450observed scenarios where Oracle Database may send incomplete or ambiguous 

451information about the numeric types being returned, such as a query where the 

452numeric types are buried under multiple levels of subquery. The type handlers 

453do their best to make the right decision in all cases, deferring to the 

454underlying cx_Oracle DBAPI for all those cases where the driver can make the 

455best decision. 

456 

457When no typing objects are present, as when executing plain SQL strings, a 

458default "outputtypehandler" is present which will generally return numeric 

459values which specify precision and scale as Python ``Decimal`` objects. To 

460disable this coercion to decimal for performance reasons, pass the flag 

461``coerce_to_decimal=False`` to :func:`_sa.create_engine`:: 

462 

463 engine = create_engine("oracle+cx_oracle://dsn", coerce_to_decimal=False) 

464 

465The ``coerce_to_decimal`` flag only impacts the results of plain string 

466SQL statements that are not otherwise associated with a :class:`.Numeric` 

467SQLAlchemy type (or a subclass of such). 

468 

469""" # noqa 

470 

471from __future__ import annotations 

472 

473import collections 

474import decimal 

475import json 

476import random 

477import re 

478 

479from . import base as oracle 

480from .base import OracleCompiler 

481from .base import OracleDialect 

482from .base import OracleExecutionContext 

483from .json import JSON 

484from .types import _OracleDateLiteralRender 

485from ... import exc 

486from ... import util 

487from ...engine import cursor as _cursor 

488from ...engine import interfaces 

489from ...engine import processors 

490from ...sql import sqltypes 

491from ...sql._typing import is_sql_compiler 

492from ...sql.base import NO_ARG 

493from ...sql.sqltypes import Boolean 

494 

495# source: 

496# https://github.com/oracle/python-cx_Oracle/issues/596#issuecomment-999243649 

497_CX_ORACLE_MAGIC_LOB_SIZE = 131072 

498 

499# largest JSON we can deserialize if we are not using 

500# DB_TYPE_JSON 

501_CX_ORACLE_MAX_JSON_CONVERTED = 32767 

502 

503 

504class _OracleJson(JSON): 

505 def get_dbapi_type(self, dbapi): 

506 return dbapi.DB_TYPE_JSON 

507 

508 def _should_use_blob(self, dialect): 

509 use_blob = ( 

510 not dialect._supports_oracle_json 

511 if self.use_blob is NO_ARG 

512 else self.use_blob 

513 ) 

514 

515 return use_blob 

516 

517 def bind_processor(self, dialect): 

518 

519 if self._should_use_blob(dialect): 

520 

521 DBAPIBinary = dialect.dbapi.Binary 

522 

523 def string_process(value): 

524 if value is not None: 

525 # utf-8 is standard for oracledb 

526 # https://python-oracledb.readthedocs.io/en/latest/user_guide/globalization.html#setting-the-client-character-set # noqa: E501 

527 return DBAPIBinary(value.encode("utf-8")) 

528 else: 

529 return None 

530 

531 else: 

532 string_process = None 

533 

534 json_serializer = dialect._json_serializer or json.dumps 

535 

536 return self._make_bind_processor(string_process, json_serializer) 

537 

538 def result_processor(self, dialect, coltype): 

539 if self._should_use_blob(dialect): 

540 # for plain BLOB, use traditional binary decode + json.loads() 

541 string_process = self._str_impl.result_processor(dialect, coltype) 

542 json_deserializer = dialect._json_deserializer or json.loads 

543 

544 def process(value): 

545 if value is None: 

546 return None 

547 if string_process: 

548 value = string_process(value) 

549 return json_deserializer(value) 

550 

551 return process 

552 

553 else: 

554 # not BLOB; the value is decoded by an outputtypehandler 

555 # rather than here. see _cx_oracle_outputtypehandler() below 

556 return None 

557 

558 def _cx_oracle_outputtypehandler(self, dialect): 

559 """Establish an outputtypehandler for JSON result columns. 

560 

561 The dialect makes use of two distinct outputtypehandlers. One is 

562 connection-wide, set up by the dialect's 

563 _generate_connection_outputtype_handler(); it sees every column of 

564 every statement and knows nothing of SQLAlchemy-side types, only of 

565 the DBAPI type the database reports. The other is the one returned 

566 here, which the execution context's 

567 _generate_cursor_outputtype_handler() installs on the cursor for one 

568 particular statement, and which applies only to those result columns 

569 that SQLAlchemy knows to be of JSON type. 

570 

571 The two do not compose. The driver consults the cursor's handler if 

572 one is present, and the connection's otherwise, never both. The 

573 cursor-level handler assembled by the execution context therefore 

574 delegates to the connection-level handler explicitly for the columns 

575 it has no handler of its own for, and the DB_TYPE_JSON case below 

576 has to repeat what the connection-level handler does rather than 

577 defer to it. 

578 

579 """ 

580 

581 if self._should_use_blob(dialect): 

582 # BLOB is decoded by result_processor() instead 

583 return None 

584 

585 cx_Oracle = dialect.dbapi 

586 json_deserializer = dialect._json_deserializer 

587 

588 def handler(cursor, name, default_type, size, precision, scale): 

589 if default_type is cx_Oracle.DB_TYPE_JSON: 

590 # a native JSON column. the driver decodes these into 

591 # Python objects on its own, so a handler is only needed in 

592 # order to route the value through a user-supplied 

593 # deserializer, receiving it as text to do so. this repeats 

594 # the connection-level handler, as noted above 

595 if json_deserializer is not None: 

596 return cursor.var( 

597 cx_Oracle.DB_TYPE_VARCHAR, 

598 _CX_ORACLE_MAX_JSON_CONVERTED, 

599 cursor.arraysize, 

600 outconverter=json_deserializer, 

601 ) 

602 else: 

603 return None 

604 elif default_type in ( 

605 cx_Oracle.DB_TYPE_VARCHAR, 

606 cx_Oracle.DB_TYPE_NVARCHAR, 

607 cx_Oracle.DB_TYPE_LONG, 

608 cx_Oracle.DB_TYPE_LONG_NVARCHAR, 

609 ): 

610 # a JSON-typed expression that isn't a native JSON column, 

611 # such as a bound parameter or a string expression selected 

612 # directly. the database reports it as ordinary character 

613 # data and won't decode it, so decode it here. the 

614 # connection-level handler can't do this as it has no way to 

615 # know the expression was intended as JSON 

616 return cursor.var( 

617 default_type, 

618 size, 

619 cursor.arraysize, 

620 outconverter=json_deserializer or json.loads, 

621 **dialect._cursor_var_unicode_kwargs, 

622 ) 

623 elif default_type in (cx_Oracle.CLOB, cx_Oracle.NCLOB): 

624 # same as the character case above, except the database 

625 # reports the expression as a LOB; read it as a string 

626 # first, then decode 

627 return dialect._cursor_lob_as_str_var( 

628 cursor, default_type, json_deserializer or json.loads 

629 ) 

630 else: 

631 return None 

632 

633 return handler 

634 

635 

636class _OracleInteger(sqltypes.Integer): 

637 def get_dbapi_type(self, dbapi): 

638 # see https://github.com/oracle/python-cx_Oracle/issues/ 

639 # 208#issuecomment-409715955 

640 return int 

641 

642 def _cx_oracle_var(self, dialect, cursor, arraysize=None): 

643 cx_Oracle = dialect.dbapi 

644 return cursor.var( 

645 cx_Oracle.STRING, 

646 255, 

647 arraysize=arraysize if arraysize is not None else cursor.arraysize, 

648 outconverter=int, 

649 ) 

650 

651 def _cx_oracle_outputtypehandler(self, dialect): 

652 def handler(cursor, name, default_type, size, precision, scale): 

653 return self._cx_oracle_var(dialect, cursor) 

654 

655 return handler 

656 

657 

658class _OracleNumericCommon(sqltypes.NumericCommon, sqltypes.TypeEngine): 

659 is_number = False 

660 

661 def bind_processor(self, dialect): 

662 if self.scale == 0: 

663 return None 

664 elif self.asdecimal: 

665 processor = processors.to_decimal_processor_factory( 

666 decimal.Decimal, self._effective_decimal_return_scale 

667 ) 

668 

669 def process(value): 

670 if isinstance(value, (int, float)): 

671 return processor(value) 

672 elif value is not None and value.is_infinite(): 

673 return float(value) 

674 else: 

675 return value 

676 

677 return process 

678 else: 

679 return processors.to_float 

680 

681 def result_processor(self, dialect, coltype): 

682 return None 

683 

684 def _cx_oracle_outputtypehandler(self, dialect): 

685 cx_Oracle = dialect.dbapi 

686 

687 def handler(cursor, name, default_type, size, precision, scale): 

688 outconverter = None 

689 

690 if precision: 

691 if self.asdecimal: 

692 if default_type == cx_Oracle.NATIVE_FLOAT: 

693 # receiving float and doing Decimal after the fact 

694 # allows for float("inf") to be handled 

695 type_ = default_type 

696 outconverter = decimal.Decimal 

697 else: 

698 type_ = decimal.Decimal 

699 else: 

700 if self.is_number and scale == 0: 

701 # integer. cx_Oracle is observed to handle the widest 

702 # variety of ints when no directives are passed, 

703 # from 5.2 to 7.0. See [ticket:4457] 

704 return None 

705 else: 

706 type_ = cx_Oracle.NATIVE_FLOAT 

707 

708 else: 

709 if self.asdecimal: 

710 if default_type == cx_Oracle.NATIVE_FLOAT: 

711 type_ = default_type 

712 outconverter = decimal.Decimal 

713 else: 

714 type_ = decimal.Decimal 

715 else: 

716 if self.is_number and scale == 0: 

717 # integer. cx_Oracle is observed to handle the widest 

718 # variety of ints when no directives are passed, 

719 # from 5.2 to 7.0. See [ticket:4457] 

720 return None 

721 else: 

722 type_ = cx_Oracle.NATIVE_FLOAT 

723 

724 return cursor.var( 

725 type_, 

726 255, 

727 arraysize=cursor.arraysize, 

728 outconverter=outconverter, 

729 ) 

730 

731 return handler 

732 

733 

734class _OracleNumeric(_OracleNumericCommon, sqltypes.Numeric): 

735 pass 

736 

737 

738class _OracleFloat(_OracleNumericCommon, sqltypes.Float): 

739 pass 

740 

741 

742class _OracleUUID(sqltypes.Uuid): 

743 def get_dbapi_type(self, dbapi): 

744 return dbapi.STRING 

745 

746 

747class _OracleBinaryFloat(_OracleNumericCommon): 

748 def get_dbapi_type(self, dbapi): 

749 return dbapi.NATIVE_FLOAT 

750 

751 

752class _OracleBINARY_FLOAT(_OracleBinaryFloat, oracle.BINARY_FLOAT): 

753 pass 

754 

755 

756class _OracleBINARY_DOUBLE(_OracleBinaryFloat, oracle.BINARY_DOUBLE): 

757 pass 

758 

759 

760class _OracleNUMBER(_OracleNumericCommon, sqltypes.Numeric): 

761 is_number = True 

762 

763 

764class _CXOracleDate(oracle._OracleDate): 

765 def bind_processor(self, dialect): 

766 return None 

767 

768 def result_processor(self, dialect, coltype): 

769 def process(value): 

770 if value is not None: 

771 return value.date() 

772 else: 

773 return value 

774 

775 return process 

776 

777 

778class _CXOracleTIMESTAMP(_OracleDateLiteralRender, sqltypes.TIMESTAMP): 

779 def literal_processor(self, dialect): 

780 return self._literal_processor_datetime(dialect) 

781 

782 

783class _LOBDataType: 

784 pass 

785 

786 

787# TODO: the names used across CHAR / VARCHAR / NCHAR / NVARCHAR 

788# here are inconsistent and not very good 

789class _OracleChar(sqltypes.CHAR): 

790 def get_dbapi_type(self, dbapi): 

791 return dbapi.FIXED_CHAR 

792 

793 

794class _OracleNChar(sqltypes.NCHAR): 

795 def get_dbapi_type(self, dbapi): 

796 return dbapi.FIXED_NCHAR 

797 

798 

799class _OracleUnicodeStringNCHAR(oracle.NVARCHAR2): 

800 def get_dbapi_type(self, dbapi): 

801 return dbapi.NCHAR 

802 

803 

804class _OracleUnicodeStringCHAR(sqltypes.Unicode): 

805 def get_dbapi_type(self, dbapi): 

806 return dbapi.LONG_STRING 

807 

808 

809class _OracleUnicodeTextNCLOB(_LOBDataType, oracle.NCLOB): 

810 def get_dbapi_type(self, dbapi): 

811 # previously, this was dbapi.NCLOB. 

812 # DB_TYPE_NVARCHAR will instead be passed to setinputsizes() 

813 # when this datatype is used. 

814 return dbapi.DB_TYPE_NVARCHAR 

815 

816 

817class _OracleUnicodeTextCLOB(_LOBDataType, sqltypes.UnicodeText): 

818 def get_dbapi_type(self, dbapi): 

819 # previously, this was dbapi.CLOB. 

820 # DB_TYPE_NVARCHAR will instead be passed to setinputsizes() 

821 # when this datatype is used. 

822 return dbapi.DB_TYPE_NVARCHAR 

823 

824 

825class _OracleText(_LOBDataType, sqltypes.Text): 

826 def get_dbapi_type(self, dbapi): 

827 # previously, this was dbapi.CLOB. 

828 # DB_TYPE_NVARCHAR will instead be passed to setinputsizes() 

829 # when this datatype is used. 

830 return dbapi.DB_TYPE_NVARCHAR 

831 

832 

833class _OracleLong(_LOBDataType, oracle.LONG): 

834 def get_dbapi_type(self, dbapi): 

835 return dbapi.LONG_STRING 

836 

837 

838class _OracleString(sqltypes.String): 

839 pass 

840 

841 

842class _OracleEnum(sqltypes.Enum): 

843 def bind_processor(self, dialect): 

844 enum_proc = sqltypes.Enum.bind_processor(self, dialect) 

845 

846 def process(value): 

847 raw_str = enum_proc(value) 

848 return raw_str 

849 

850 return process 

851 

852 

853class _OracleBinary(_LOBDataType, sqltypes.LargeBinary): 

854 def get_dbapi_type(self, dbapi): 

855 # previously, this was dbapi.BLOB. 

856 # DB_TYPE_RAW will instead be passed to setinputsizes() 

857 # when this datatype is used. 

858 return dbapi.DB_TYPE_RAW 

859 

860 def bind_processor(self, dialect): 

861 return None 

862 

863 def result_processor(self, dialect, coltype): 

864 if not dialect.auto_convert_lobs: 

865 return None 

866 else: 

867 return super().result_processor(dialect, coltype) 

868 

869 

870class _OracleInterval(oracle.INTERVAL): 

871 def get_dbapi_type(self, dbapi): 

872 return dbapi.INTERVAL 

873 

874 

875class _OracleRaw(oracle.RAW): 

876 pass 

877 

878 

879class _OracleRowid(oracle.ROWID): 

880 def get_dbapi_type(self, dbapi): 

881 return dbapi.ROWID 

882 

883 

884class OracleCompiler_cx_oracle(OracleCompiler): 

885 _oracle_cx_sql_compiler = True 

886 

887 _oracle_returning = False 

888 

889 # Oracle bind names can't start with digits or underscores. 

890 # currently we rely upon Oracle-specific quoting of bind names in most 

891 # cases. however for expanding params, the escape chars are used. 

892 # see #8708 

893 bindname_escape_characters = util.immutabledict( 

894 { 

895 "%": "P", 

896 "(": "A", 

897 ")": "Z", 

898 ":": "C", 

899 ".": "C", 

900 "[": "C", 

901 "]": "C", 

902 " ": "C", 

903 "\\": "C", 

904 "/": "C", 

905 "?": "C", 

906 } 

907 ) 

908 

909 def bindparam_string(self, name, **kw): 

910 quote = getattr(name, "quote", None) 

911 if ( 

912 quote is True 

913 or quote is not False 

914 and self.preparer._bindparam_requires_quotes(name) 

915 # bind param quoting for Oracle doesn't work with post_compile 

916 # params. For those, the default bindparam_string will escape 

917 # special chars, and the appending of a number "_1" etc. will 

918 # take care of reserved words 

919 and not kw.get("post_compile", False) 

920 ): 

921 # interesting to note about expanding parameters - since the 

922 # new parameters take the form <paramname>_<int>, at least if 

923 # they are originally formed from reserved words, they no longer 

924 # need quoting :). names that include illegal characters 

925 # won't work however. 

926 quoted_name = '"%s"' % name 

927 kw["escaped_from"] = name 

928 name = quoted_name 

929 return OracleCompiler.bindparam_string(self, name, **kw) 

930 

931 # TODO: we could likely do away with quoting altogether for 

932 # Oracle parameters and use the custom escaping here 

933 escaped_from = kw.get("escaped_from", None) 

934 if not escaped_from: 

935 if self._bind_translate_re.search(name): 

936 # not quite the translate use case as we want to 

937 # also get a quick boolean if we even found 

938 # unusual characters in the name 

939 new_name = self._bind_translate_re.sub( 

940 lambda m: self._bind_translate_chars[m.group(0)], 

941 name, 

942 ) 

943 if new_name[0].isdigit() or new_name[0] == "_": 

944 new_name = "D" + new_name 

945 kw["escaped_from"] = name 

946 name = new_name 

947 elif name[0].isdigit() or name[0] == "_": 

948 new_name = "D" + name 

949 kw["escaped_from"] = name 

950 name = new_name 

951 

952 return OracleCompiler.bindparam_string(self, name, **kw) 

953 

954 

955class OracleExecutionContext_cx_oracle(OracleExecutionContext): 

956 out_parameters = None 

957 

958 def _generate_out_parameter_vars(self): 

959 # check for has_out_parameters or RETURNING, create cx_Oracle.var 

960 # objects if so 

961 if self.compiled.has_out_parameters or self.compiled._oracle_returning: 

962 out_parameters = self.out_parameters 

963 assert out_parameters is not None 

964 

965 len_params = len(self.parameters) 

966 

967 quoted_bind_names = self.compiled.escaped_bind_names 

968 for bindparam in self.compiled.binds.values(): 

969 if bindparam.isoutparam: 

970 name = self.compiled.bind_names[bindparam] 

971 type_impl = bindparam.type.dialect_impl(self.dialect) 

972 

973 if hasattr(type_impl, "_cx_oracle_var"): 

974 out_parameters[name] = type_impl._cx_oracle_var( 

975 self.dialect, self.cursor, arraysize=len_params 

976 ) 

977 else: 

978 dbtype = type_impl.get_dbapi_type(self.dialect.dbapi) 

979 

980 cx_Oracle = self.dialect.dbapi 

981 

982 assert cx_Oracle is not None 

983 

984 if dbtype is None: 

985 raise exc.InvalidRequestError( 

986 "Cannot create out parameter for " 

987 "parameter " 

988 "%r - its type %r is not supported by" 

989 " cx_oracle" % (bindparam.key, bindparam.type) 

990 ) 

991 

992 # note this is an OUT parameter. Using 

993 # non-LOB datavalues with large unicode-holding 

994 # values causes the failure (both cx_Oracle and 

995 # oracledb): 

996 # ORA-22835: Buffer too small for CLOB to CHAR or 

997 # BLOB to RAW conversion (actual: 16507, 

998 # maximum: 4000) 

999 # [SQL: INSERT INTO long_text (x, y, z) VALUES 

1000 # (:x, :y, :z) RETURNING long_text.x, long_text.y, 

1001 # long_text.z INTO :ret_0, :ret_1, :ret_2] 

1002 # so even for DB_TYPE_NVARCHAR we convert to a LOB 

1003 

1004 if isinstance(type_impl, _LOBDataType): 

1005 if dbtype == cx_Oracle.DB_TYPE_NVARCHAR: 

1006 dbtype = cx_Oracle.NCLOB 

1007 elif dbtype == cx_Oracle.DB_TYPE_RAW: 

1008 dbtype = cx_Oracle.BLOB 

1009 # other LOB types go in directly 

1010 

1011 out_parameters[name] = self.cursor.var( 

1012 dbtype, 

1013 # this is fine also in oracledb_async since 

1014 # the driver will await the read coroutine 

1015 outconverter=lambda value: value.read(), 

1016 arraysize=len_params, 

1017 ) 

1018 elif ( 

1019 isinstance(type_impl, _OracleNumericCommon) 

1020 and type_impl.asdecimal 

1021 ): 

1022 out_parameters[name] = self.cursor.var( 

1023 decimal.Decimal, 

1024 arraysize=len_params, 

1025 ) 

1026 elif isinstance(type_impl, Boolean): 

1027 if self.dialect.supports_native_boolean: 

1028 out_parameters[name] = self.cursor.var( 

1029 cx_Oracle.BOOLEAN, arraysize=len_params 

1030 ) 

1031 else: 

1032 out_parameters[name] = self.cursor.var( 

1033 cx_Oracle.NUMBER, 

1034 arraysize=len_params, 

1035 outconverter=bool, 

1036 ) 

1037 else: 

1038 out_parameters[name] = self.cursor.var( 

1039 dbtype, arraysize=len_params 

1040 ) 

1041 

1042 for param in self.parameters: 

1043 param[quoted_bind_names.get(name, name)] = ( 

1044 out_parameters[name] 

1045 ) 

1046 

1047 def _generate_cursor_outputtype_handler(self): 

1048 assert isinstance(self.compiled, OracleCompiler) 

1049 

1050 # accumulate handlers positionally 

1051 handlers = [ 

1052 type_._cached_custom_processor( 

1053 self.dialect, 

1054 "cx_oracle_outputtypehandler", 

1055 self._get_cx_oracle_type_handler, 

1056 ) 

1057 for _, _, _, type_ in self.compiled._result_columns 

1058 ] 

1059 

1060 if not any(handlers): 

1061 return 

1062 

1063 # a handler on the cursor replaces the connection-wide handler 

1064 # outright rather than adding to it, so keep a reference to the 

1065 # latter and delegate to it for columns we have no handler for 

1066 default_handler = self._dbapi_connection.outputtypehandler 

1067 

1068 if self.compiled._textual_ordered_columns: 

1069 # for a textual construct with positional columns, i.e. 

1070 # text().columns() / tstring().columns(), match handlers 

1071 # positionally instead of by name, since names are not 

1072 # deterministic. See #13479 

1073 

1074 handler_queue = collections.deque(handlers) 

1075 

1076 def output_type_handler( 

1077 cursor, name, default_type, size, precision, scale 

1078 ): 

1079 handler = handler_queue.popleft() if handler_queue else None 

1080 if handler is not None: 

1081 return handler( 

1082 cursor, name, default_type, size, precision, scale 

1083 ) 

1084 else: 

1085 return default_handler( 

1086 cursor, name, default_type, size, precision, scale 

1087 ) 

1088 

1089 else: 

1090 output_handlers = { 

1091 self.dialect.denormalize_name(rc[0]): handler 

1092 for rc, handler in zip(self.compiled._result_columns, handlers) 

1093 if handler 

1094 } 

1095 

1096 def output_type_handler( 

1097 cursor, name, default_type, size, precision, scale 

1098 ): 

1099 handler = output_handlers.get(name) 

1100 if handler is not None: 

1101 return handler( 

1102 cursor, name, default_type, size, precision, scale 

1103 ) 

1104 else: 

1105 return default_handler( 

1106 cursor, name, default_type, size, precision, scale 

1107 ) 

1108 

1109 self.cursor.outputtypehandler = output_type_handler 

1110 

1111 def _get_cx_oracle_type_handler(self, impl): 

1112 if hasattr(impl, "_cx_oracle_outputtypehandler"): 

1113 return impl._cx_oracle_outputtypehandler(self.dialect) 

1114 else: 

1115 return None 

1116 

1117 def pre_exec(self): 

1118 super().pre_exec() 

1119 if not getattr(self.compiled, "_oracle_cx_sql_compiler", False): 

1120 return 

1121 

1122 self.out_parameters = {} 

1123 

1124 self._generate_out_parameter_vars() 

1125 

1126 self._generate_cursor_outputtype_handler() 

1127 

1128 def post_exec(self): 

1129 if ( 

1130 self.compiled 

1131 and is_sql_compiler(self.compiled) 

1132 and self.compiled._oracle_returning 

1133 ): 

1134 initial_buffer = self.fetchall_for_returning( 

1135 self.cursor, _internal=True 

1136 ) 

1137 

1138 fetch_strategy = _cursor.FullyBufferedCursorFetchStrategy( 

1139 self.cursor, 

1140 [ 

1141 (entry.keyname, None) 

1142 for entry in self.compiled._result_columns 

1143 ], 

1144 initial_buffer=initial_buffer, 

1145 ) 

1146 

1147 self.cursor_fetch_strategy = fetch_strategy 

1148 

1149 def create_cursor(self): 

1150 c = self._dbapi_connection.cursor() 

1151 if self.dialect.arraysize: 

1152 c.arraysize = self.dialect.arraysize 

1153 

1154 return c 

1155 

1156 def fetchall_for_returning(self, cursor, *, _internal=False): 

1157 compiled = self.compiled 

1158 if ( 

1159 not _internal 

1160 and compiled is None 

1161 or not is_sql_compiler(compiled) 

1162 or not compiled._oracle_returning 

1163 ): 

1164 raise NotImplementedError( 

1165 "execution context was not prepared for Oracle RETURNING" 

1166 ) 

1167 

1168 # create a fake cursor result from the out parameters. unlike 

1169 # get_out_parameter_values(), the result-row handlers here will be 

1170 # applied at the Result level 

1171 

1172 numcols = len(self.out_parameters) 

1173 

1174 # [stmt_result for stmt_result in outparam.values] == each 

1175 # statement in executemany 

1176 # [val for val in stmt_result] == each row for a particular 

1177 # statement 

1178 return list( 

1179 zip( 

1180 *[ 

1181 [ 

1182 val 

1183 for stmt_result in self.out_parameters[ 

1184 f"ret_{j}" 

1185 ].values 

1186 for val in (stmt_result or ()) 

1187 ] 

1188 for j in range(numcols) 

1189 ] 

1190 ) 

1191 ) 

1192 

1193 def get_out_parameter_values(self, out_param_names): 

1194 # this method should not be called when the compiler has 

1195 # RETURNING as we've turned the has_out_parameters flag set to 

1196 # False. 

1197 assert not self.compiled.returning 

1198 

1199 return [ 

1200 self.dialect._paramval(self.out_parameters[name]) 

1201 for name in out_param_names 

1202 ] 

1203 

1204 

1205class OracleDialect_cx_oracle(OracleDialect): 

1206 supports_statement_cache = True 

1207 execution_ctx_cls = OracleExecutionContext_cx_oracle 

1208 statement_compiler = OracleCompiler_cx_oracle 

1209 

1210 supports_sane_rowcount = True 

1211 supports_sane_multi_rowcount = True 

1212 

1213 insert_executemany_returning = True 

1214 insert_executemany_returning_sort_by_parameter_order = True 

1215 update_executemany_returning = True 

1216 delete_executemany_returning = True 

1217 

1218 supports_native_json_serialization = False 

1219 supports_native_json_deserialization = False 

1220 dialect_injects_custom_json_deserializer = True 

1221 

1222 bind_typing = interfaces.BindTyping.SETINPUTSIZES 

1223 

1224 minimum_dbapi_version = util.VersionInfo((8,)) 

1225 

1226 driver = "cx_oracle" 

1227 

1228 colspecs = util.update_copy( 

1229 OracleDialect.colspecs, 

1230 { 

1231 sqltypes.TIMESTAMP: _CXOracleTIMESTAMP, 

1232 sqltypes.Numeric: _OracleNumeric, 

1233 sqltypes.Float: _OracleFloat, 

1234 oracle.BINARY_FLOAT: _OracleBINARY_FLOAT, 

1235 oracle.BINARY_DOUBLE: _OracleBINARY_DOUBLE, 

1236 sqltypes.JSON: _OracleJson, 

1237 sqltypes.Integer: _OracleInteger, 

1238 oracle.NUMBER: _OracleNUMBER, 

1239 sqltypes.Date: _CXOracleDate, 

1240 sqltypes.LargeBinary: _OracleBinary, 

1241 sqltypes.Boolean: oracle._OracleBoolean, 

1242 sqltypes.Interval: _OracleInterval, 

1243 oracle.INTERVAL: _OracleInterval, 

1244 sqltypes.Text: _OracleText, 

1245 sqltypes.String: _OracleString, 

1246 sqltypes.UnicodeText: _OracleUnicodeTextCLOB, 

1247 sqltypes.CHAR: _OracleChar, 

1248 sqltypes.NCHAR: _OracleNChar, 

1249 sqltypes.Enum: _OracleEnum, 

1250 oracle.LONG: _OracleLong, 

1251 oracle.RAW: _OracleRaw, 

1252 sqltypes.Unicode: _OracleUnicodeStringCHAR, 

1253 sqltypes.NVARCHAR: _OracleUnicodeStringNCHAR, 

1254 sqltypes.Uuid: _OracleUUID, 

1255 oracle.NCLOB: _OracleUnicodeTextNCLOB, 

1256 oracle.ROWID: _OracleRowid, 

1257 }, 

1258 ) 

1259 

1260 execute_sequence_format = list 

1261 

1262 _cursor_var_unicode_kwargs = util.immutabledict() 

1263 

1264 def __init__( 

1265 self, 

1266 auto_convert_lobs=True, 

1267 coerce_to_decimal=True, 

1268 arraysize=None, 

1269 encoding_errors=None, 

1270 **kwargs, 

1271 ): 

1272 OracleDialect.__init__(self, **kwargs) 

1273 self.arraysize = arraysize 

1274 self.encoding_errors = encoding_errors 

1275 if encoding_errors: 

1276 self._cursor_var_unicode_kwargs = { 

1277 "encodingErrors": encoding_errors 

1278 } 

1279 self.auto_convert_lobs = auto_convert_lobs 

1280 self.coerce_to_decimal = coerce_to_decimal 

1281 if self._use_nchar_for_unicode: 

1282 self.colspecs = self.colspecs.copy() 

1283 self.colspecs[sqltypes.Unicode] = _OracleUnicodeStringNCHAR 

1284 self.colspecs[sqltypes.UnicodeText] = _OracleUnicodeTextNCLOB 

1285 

1286 dbapi_module = self.dbapi 

1287 

1288 if dbapi_module is not None: 

1289 # these constants will first be seen in SQLAlchemy datatypes 

1290 # coming from the get_dbapi_type() method. We then 

1291 # will place the following types into setinputsizes() calls 

1292 # on each statement. Oracle constants that are not in this 

1293 # list will not be put into setinputsizes(). 

1294 self.include_set_input_sizes = { 

1295 dbapi_module.DATETIME, 

1296 dbapi_module.DB_TYPE_NVARCHAR, # used for CLOB, NCLOB 

1297 dbapi_module.DB_TYPE_RAW, # used for BLOB 

1298 dbapi_module.NCLOB, # not currently used except for OUT param 

1299 dbapi_module.CLOB, # not currently used except for OUT param 

1300 dbapi_module.LOB, # not currently used 

1301 dbapi_module.BLOB, # not currently used except for OUT param 

1302 dbapi_module.NCHAR, 

1303 dbapi_module.FIXED_NCHAR, 

1304 dbapi_module.FIXED_CHAR, 

1305 dbapi_module.TIMESTAMP, 

1306 # we dont make use of Oracle's JSON serialization; does not 

1307 # handle "none as null" 

1308 # dbapi_module.DB_TYPE_JSON, 

1309 int, # _OracleInteger, 

1310 # _OracleBINARY_FLOAT, _OracleBINARY_DOUBLE, 

1311 dbapi_module.NATIVE_FLOAT, 

1312 } 

1313 

1314 self._paramval = lambda value: value.getvalue() 

1315 

1316 def retrieve_dbapi_version(self, dbapi): 

1317 return util.parse_version_string(getattr(dbapi, "version", None)) 

1318 

1319 @property 

1320 def cx_oracle_ver(self): 

1321 """Legacy accessor for :attr:`.Dialect.dbapi_version`. 

1322 

1323 Retained for backwards compatibility; ``(0, 0, 0)`` is returned 

1324 when no version can be determined. 

1325 

1326 """ 

1327 version = self._dbapi_version_or_none 

1328 return version if version is not None else util.VersionInfo((0, 0, 0)) 

1329 

1330 @classmethod 

1331 def import_dbapi(cls): 

1332 import cx_Oracle 

1333 

1334 return cx_Oracle 

1335 

1336 def initialize(self, connection): 

1337 super().initialize(connection) 

1338 self._detect_decimal_char(connection) 

1339 

1340 def get_isolation_level(self, dbapi_connection): 

1341 # sources: 

1342 

1343 # general idea of transaction id, have to start one, etc. 

1344 # https://stackoverflow.com/questions/10711204/how-to-check-isoloation-level 

1345 

1346 # how to decode xid cols from v$transaction to match 

1347 # https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:9532779900346079444 

1348 

1349 # Oracle tuple comparison without using IN: 

1350 # https://www.sql-workbench.eu/comparison/tuple_comparison.html 

1351 

1352 with dbapi_connection.cursor() as cursor: 

1353 # this is the only way to ensure a transaction is started without 

1354 # actually running DML. There's no way to see the configured 

1355 # isolation level without getting it from v$transaction which 

1356 # means transaction has to be started. 

1357 outval = cursor.var(str) 

1358 cursor.execute( 

1359 """ 

1360 begin 

1361 :trans_id := dbms_transaction.local_transaction_id( TRUE ); 

1362 end; 

1363 """, 

1364 {"trans_id": outval}, 

1365 ) 

1366 trans_id = outval.getvalue() 

1367 xidusn, xidslot, xidsqn = trans_id.split(".", 2) 

1368 

1369 cursor.execute( 

1370 "SELECT CASE BITAND(t.flag, POWER(2, 28)) " 

1371 "WHEN 0 THEN 'READ COMMITTED' " 

1372 "ELSE 'SERIALIZABLE' END AS isolation_level " 

1373 "FROM v$transaction t WHERE " 

1374 "(t.xidusn, t.xidslot, t.xidsqn) = " 

1375 "((:xidusn, :xidslot, :xidsqn))", 

1376 {"xidusn": xidusn, "xidslot": xidslot, "xidsqn": xidsqn}, 

1377 ) 

1378 row = cursor.fetchone() 

1379 if row is None: 

1380 raise exc.InvalidRequestError( 

1381 "could not retrieve isolation level" 

1382 ) 

1383 result = row[0] 

1384 

1385 return result 

1386 

1387 def get_isolation_level_values(self, dbapi_connection): 

1388 return super().get_isolation_level_values(dbapi_connection) + [ 

1389 "AUTOCOMMIT" 

1390 ] 

1391 

1392 def set_isolation_level(self, dbapi_connection, level): 

1393 if level == "AUTOCOMMIT": 

1394 dbapi_connection.autocommit = True 

1395 else: 

1396 dbapi_connection.autocommit = False 

1397 dbapi_connection.rollback() 

1398 with dbapi_connection.cursor() as cursor: 

1399 cursor.execute(f"ALTER SESSION SET ISOLATION_LEVEL={level}") 

1400 

1401 def detect_autocommit_setting(self, dbapi_conn) -> bool: 

1402 return bool(dbapi_conn.autocommit) 

1403 

1404 def _detect_decimal_char(self, connection): 

1405 # we have the option to change this setting upon connect, 

1406 # or just look at what it is upon connect and convert. 

1407 # to minimize the chance of interference with changes to 

1408 # NLS_TERRITORY or formatting behavior of the DB, we opt 

1409 # to just look at it 

1410 

1411 dbapi_connection = connection.connection 

1412 

1413 with dbapi_connection.cursor() as cursor: 

1414 # issue #8744 

1415 # nls_session_parameters is not available in some Oracle 

1416 # modes like "mount mode". But then, v$nls_parameters is not 

1417 # available if the connection doesn't have SYSDBA priv. 

1418 # 

1419 # simplify the whole thing and just use the method that we were 

1420 # doing in the test suite already, selecting a number 

1421 

1422 def output_type_handler( 

1423 cursor, name, defaultType, size, precision, scale 

1424 ): 

1425 return cursor.var( 

1426 self.dbapi.STRING, 255, arraysize=cursor.arraysize 

1427 ) 

1428 

1429 cursor.outputtypehandler = output_type_handler 

1430 cursor.execute("SELECT 1.1 FROM DUAL") 

1431 value = cursor.fetchone()[0] 

1432 

1433 decimal_char = value.lstrip("0")[1] 

1434 assert not decimal_char[0].isdigit() 

1435 

1436 self._decimal_char = decimal_char 

1437 

1438 if self._decimal_char != ".": 

1439 _detect_decimal = self._detect_decimal 

1440 _to_decimal = self._to_decimal 

1441 

1442 self._detect_decimal = lambda value: _detect_decimal( 

1443 value.replace(self._decimal_char, ".") 

1444 ) 

1445 self._to_decimal = lambda value: _to_decimal( 

1446 value.replace(self._decimal_char, ".") 

1447 ) 

1448 

1449 def _detect_decimal(self, value): 

1450 if "." in value: 

1451 return self._to_decimal(value) 

1452 else: 

1453 return int(value) 

1454 

1455 _to_decimal = decimal.Decimal 

1456 

1457 def _cursor_lob_as_str_var(self, cursor, default_type, outconverter=None): 

1458 """Produce a ``cursor.var()`` that receives a CLOB / NCLOB as a 

1459 string. 

1460 

1461 Used both by the connection-wide outputtypehandler and by 

1462 type-level handlers such as that of :class:`._OracleJson`, which 

1463 need the LOB contents as a string in order to decode them further. 

1464 

1465 """ 

1466 cx_Oracle = self.dbapi 

1467 

1468 kw = self._cursor_var_unicode_kwargs 

1469 if outconverter is not None: 

1470 kw = {**kw, "outconverter": outconverter} 

1471 

1472 return cursor.var( 

1473 ( 

1474 cx_Oracle.DB_TYPE_VARCHAR 

1475 if default_type is cx_Oracle.CLOB 

1476 else cx_Oracle.DB_TYPE_NVARCHAR 

1477 ), 

1478 _CX_ORACLE_MAGIC_LOB_SIZE, 

1479 cursor.arraysize, 

1480 **kw, 

1481 ) 

1482 

1483 def _generate_connection_outputtype_handler(self): 

1484 """establish the default outputtypehandler established at the 

1485 connection level. 

1486 

1487 note that when using a Compiled statement that has types (e.g. 

1488 TypeEngine), we set up a per-cursor handler instead which supercedes 

1489 this one, using Oracle-specific TypeEngine handlers delivered by the 

1490 _cx_oracle_outputtypehandler() method of each one. 

1491 

1492 """ 

1493 

1494 dialect = self 

1495 cx_Oracle = dialect.dbapi 

1496 

1497 number_handler = _OracleNUMBER( 

1498 asdecimal=True 

1499 )._cx_oracle_outputtypehandler(dialect) 

1500 float_handler = _OracleNUMBER( 

1501 asdecimal=False 

1502 )._cx_oracle_outputtypehandler(dialect) 

1503 

1504 def output_type_handler( 

1505 cursor, name, default_type, size, precision, scale 

1506 ): 

1507 if ( 

1508 default_type == cx_Oracle.NUMBER 

1509 and default_type is not cx_Oracle.NATIVE_FLOAT 

1510 ): 

1511 if not dialect.coerce_to_decimal: 

1512 return None 

1513 elif precision == 0 and scale in (0, -127): 

1514 # ambiguous type, this occurs when selecting 

1515 # numbers from deep subqueries 

1516 return cursor.var( 

1517 cx_Oracle.STRING, 

1518 255, 

1519 outconverter=dialect._detect_decimal, 

1520 arraysize=cursor.arraysize, 

1521 ) 

1522 elif precision and scale > 0: 

1523 return number_handler( 

1524 cursor, name, default_type, size, precision, scale 

1525 ) 

1526 else: 

1527 return float_handler( 

1528 cursor, name, default_type, size, precision, scale 

1529 ) 

1530 

1531 # if unicode options were specified, add a decoder, otherwise 

1532 # cx_Oracle should return Unicode 

1533 elif ( 

1534 dialect._cursor_var_unicode_kwargs 

1535 and default_type 

1536 in ( 

1537 cx_Oracle.STRING, 

1538 cx_Oracle.FIXED_CHAR, 

1539 ) 

1540 and default_type is not cx_Oracle.CLOB 

1541 and default_type is not cx_Oracle.NCLOB 

1542 ): 

1543 return cursor.var( 

1544 str, 

1545 size, 

1546 cursor.arraysize, 

1547 **dialect._cursor_var_unicode_kwargs, 

1548 ) 

1549 

1550 elif dialect.auto_convert_lobs and default_type in ( 

1551 cx_Oracle.CLOB, 

1552 cx_Oracle.NCLOB, 

1553 ): 

1554 return dialect._cursor_lob_as_str_var(cursor, default_type) 

1555 

1556 elif dialect.auto_convert_lobs and default_type in ( 

1557 cx_Oracle.BLOB, 

1558 ): 

1559 return cursor.var( 

1560 cx_Oracle.DB_TYPE_RAW, 

1561 _CX_ORACLE_MAGIC_LOB_SIZE, 

1562 cursor.arraysize, 

1563 ) 

1564 elif ( 

1565 default_type is cx_Oracle.DB_TYPE_JSON 

1566 and dialect._json_deserializer is not None 

1567 ): 

1568 return cursor.var( 

1569 cx_Oracle.DB_TYPE_VARCHAR, 

1570 _CX_ORACLE_MAX_JSON_CONVERTED, 

1571 cursor.arraysize, 

1572 outconverter=dialect._json_deserializer, 

1573 ) 

1574 

1575 return output_type_handler 

1576 

1577 def on_connect(self): 

1578 output_type_handler = self._generate_connection_outputtype_handler() 

1579 

1580 def on_connect(conn): 

1581 conn.outputtypehandler = output_type_handler 

1582 

1583 return on_connect 

1584 

1585 def create_connect_args(self, url): 

1586 opts = dict(url.query) 

1587 

1588 database = url.database 

1589 service_name = opts.pop("service_name", None) 

1590 if database or service_name: 

1591 # if we have a database, then we have a remote host 

1592 port = url.port 

1593 if port: 

1594 port = int(port) 

1595 else: 

1596 port = 1521 

1597 

1598 if database and service_name: 

1599 raise exc.InvalidRequestError( 

1600 '"service_name" option shouldn\'t ' 

1601 'be used with a "database" part of the url' 

1602 ) 

1603 if database: 

1604 makedsn_kwargs = {"sid": database} 

1605 if service_name: 

1606 makedsn_kwargs = {"service_name": service_name} 

1607 

1608 dsn = self.dbapi.makedsn(url.host, port, **makedsn_kwargs) 

1609 else: 

1610 # we have a local tnsname 

1611 dsn = url.host 

1612 

1613 if dsn is not None: 

1614 opts["dsn"] = dsn 

1615 if url.password is not None: 

1616 opts["password"] = url.password 

1617 if url.username is not None: 

1618 opts["user"] = url.username 

1619 

1620 def convert_cx_oracle_constant(value): 

1621 if isinstance(value, str): 

1622 try: 

1623 int_val = int(value) 

1624 except ValueError: 

1625 value = value.upper() 

1626 return getattr(self.dbapi, value) 

1627 else: 

1628 return int_val 

1629 else: 

1630 return value 

1631 

1632 util.coerce_kw_type(opts, "mode", convert_cx_oracle_constant) 

1633 util.coerce_kw_type(opts, "threaded", bool) 

1634 util.coerce_kw_type(opts, "events", bool) 

1635 util.coerce_kw_type(opts, "purity", convert_cx_oracle_constant) 

1636 return ([], opts) 

1637 

1638 def _get_server_version_info(self, connection): 

1639 return tuple(int(x) for x in connection.connection.version.split(".")) 

1640 

1641 def is_disconnect(self, e, connection, cursor): 

1642 (error,) = e.args 

1643 if isinstance( 

1644 e, (self.dbapi.InterfaceError, self.dbapi.DatabaseError) 

1645 ) and "not connected" in str(e): 

1646 return True 

1647 

1648 if hasattr(error, "code") and error.code in { 

1649 28, 

1650 3114, 

1651 3113, 

1652 3135, 

1653 1033, 

1654 2396, 

1655 }: 

1656 # ORA-00028: your session has been killed 

1657 # ORA-03114: not connected to ORACLE 

1658 # ORA-03113: end-of-file on communication channel 

1659 # ORA-03135: connection lost contact 

1660 # ORA-01033: ORACLE initialization or shutdown in progress 

1661 # ORA-02396: exceeded maximum idle time, please connect again 

1662 # TODO: Others ? 

1663 return True 

1664 

1665 if re.match(r"^(?:DPI-1010|DPI-1080|DPY-1001|DPY-4011)", str(e)): 

1666 # DPI-1010: not connected 

1667 # DPI-1080: connection was closed by ORA-3113 

1668 # python-oracledb's DPY-1001: not connected to database 

1669 # python-oracledb's DPY-4011: the database or network closed the 

1670 # connection 

1671 # TODO: others? 

1672 return True 

1673 

1674 return False 

1675 

1676 def create_xid(self): 

1677 id_ = random.randint(0, 2**128) 

1678 return (0x1234, "%032x" % id_, "%032x" % 9) 

1679 

1680 def do_executemany(self, cursor, statement, parameters, context=None): 

1681 if isinstance(parameters, tuple): 

1682 parameters = list(parameters) 

1683 cursor.executemany(statement, parameters) 

1684 

1685 def do_begin_twophase(self, connection, xid): 

1686 connection.connection.begin(*xid) 

1687 connection.connection.info["cx_oracle_xid"] = xid 

1688 

1689 def do_prepare_twophase(self, connection, xid): 

1690 result = connection.connection.prepare() 

1691 connection.info["cx_oracle_prepared"] = result 

1692 

1693 def do_rollback_twophase( 

1694 self, connection, xid, is_prepared=True, recover=False 

1695 ): 

1696 self.do_rollback(connection.connection) 

1697 # TODO: need to end XA state here 

1698 

1699 def do_commit_twophase( 

1700 self, connection, xid, is_prepared=True, recover=False 

1701 ): 

1702 if not is_prepared: 

1703 self.do_commit(connection.connection) 

1704 else: 

1705 if recover: 

1706 raise NotImplementedError( 

1707 "2pc recovery not implemented for cx_Oracle" 

1708 ) 

1709 oci_prepared = connection.info["cx_oracle_prepared"] 

1710 if oci_prepared: 

1711 self.do_commit(connection.connection) 

1712 # TODO: need to end XA state here 

1713 

1714 def do_set_input_sizes(self, cursor, list_of_tuples, context): 

1715 if self.positional: 

1716 # not usually used, here to support if someone is modifying 

1717 # the dialect to use positional style 

1718 cursor.setinputsizes( 

1719 *[dbtype for key, dbtype, sqltype in list_of_tuples] 

1720 ) 

1721 else: 

1722 collection = ( 

1723 (key, dbtype) 

1724 for key, dbtype, sqltype in list_of_tuples 

1725 if dbtype 

1726 ) 

1727 

1728 cursor.setinputsizes(**{key: dbtype for key, dbtype in collection}) 

1729 

1730 def do_recover_twophase(self, connection): 

1731 raise NotImplementedError( 

1732 "recover two phase query for cx_Oracle not implemented" 

1733 ) 

1734 

1735 

1736dialect = OracleDialect_cx_oracle