SQLAlchemy 2.0.36__cp313-cp313-win32.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. SQLAlchemy-2.0.36.dist-info/LICENSE +19 -0
  2. SQLAlchemy-2.0.36.dist-info/METADATA +243 -0
  3. SQLAlchemy-2.0.36.dist-info/RECORD +273 -0
  4. SQLAlchemy-2.0.36.dist-info/WHEEL +5 -0
  5. SQLAlchemy-2.0.36.dist-info/top_level.txt +1 -0
  6. sqlalchemy/__init__.py +294 -0
  7. sqlalchemy/connectors/__init__.py +18 -0
  8. sqlalchemy/connectors/aioodbc.py +174 -0
  9. sqlalchemy/connectors/asyncio.py +213 -0
  10. sqlalchemy/connectors/pyodbc.py +249 -0
  11. sqlalchemy/cyextension/__init__.py +6 -0
  12. sqlalchemy/cyextension/collections.cp313-win32.pyd +0 -0
  13. sqlalchemy/cyextension/collections.pyx +409 -0
  14. sqlalchemy/cyextension/immutabledict.cp313-win32.pyd +0 -0
  15. sqlalchemy/cyextension/immutabledict.pxd +8 -0
  16. sqlalchemy/cyextension/immutabledict.pyx +133 -0
  17. sqlalchemy/cyextension/processors.cp313-win32.pyd +0 -0
  18. sqlalchemy/cyextension/processors.pyx +68 -0
  19. sqlalchemy/cyextension/resultproxy.cp313-win32.pyd +0 -0
  20. sqlalchemy/cyextension/resultproxy.pyx +102 -0
  21. sqlalchemy/cyextension/util.cp313-win32.pyd +0 -0
  22. sqlalchemy/cyextension/util.pyx +91 -0
  23. sqlalchemy/dialects/__init__.py +61 -0
  24. sqlalchemy/dialects/_typing.py +25 -0
  25. sqlalchemy/dialects/mssql/__init__.py +88 -0
  26. sqlalchemy/dialects/mssql/aioodbc.py +64 -0
  27. sqlalchemy/dialects/mssql/base.py +4010 -0
  28. sqlalchemy/dialects/mssql/information_schema.py +254 -0
  29. sqlalchemy/dialects/mssql/json.py +133 -0
  30. sqlalchemy/dialects/mssql/provision.py +162 -0
  31. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  32. sqlalchemy/dialects/mssql/pyodbc.py +745 -0
  33. sqlalchemy/dialects/mysql/__init__.py +101 -0
  34. sqlalchemy/dialects/mysql/aiomysql.py +333 -0
  35. sqlalchemy/dialects/mysql/asyncmy.py +337 -0
  36. sqlalchemy/dialects/mysql/base.py +3494 -0
  37. sqlalchemy/dialects/mysql/cymysql.py +84 -0
  38. sqlalchemy/dialects/mysql/dml.py +219 -0
  39. sqlalchemy/dialects/mysql/enumerated.py +244 -0
  40. sqlalchemy/dialects/mysql/expression.py +141 -0
  41. sqlalchemy/dialects/mysql/json.py +81 -0
  42. sqlalchemy/dialects/mysql/mariadb.py +32 -0
  43. sqlalchemy/dialects/mysql/mariadbconnector.py +277 -0
  44. sqlalchemy/dialects/mysql/mysqlconnector.py +180 -0
  45. sqlalchemy/dialects/mysql/mysqldb.py +303 -0
  46. sqlalchemy/dialects/mysql/provision.py +110 -0
  47. sqlalchemy/dialects/mysql/pymysql.py +137 -0
  48. sqlalchemy/dialects/mysql/pyodbc.py +138 -0
  49. sqlalchemy/dialects/mysql/reflection.py +677 -0
  50. sqlalchemy/dialects/mysql/reserved_words.py +571 -0
  51. sqlalchemy/dialects/mysql/types.py +774 -0
  52. sqlalchemy/dialects/oracle/__init__.py +67 -0
  53. sqlalchemy/dialects/oracle/base.py +3271 -0
  54. sqlalchemy/dialects/oracle/cx_oracle.py +1483 -0
  55. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  56. sqlalchemy/dialects/oracle/oracledb.py +431 -0
  57. sqlalchemy/dialects/oracle/provision.py +220 -0
  58. sqlalchemy/dialects/oracle/types.py +287 -0
  59. sqlalchemy/dialects/postgresql/__init__.py +167 -0
  60. sqlalchemy/dialects/postgresql/_psycopg_common.py +187 -0
  61. sqlalchemy/dialects/postgresql/array.py +425 -0
  62. sqlalchemy/dialects/postgresql/asyncpg.py +1274 -0
  63. sqlalchemy/dialects/postgresql/base.py +5008 -0
  64. sqlalchemy/dialects/postgresql/dml.py +310 -0
  65. sqlalchemy/dialects/postgresql/ext.py +496 -0
  66. sqlalchemy/dialects/postgresql/hstore.py +397 -0
  67. sqlalchemy/dialects/postgresql/json.py +333 -0
  68. sqlalchemy/dialects/postgresql/named_types.py +509 -0
  69. sqlalchemy/dialects/postgresql/operators.py +129 -0
  70. sqlalchemy/dialects/postgresql/pg8000.py +662 -0
  71. sqlalchemy/dialects/postgresql/pg_catalog.py +300 -0
  72. sqlalchemy/dialects/postgresql/provision.py +175 -0
  73. sqlalchemy/dialects/postgresql/psycopg.py +772 -0
  74. sqlalchemy/dialects/postgresql/psycopg2.py +886 -0
  75. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  76. sqlalchemy/dialects/postgresql/ranges.py +1029 -0
  77. sqlalchemy/dialects/postgresql/types.py +303 -0
  78. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  79. sqlalchemy/dialects/sqlite/aiosqlite.py +396 -0
  80. sqlalchemy/dialects/sqlite/base.py +2805 -0
  81. sqlalchemy/dialects/sqlite/dml.py +240 -0
  82. sqlalchemy/dialects/sqlite/json.py +92 -0
  83. sqlalchemy/dialects/sqlite/provision.py +198 -0
  84. sqlalchemy/dialects/sqlite/pysqlcipher.py +155 -0
  85. sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
  86. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  87. sqlalchemy/engine/__init__.py +62 -0
  88. sqlalchemy/engine/_py_processors.py +136 -0
  89. sqlalchemy/engine/_py_row.py +128 -0
  90. sqlalchemy/engine/_py_util.py +74 -0
  91. sqlalchemy/engine/base.py +3375 -0
  92. sqlalchemy/engine/characteristics.py +155 -0
  93. sqlalchemy/engine/create.py +875 -0
  94. sqlalchemy/engine/cursor.py +2181 -0
  95. sqlalchemy/engine/default.py +2365 -0
  96. sqlalchemy/engine/events.py +951 -0
  97. sqlalchemy/engine/interfaces.py +3403 -0
  98. sqlalchemy/engine/mock.py +131 -0
  99. sqlalchemy/engine/processors.py +61 -0
  100. sqlalchemy/engine/reflection.py +2098 -0
  101. sqlalchemy/engine/result.py +2382 -0
  102. sqlalchemy/engine/row.py +401 -0
  103. sqlalchemy/engine/strategies.py +19 -0
  104. sqlalchemy/engine/url.py +910 -0
  105. sqlalchemy/engine/util.py +167 -0
  106. sqlalchemy/event/__init__.py +25 -0
  107. sqlalchemy/event/api.py +225 -0
  108. sqlalchemy/event/attr.py +655 -0
  109. sqlalchemy/event/base.py +470 -0
  110. sqlalchemy/event/legacy.py +246 -0
  111. sqlalchemy/event/registry.py +386 -0
  112. sqlalchemy/events.py +17 -0
  113. sqlalchemy/exc.py +830 -0
  114. sqlalchemy/ext/__init__.py +11 -0
  115. sqlalchemy/ext/associationproxy.py +2013 -0
  116. sqlalchemy/ext/asyncio/__init__.py +25 -0
  117. sqlalchemy/ext/asyncio/base.py +279 -0
  118. sqlalchemy/ext/asyncio/engine.py +1466 -0
  119. sqlalchemy/ext/asyncio/exc.py +21 -0
  120. sqlalchemy/ext/asyncio/result.py +961 -0
  121. sqlalchemy/ext/asyncio/scoping.py +1614 -0
  122. sqlalchemy/ext/asyncio/session.py +1936 -0
  123. sqlalchemy/ext/automap.py +1691 -0
  124. sqlalchemy/ext/baked.py +574 -0
  125. sqlalchemy/ext/compiler.py +570 -0
  126. sqlalchemy/ext/declarative/__init__.py +65 -0
  127. sqlalchemy/ext/declarative/extensions.py +548 -0
  128. sqlalchemy/ext/horizontal_shard.py +481 -0
  129. sqlalchemy/ext/hybrid.py +1514 -0
  130. sqlalchemy/ext/indexable.py +341 -0
  131. sqlalchemy/ext/instrumentation.py +450 -0
  132. sqlalchemy/ext/mutable.py +1073 -0
  133. sqlalchemy/ext/mypy/__init__.py +6 -0
  134. sqlalchemy/ext/mypy/apply.py +320 -0
  135. sqlalchemy/ext/mypy/decl_class.py +515 -0
  136. sqlalchemy/ext/mypy/infer.py +590 -0
  137. sqlalchemy/ext/mypy/names.py +335 -0
  138. sqlalchemy/ext/mypy/plugin.py +303 -0
  139. sqlalchemy/ext/mypy/util.py +357 -0
  140. sqlalchemy/ext/orderinglist.py +416 -0
  141. sqlalchemy/ext/serializer.py +181 -0
  142. sqlalchemy/future/__init__.py +16 -0
  143. sqlalchemy/future/engine.py +15 -0
  144. sqlalchemy/inspection.py +174 -0
  145. sqlalchemy/log.py +288 -0
  146. sqlalchemy/orm/__init__.py +170 -0
  147. sqlalchemy/orm/_orm_constructors.py +2571 -0
  148. sqlalchemy/orm/_typing.py +179 -0
  149. sqlalchemy/orm/attributes.py +2835 -0
  150. sqlalchemy/orm/base.py +973 -0
  151. sqlalchemy/orm/bulk_persistence.py +2123 -0
  152. sqlalchemy/orm/clsregistry.py +571 -0
  153. sqlalchemy/orm/collections.py +1620 -0
  154. sqlalchemy/orm/context.py +3268 -0
  155. sqlalchemy/orm/decl_api.py +1883 -0
  156. sqlalchemy/orm/decl_base.py +2190 -0
  157. sqlalchemy/orm/dependency.py +1304 -0
  158. sqlalchemy/orm/descriptor_props.py +1076 -0
  159. sqlalchemy/orm/dynamic.py +300 -0
  160. sqlalchemy/orm/evaluator.py +379 -0
  161. sqlalchemy/orm/events.py +3261 -0
  162. sqlalchemy/orm/exc.py +228 -0
  163. sqlalchemy/orm/identity.py +302 -0
  164. sqlalchemy/orm/instrumentation.py +754 -0
  165. sqlalchemy/orm/interfaces.py +1474 -0
  166. sqlalchemy/orm/loading.py +1682 -0
  167. sqlalchemy/orm/mapped_collection.py +557 -0
  168. sqlalchemy/orm/mapper.py +4432 -0
  169. sqlalchemy/orm/path_registry.py +811 -0
  170. sqlalchemy/orm/persistence.py +1782 -0
  171. sqlalchemy/orm/properties.py +886 -0
  172. sqlalchemy/orm/query.py +3396 -0
  173. sqlalchemy/orm/relationships.py +3500 -0
  174. sqlalchemy/orm/scoping.py +2165 -0
  175. sqlalchemy/orm/session.py +5301 -0
  176. sqlalchemy/orm/state.py +1143 -0
  177. sqlalchemy/orm/state_changes.py +198 -0
  178. sqlalchemy/orm/strategies.py +3473 -0
  179. sqlalchemy/orm/strategy_options.py +2569 -0
  180. sqlalchemy/orm/sync.py +164 -0
  181. sqlalchemy/orm/unitofwork.py +796 -0
  182. sqlalchemy/orm/util.py +2424 -0
  183. sqlalchemy/orm/writeonly.py +678 -0
  184. sqlalchemy/pool/__init__.py +44 -0
  185. sqlalchemy/pool/base.py +1515 -0
  186. sqlalchemy/pool/events.py +370 -0
  187. sqlalchemy/pool/impl.py +581 -0
  188. sqlalchemy/py.typed +0 -0
  189. sqlalchemy/schema.py +70 -0
  190. sqlalchemy/sql/__init__.py +145 -0
  191. sqlalchemy/sql/_dml_constructors.py +140 -0
  192. sqlalchemy/sql/_elements_constructors.py +1850 -0
  193. sqlalchemy/sql/_orm_types.py +20 -0
  194. sqlalchemy/sql/_py_util.py +75 -0
  195. sqlalchemy/sql/_selectable_constructors.py +635 -0
  196. sqlalchemy/sql/_typing.py +460 -0
  197. sqlalchemy/sql/annotation.py +585 -0
  198. sqlalchemy/sql/base.py +2185 -0
  199. sqlalchemy/sql/cache_key.py +1057 -0
  200. sqlalchemy/sql/coercions.py +1405 -0
  201. sqlalchemy/sql/compiler.py +7818 -0
  202. sqlalchemy/sql/crud.py +1669 -0
  203. sqlalchemy/sql/ddl.py +1378 -0
  204. sqlalchemy/sql/default_comparator.py +552 -0
  205. sqlalchemy/sql/dml.py +1817 -0
  206. sqlalchemy/sql/elements.py +5499 -0
  207. sqlalchemy/sql/events.py +455 -0
  208. sqlalchemy/sql/expression.py +162 -0
  209. sqlalchemy/sql/functions.py +2055 -0
  210. sqlalchemy/sql/lambdas.py +1449 -0
  211. sqlalchemy/sql/naming.py +212 -0
  212. sqlalchemy/sql/operators.py +2579 -0
  213. sqlalchemy/sql/roles.py +323 -0
  214. sqlalchemy/sql/schema.py +6158 -0
  215. sqlalchemy/sql/selectable.py +7004 -0
  216. sqlalchemy/sql/sqltypes.py +3827 -0
  217. sqlalchemy/sql/traversals.py +1024 -0
  218. sqlalchemy/sql/type_api.py +2339 -0
  219. sqlalchemy/sql/util.py +1486 -0
  220. sqlalchemy/sql/visitors.py +1165 -0
  221. sqlalchemy/testing/__init__.py +96 -0
  222. sqlalchemy/testing/assertions.py +989 -0
  223. sqlalchemy/testing/assertsql.py +516 -0
  224. sqlalchemy/testing/asyncio.py +135 -0
  225. sqlalchemy/testing/config.py +427 -0
  226. sqlalchemy/testing/engines.py +472 -0
  227. sqlalchemy/testing/entities.py +117 -0
  228. sqlalchemy/testing/exclusions.py +435 -0
  229. sqlalchemy/testing/fixtures/__init__.py +28 -0
  230. sqlalchemy/testing/fixtures/base.py +366 -0
  231. sqlalchemy/testing/fixtures/mypy.py +312 -0
  232. sqlalchemy/testing/fixtures/orm.py +227 -0
  233. sqlalchemy/testing/fixtures/sql.py +503 -0
  234. sqlalchemy/testing/pickleable.py +155 -0
  235. sqlalchemy/testing/plugin/__init__.py +6 -0
  236. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  237. sqlalchemy/testing/plugin/plugin_base.py +779 -0
  238. sqlalchemy/testing/plugin/pytestplugin.py +868 -0
  239. sqlalchemy/testing/profiling.py +324 -0
  240. sqlalchemy/testing/provision.py +496 -0
  241. sqlalchemy/testing/requirements.py +1818 -0
  242. sqlalchemy/testing/schema.py +224 -0
  243. sqlalchemy/testing/suite/__init__.py +19 -0
  244. sqlalchemy/testing/suite/test_cte.py +211 -0
  245. sqlalchemy/testing/suite/test_ddl.py +389 -0
  246. sqlalchemy/testing/suite/test_deprecations.py +153 -0
  247. sqlalchemy/testing/suite/test_dialect.py +740 -0
  248. sqlalchemy/testing/suite/test_insert.py +630 -0
  249. sqlalchemy/testing/suite/test_reflection.py +3225 -0
  250. sqlalchemy/testing/suite/test_results.py +502 -0
  251. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  252. sqlalchemy/testing/suite/test_select.py +1999 -0
  253. sqlalchemy/testing/suite/test_sequence.py +317 -0
  254. sqlalchemy/testing/suite/test_types.py +2141 -0
  255. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  256. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  257. sqlalchemy/testing/util.py +537 -0
  258. sqlalchemy/testing/warnings.py +52 -0
  259. sqlalchemy/types.py +76 -0
  260. sqlalchemy/util/__init__.py +160 -0
  261. sqlalchemy/util/_collections.py +715 -0
  262. sqlalchemy/util/_concurrency_py3k.py +288 -0
  263. sqlalchemy/util/_has_cy.py +40 -0
  264. sqlalchemy/util/_py_collections.py +541 -0
  265. sqlalchemy/util/compat.py +301 -0
  266. sqlalchemy/util/concurrency.py +108 -0
  267. sqlalchemy/util/deprecations.py +401 -0
  268. sqlalchemy/util/langhelpers.py +2218 -0
  269. sqlalchemy/util/preloaded.py +150 -0
  270. sqlalchemy/util/queue.py +322 -0
  271. sqlalchemy/util/tool_support.py +201 -0
  272. sqlalchemy/util/topological.py +120 -0
  273. sqlalchemy/util/typing.py +629 -0
@@ -0,0 +1,1483 @@
1
+ # dialects/oracle/cx_oracle.py
2
+ # Copyright (C) 2005-2024 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
+
10
+ r"""
11
+ .. dialect:: oracle+cx_oracle
12
+ :name: cx-Oracle
13
+ :dbapi: cx_oracle
14
+ :connectstring: oracle+cx_oracle://user:pass@hostname:port[/dbname][?service_name=<service>[&key=value&key=value...]]
15
+ :url: https://oracle.github.io/python-cx_Oracle/
16
+
17
+ DSN vs. Hostname connections
18
+ -----------------------------
19
+
20
+ cx_Oracle provides several methods of indicating the target database. The
21
+ dialect translates from a series of different URL forms.
22
+
23
+ Hostname Connections with Easy Connect Syntax
24
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
25
+
26
+ Given a hostname, port and service name of the target Oracle Database, for
27
+ example from Oracle's `Easy Connect syntax
28
+ <https://cx-oracle.readthedocs.io/en/latest/user_guide/connection_handling.html#easy-connect-syntax-for-connection-strings>`_,
29
+ then connect in SQLAlchemy using the ``service_name`` query string parameter::
30
+
31
+ engine = create_engine("oracle+cx_oracle://scott:tiger@hostname:port/?service_name=myservice&encoding=UTF-8&nencoding=UTF-8")
32
+
33
+ The `full Easy Connect syntax
34
+ <https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-B0437826-43C1-49EC-A94D-B650B6A4A6EE>`_
35
+ is not supported. Instead, use a ``tnsnames.ora`` file and connect using a
36
+ DSN.
37
+
38
+ Connections with tnsnames.ora or Oracle Cloud
39
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
40
+
41
+ Alternatively, if no port, database name, or ``service_name`` is provided, the
42
+ dialect will use an Oracle DSN "connection string". This takes the "hostname"
43
+ portion of the URL as the data source name. For example, if the
44
+ ``tnsnames.ora`` file contains a `Net Service Name
45
+ <https://cx-oracle.readthedocs.io/en/latest/user_guide/connection_handling.html#net-service-names-for-connection-strings>`_
46
+ of ``myalias`` as below::
47
+
48
+ myalias =
49
+ (DESCRIPTION =
50
+ (ADDRESS = (PROTOCOL = TCP)(HOST = mymachine.example.com)(PORT = 1521))
51
+ (CONNECT_DATA =
52
+ (SERVER = DEDICATED)
53
+ (SERVICE_NAME = orclpdb1)
54
+ )
55
+ )
56
+
57
+ The cx_Oracle dialect connects to this database service when ``myalias`` is the
58
+ hostname portion of the URL, without specifying a port, database name or
59
+ ``service_name``::
60
+
61
+ engine = create_engine("oracle+cx_oracle://scott:tiger@myalias/?encoding=UTF-8&nencoding=UTF-8")
62
+
63
+ Users of Oracle Cloud should use this syntax and also configure the cloud
64
+ wallet as shown in cx_Oracle documentation `Connecting to Autononmous Databases
65
+ <https://cx-oracle.readthedocs.io/en/latest/user_guide/connection_handling.html#autonomousdb>`_.
66
+
67
+ SID Connections
68
+ ^^^^^^^^^^^^^^^
69
+
70
+ To use Oracle's obsolete SID connection syntax, the SID can be passed in a
71
+ "database name" portion of the URL as below::
72
+
73
+ engine = create_engine("oracle+cx_oracle://scott:tiger@hostname:1521/dbname?encoding=UTF-8&nencoding=UTF-8")
74
+
75
+ Above, the DSN passed to cx_Oracle is created by ``cx_Oracle.makedsn()`` as
76
+ follows::
77
+
78
+ >>> import cx_Oracle
79
+ >>> cx_Oracle.makedsn("hostname", 1521, sid="dbname")
80
+ '(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=hostname)(PORT=1521))(CONNECT_DATA=(SID=dbname)))'
81
+
82
+ Passing cx_Oracle connect arguments
83
+ -----------------------------------
84
+
85
+ Additional connection arguments can usually be passed via the URL
86
+ query string; particular symbols like ``cx_Oracle.SYSDBA`` are intercepted
87
+ and converted to the correct symbol::
88
+
89
+ e = create_engine(
90
+ "oracle+cx_oracle://user:pass@dsn?encoding=UTF-8&nencoding=UTF-8&mode=SYSDBA&events=true")
91
+
92
+ .. versionchanged:: 1.3 the cx_oracle dialect now accepts all argument names
93
+ within the URL string itself, to be passed to the cx_Oracle DBAPI. As
94
+ was the case earlier but not correctly documented, the
95
+ :paramref:`_sa.create_engine.connect_args` parameter also accepts all
96
+ cx_Oracle DBAPI connect arguments.
97
+
98
+ To pass arguments directly to ``.connect()`` without using the query
99
+ string, use the :paramref:`_sa.create_engine.connect_args` dictionary.
100
+ Any cx_Oracle parameter value and/or constant may be passed, such as::
101
+
102
+ import cx_Oracle
103
+ e = create_engine(
104
+ "oracle+cx_oracle://user:pass@dsn",
105
+ connect_args={
106
+ "encoding": "UTF-8",
107
+ "nencoding": "UTF-8",
108
+ "mode": cx_Oracle.SYSDBA,
109
+ "events": True
110
+ }
111
+ )
112
+
113
+ Note that the default value for ``encoding`` and ``nencoding`` was changed to
114
+ "UTF-8" in cx_Oracle 8.0 so these parameters can be omitted when using that
115
+ version, or later.
116
+
117
+ Options consumed by the SQLAlchemy cx_Oracle dialect outside of the driver
118
+ --------------------------------------------------------------------------
119
+
120
+ There are also options that are consumed by the SQLAlchemy cx_oracle dialect
121
+ itself. These options are always passed directly to :func:`_sa.create_engine`
122
+ , such as::
123
+
124
+ e = create_engine(
125
+ "oracle+cx_oracle://user:pass@dsn", coerce_to_decimal=False)
126
+
127
+ The parameters accepted by the cx_oracle dialect are as follows:
128
+
129
+ * ``arraysize`` - set the cx_oracle.arraysize value on cursors; defaults
130
+ to ``None``, indicating that the driver default should be used (typically
131
+ the value is 100). This setting controls how many rows are buffered when
132
+ fetching rows, and can have a significant effect on performance when
133
+ modified. The setting is used for both ``cx_Oracle`` as well as
134
+ ``oracledb``.
135
+
136
+ .. versionchanged:: 2.0.26 - changed the default value from 50 to None,
137
+ to use the default value of the driver itself.
138
+
139
+ * ``auto_convert_lobs`` - defaults to True; See :ref:`cx_oracle_lob`.
140
+
141
+ * ``coerce_to_decimal`` - see :ref:`cx_oracle_numeric` for detail.
142
+
143
+ * ``encoding_errors`` - see :ref:`cx_oracle_unicode_encoding_errors` for detail.
144
+
145
+ .. _cx_oracle_sessionpool:
146
+
147
+ Using cx_Oracle SessionPool
148
+ ---------------------------
149
+
150
+ The cx_Oracle library provides its own connection pool implementation that may
151
+ be used in place of SQLAlchemy's pooling functionality. This can be achieved
152
+ by using the :paramref:`_sa.create_engine.creator` parameter to provide a
153
+ function that returns a new connection, along with setting
154
+ :paramref:`_sa.create_engine.pool_class` to ``NullPool`` to disable
155
+ SQLAlchemy's pooling::
156
+
157
+ import cx_Oracle
158
+ from sqlalchemy import create_engine
159
+ from sqlalchemy.pool import NullPool
160
+
161
+ pool = cx_Oracle.SessionPool(
162
+ user="scott", password="tiger", dsn="orclpdb",
163
+ min=2, max=5, increment=1, threaded=True,
164
+ encoding="UTF-8", nencoding="UTF-8"
165
+ )
166
+
167
+ engine = create_engine("oracle+cx_oracle://", creator=pool.acquire, poolclass=NullPool)
168
+
169
+ The above engine may then be used normally where cx_Oracle's pool handles
170
+ connection pooling::
171
+
172
+ with engine.connect() as conn:
173
+ print(conn.scalar("select 1 FROM dual"))
174
+
175
+
176
+ As well as providing a scalable solution for multi-user applications, the
177
+ cx_Oracle session pool supports some Oracle features such as DRCP and
178
+ `Application Continuity
179
+ <https://cx-oracle.readthedocs.io/en/latest/user_guide/ha.html#application-continuity-ac>`_.
180
+
181
+ Using Oracle Database Resident Connection Pooling (DRCP)
182
+ --------------------------------------------------------
183
+
184
+ When using Oracle's `DRCP
185
+ <https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-015CA8C1-2386-4626-855D-CC546DDC1086>`_,
186
+ the best practice is to pass a connection class and "purity" when acquiring a
187
+ connection from the SessionPool. Refer to the `cx_Oracle DRCP documentation
188
+ <https://cx-oracle.readthedocs.io/en/latest/user_guide/connection_handling.html#database-resident-connection-pooling-drcp>`_.
189
+
190
+ This can be achieved by wrapping ``pool.acquire()``::
191
+
192
+ import cx_Oracle
193
+ from sqlalchemy import create_engine
194
+ from sqlalchemy.pool import NullPool
195
+
196
+ pool = cx_Oracle.SessionPool(
197
+ user="scott", password="tiger", dsn="orclpdb",
198
+ min=2, max=5, increment=1, threaded=True,
199
+ encoding="UTF-8", nencoding="UTF-8"
200
+ )
201
+
202
+ def creator():
203
+ return pool.acquire(cclass="MYCLASS", purity=cx_Oracle.ATTR_PURITY_SELF)
204
+
205
+ engine = create_engine("oracle+cx_oracle://", creator=creator, poolclass=NullPool)
206
+
207
+ The above engine may then be used normally where cx_Oracle handles session
208
+ pooling and Oracle Database additionally uses DRCP::
209
+
210
+ with engine.connect() as conn:
211
+ print(conn.scalar("select 1 FROM dual"))
212
+
213
+ .. _cx_oracle_unicode:
214
+
215
+ Unicode
216
+ -------
217
+
218
+ As is the case for all DBAPIs under Python 3, all strings are inherently
219
+ Unicode strings. In all cases however, the driver requires an explicit
220
+ encoding configuration.
221
+
222
+ Ensuring the Correct Client Encoding
223
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
224
+
225
+ The long accepted standard for establishing client encoding for nearly all
226
+ Oracle related software is via the `NLS_LANG <https://www.oracle.com/database/technologies/faq-nls-lang.html>`_
227
+ environment variable. cx_Oracle like most other Oracle drivers will use
228
+ this environment variable as the source of its encoding configuration. The
229
+ format of this variable is idiosyncratic; a typical value would be
230
+ ``AMERICAN_AMERICA.AL32UTF8``.
231
+
232
+ The cx_Oracle driver also supports a programmatic alternative which is to
233
+ pass the ``encoding`` and ``nencoding`` parameters directly to its
234
+ ``.connect()`` function. These can be present in the URL as follows::
235
+
236
+ engine = create_engine("oracle+cx_oracle://scott:tiger@orclpdb/?encoding=UTF-8&nencoding=UTF-8")
237
+
238
+ For the meaning of the ``encoding`` and ``nencoding`` parameters, please
239
+ consult
240
+ `Characters Sets and National Language Support (NLS) <https://cx-oracle.readthedocs.io/en/latest/user_guide/globalization.html#globalization>`_.
241
+
242
+ .. seealso::
243
+
244
+ `Characters Sets and National Language Support (NLS) <https://cx-oracle.readthedocs.io/en/latest/user_guide/globalization.html#globalization>`_
245
+ - in the cx_Oracle documentation.
246
+
247
+
248
+ Unicode-specific Column datatypes
249
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
250
+
251
+ The Core expression language handles unicode data by use of the :class:`.Unicode`
252
+ and :class:`.UnicodeText`
253
+ datatypes. These types correspond to the VARCHAR2 and CLOB Oracle datatypes by
254
+ default. When using these datatypes with Unicode data, it is expected that
255
+ the Oracle database is configured with a Unicode-aware character set, as well
256
+ as that the ``NLS_LANG`` environment variable is set appropriately, so that
257
+ the VARCHAR2 and CLOB datatypes can accommodate the data.
258
+
259
+ In the case that the Oracle database is not configured with a Unicode character
260
+ set, the two options are to use the :class:`_types.NCHAR` and
261
+ :class:`_oracle.NCLOB` datatypes explicitly, or to pass the flag
262
+ ``use_nchar_for_unicode=True`` to :func:`_sa.create_engine`,
263
+ which will cause the
264
+ SQLAlchemy dialect to use NCHAR/NCLOB for the :class:`.Unicode` /
265
+ :class:`.UnicodeText` datatypes instead of VARCHAR/CLOB.
266
+
267
+ .. versionchanged:: 1.3 The :class:`.Unicode` and :class:`.UnicodeText`
268
+ datatypes now correspond to the ``VARCHAR2`` and ``CLOB`` Oracle datatypes
269
+ unless the ``use_nchar_for_unicode=True`` is passed to the dialect
270
+ when :func:`_sa.create_engine` is called.
271
+
272
+
273
+ .. _cx_oracle_unicode_encoding_errors:
274
+
275
+ Encoding Errors
276
+ ^^^^^^^^^^^^^^^
277
+
278
+ For the unusual case that data in the Oracle database is present with a broken
279
+ encoding, the dialect accepts a parameter ``encoding_errors`` which will be
280
+ passed to Unicode decoding functions in order to affect how decoding errors are
281
+ handled. The value is ultimately consumed by the Python `decode
282
+ <https://docs.python.org/3/library/stdtypes.html#bytes.decode>`_ function, and
283
+ is passed both via cx_Oracle's ``encodingErrors`` parameter consumed by
284
+ ``Cursor.var()``, as well as SQLAlchemy's own decoding function, as the
285
+ cx_Oracle dialect makes use of both under different circumstances.
286
+
287
+ .. versionadded:: 1.3.11
288
+
289
+
290
+ .. _cx_oracle_setinputsizes:
291
+
292
+ Fine grained control over cx_Oracle data binding performance with setinputsizes
293
+ -------------------------------------------------------------------------------
294
+
295
+ The cx_Oracle DBAPI has a deep and fundamental reliance upon the usage of the
296
+ DBAPI ``setinputsizes()`` call. The purpose of this call is to establish the
297
+ datatypes that are bound to a SQL statement for Python values being passed as
298
+ parameters. While virtually no other DBAPI assigns any use to the
299
+ ``setinputsizes()`` call, the cx_Oracle DBAPI relies upon it heavily in its
300
+ interactions with the Oracle client interface, and in some scenarios it is not
301
+ possible for SQLAlchemy to know exactly how data should be bound, as some
302
+ settings can cause profoundly different performance characteristics, while
303
+ altering the type coercion behavior at the same time.
304
+
305
+ Users of the cx_Oracle dialect are **strongly encouraged** to read through
306
+ cx_Oracle's list of built-in datatype symbols at
307
+ https://cx-oracle.readthedocs.io/en/latest/api_manual/module.html#database-types.
308
+ Note that in some cases, significant performance degradation can occur when
309
+ using these types vs. not, in particular when specifying ``cx_Oracle.CLOB``.
310
+
311
+ On the SQLAlchemy side, the :meth:`.DialectEvents.do_setinputsizes` event can
312
+ be used both for runtime visibility (e.g. logging) of the setinputsizes step as
313
+ well as to fully control how ``setinputsizes()`` is used on a per-statement
314
+ basis.
315
+
316
+ .. versionadded:: 1.2.9 Added :meth:`.DialectEvents.setinputsizes`
317
+
318
+
319
+ Example 1 - logging all setinputsizes calls
320
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
321
+
322
+ The following example illustrates how to log the intermediary values from a
323
+ SQLAlchemy perspective before they are converted to the raw ``setinputsizes()``
324
+ parameter dictionary. The keys of the dictionary are :class:`.BindParameter`
325
+ objects which have a ``.key`` and a ``.type`` attribute::
326
+
327
+ from sqlalchemy import create_engine, event
328
+
329
+ engine = create_engine("oracle+cx_oracle://scott:tiger@host/xe")
330
+
331
+ @event.listens_for(engine, "do_setinputsizes")
332
+ def _log_setinputsizes(inputsizes, cursor, statement, parameters, context):
333
+ for bindparam, dbapitype in inputsizes.items():
334
+ log.info(
335
+ "Bound parameter name: %s SQLAlchemy type: %r "
336
+ "DBAPI object: %s",
337
+ bindparam.key, bindparam.type, dbapitype)
338
+
339
+ Example 2 - remove all bindings to CLOB
340
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
341
+
342
+ The ``CLOB`` datatype in cx_Oracle incurs a significant performance overhead,
343
+ however is set by default for the ``Text`` type within the SQLAlchemy 1.2
344
+ series. This setting can be modified as follows::
345
+
346
+ from sqlalchemy import create_engine, event
347
+ from cx_Oracle import CLOB
348
+
349
+ engine = create_engine("oracle+cx_oracle://scott:tiger@host/xe")
350
+
351
+ @event.listens_for(engine, "do_setinputsizes")
352
+ def _remove_clob(inputsizes, cursor, statement, parameters, context):
353
+ for bindparam, dbapitype in list(inputsizes.items()):
354
+ if dbapitype is CLOB:
355
+ del inputsizes[bindparam]
356
+
357
+ .. _cx_oracle_returning:
358
+
359
+ RETURNING Support
360
+ -----------------
361
+
362
+ The cx_Oracle dialect implements RETURNING using OUT parameters.
363
+ The dialect supports RETURNING fully.
364
+
365
+ .. _cx_oracle_lob:
366
+
367
+ LOB Datatypes
368
+ --------------
369
+
370
+ LOB datatypes refer to the "large object" datatypes such as CLOB, NCLOB and
371
+ BLOB. Modern versions of cx_Oracle and oracledb are optimized for these
372
+ datatypes to be delivered as a single buffer. As such, SQLAlchemy makes use of
373
+ these newer type handlers by default.
374
+
375
+ To disable the use of newer type handlers and deliver LOB objects as classic
376
+ buffered objects with a ``read()`` method, the parameter
377
+ ``auto_convert_lobs=False`` may be passed to :func:`_sa.create_engine`,
378
+ which takes place only engine-wide.
379
+
380
+ Two Phase Transactions Not Supported (use oracledb)
381
+ ---------------------------------------------------
382
+
383
+ Two phase transactions are **not supported** under cx_Oracle due to poor driver
384
+ support. The newer :ref:`oracledb` dialect however **does** support two phase
385
+ transactions and should be preferred.
386
+
387
+ .. _cx_oracle_numeric:
388
+
389
+ Precision Numerics
390
+ ------------------
391
+
392
+ SQLAlchemy's numeric types can handle receiving and returning values as Python
393
+ ``Decimal`` objects or float objects. When a :class:`.Numeric` object, or a
394
+ subclass such as :class:`.Float`, :class:`_oracle.DOUBLE_PRECISION` etc. is in
395
+ use, the :paramref:`.Numeric.asdecimal` flag determines if values should be
396
+ coerced to ``Decimal`` upon return, or returned as float objects. To make
397
+ matters more complicated under Oracle, Oracle's ``NUMBER`` type can also
398
+ represent integer values if the "scale" is zero, so the Oracle-specific
399
+ :class:`_oracle.NUMBER` type takes this into account as well.
400
+
401
+ The cx_Oracle dialect makes extensive use of connection- and cursor-level
402
+ "outputtypehandler" callables in order to coerce numeric values as requested.
403
+ These callables are specific to the specific flavor of :class:`.Numeric` in
404
+ use, as well as if no SQLAlchemy typing objects are present. There are
405
+ observed scenarios where Oracle may sends incomplete or ambiguous information
406
+ about the numeric types being returned, such as a query where the numeric types
407
+ are buried under multiple levels of subquery. The type handlers do their best
408
+ to make the right decision in all cases, deferring to the underlying cx_Oracle
409
+ DBAPI for all those cases where the driver can make the best decision.
410
+
411
+ When no typing objects are present, as when executing plain SQL strings, a
412
+ default "outputtypehandler" is present which will generally return numeric
413
+ values which specify precision and scale as Python ``Decimal`` objects. To
414
+ disable this coercion to decimal for performance reasons, pass the flag
415
+ ``coerce_to_decimal=False`` to :func:`_sa.create_engine`::
416
+
417
+ engine = create_engine("oracle+cx_oracle://dsn", coerce_to_decimal=False)
418
+
419
+ The ``coerce_to_decimal`` flag only impacts the results of plain string
420
+ SQL statements that are not otherwise associated with a :class:`.Numeric`
421
+ SQLAlchemy type (or a subclass of such).
422
+
423
+ .. versionchanged:: 1.2 The numeric handling system for cx_Oracle has been
424
+ reworked to take advantage of newer cx_Oracle features as well
425
+ as better integration of outputtypehandlers.
426
+
427
+ """ # noqa
428
+ from __future__ import annotations
429
+
430
+ import decimal
431
+ import random
432
+ import re
433
+
434
+ from . import base as oracle
435
+ from .base import OracleCompiler
436
+ from .base import OracleDialect
437
+ from .base import OracleExecutionContext
438
+ from .types import _OracleDateLiteralRender
439
+ from ... import exc
440
+ from ... import util
441
+ from ...engine import cursor as _cursor
442
+ from ...engine import interfaces
443
+ from ...engine import processors
444
+ from ...sql import sqltypes
445
+ from ...sql._typing import is_sql_compiler
446
+
447
+ # source:
448
+ # https://github.com/oracle/python-cx_Oracle/issues/596#issuecomment-999243649
449
+ _CX_ORACLE_MAGIC_LOB_SIZE = 131072
450
+
451
+
452
+ class _OracleInteger(sqltypes.Integer):
453
+ def get_dbapi_type(self, dbapi):
454
+ # see https://github.com/oracle/python-cx_Oracle/issues/
455
+ # 208#issuecomment-409715955
456
+ return int
457
+
458
+ def _cx_oracle_var(self, dialect, cursor, arraysize=None):
459
+ cx_Oracle = dialect.dbapi
460
+ return cursor.var(
461
+ cx_Oracle.STRING,
462
+ 255,
463
+ arraysize=arraysize if arraysize is not None else cursor.arraysize,
464
+ outconverter=int,
465
+ )
466
+
467
+ def _cx_oracle_outputtypehandler(self, dialect):
468
+ def handler(cursor, name, default_type, size, precision, scale):
469
+ return self._cx_oracle_var(dialect, cursor)
470
+
471
+ return handler
472
+
473
+
474
+ class _OracleNumeric(sqltypes.Numeric):
475
+ is_number = False
476
+
477
+ def bind_processor(self, dialect):
478
+ if self.scale == 0:
479
+ return None
480
+ elif self.asdecimal:
481
+ processor = processors.to_decimal_processor_factory(
482
+ decimal.Decimal, self._effective_decimal_return_scale
483
+ )
484
+
485
+ def process(value):
486
+ if isinstance(value, (int, float)):
487
+ return processor(value)
488
+ elif value is not None and value.is_infinite():
489
+ return float(value)
490
+ else:
491
+ return value
492
+
493
+ return process
494
+ else:
495
+ return processors.to_float
496
+
497
+ def result_processor(self, dialect, coltype):
498
+ return None
499
+
500
+ def _cx_oracle_outputtypehandler(self, dialect):
501
+ cx_Oracle = dialect.dbapi
502
+
503
+ def handler(cursor, name, default_type, size, precision, scale):
504
+ outconverter = None
505
+
506
+ if precision:
507
+ if self.asdecimal:
508
+ if default_type == cx_Oracle.NATIVE_FLOAT:
509
+ # receiving float and doing Decimal after the fact
510
+ # allows for float("inf") to be handled
511
+ type_ = default_type
512
+ outconverter = decimal.Decimal
513
+ else:
514
+ type_ = decimal.Decimal
515
+ else:
516
+ if self.is_number and scale == 0:
517
+ # integer. cx_Oracle is observed to handle the widest
518
+ # variety of ints when no directives are passed,
519
+ # from 5.2 to 7.0. See [ticket:4457]
520
+ return None
521
+ else:
522
+ type_ = cx_Oracle.NATIVE_FLOAT
523
+
524
+ else:
525
+ if self.asdecimal:
526
+ if default_type == cx_Oracle.NATIVE_FLOAT:
527
+ type_ = default_type
528
+ outconverter = decimal.Decimal
529
+ else:
530
+ type_ = decimal.Decimal
531
+ else:
532
+ if self.is_number and scale == 0:
533
+ # integer. cx_Oracle is observed to handle the widest
534
+ # variety of ints when no directives are passed,
535
+ # from 5.2 to 7.0. See [ticket:4457]
536
+ return None
537
+ else:
538
+ type_ = cx_Oracle.NATIVE_FLOAT
539
+
540
+ return cursor.var(
541
+ type_,
542
+ 255,
543
+ arraysize=cursor.arraysize,
544
+ outconverter=outconverter,
545
+ )
546
+
547
+ return handler
548
+
549
+
550
+ class _OracleUUID(sqltypes.Uuid):
551
+ def get_dbapi_type(self, dbapi):
552
+ return dbapi.STRING
553
+
554
+
555
+ class _OracleBinaryFloat(_OracleNumeric):
556
+ def get_dbapi_type(self, dbapi):
557
+ return dbapi.NATIVE_FLOAT
558
+
559
+
560
+ class _OracleBINARY_FLOAT(_OracleBinaryFloat, oracle.BINARY_FLOAT):
561
+ pass
562
+
563
+
564
+ class _OracleBINARY_DOUBLE(_OracleBinaryFloat, oracle.BINARY_DOUBLE):
565
+ pass
566
+
567
+
568
+ class _OracleNUMBER(_OracleNumeric):
569
+ is_number = True
570
+
571
+
572
+ class _CXOracleDate(oracle._OracleDate):
573
+ def bind_processor(self, dialect):
574
+ return None
575
+
576
+ def result_processor(self, dialect, coltype):
577
+ def process(value):
578
+ if value is not None:
579
+ return value.date()
580
+ else:
581
+ return value
582
+
583
+ return process
584
+
585
+
586
+ class _CXOracleTIMESTAMP(_OracleDateLiteralRender, sqltypes.TIMESTAMP):
587
+ def literal_processor(self, dialect):
588
+ return self._literal_processor_datetime(dialect)
589
+
590
+
591
+ class _LOBDataType:
592
+ pass
593
+
594
+
595
+ # TODO: the names used across CHAR / VARCHAR / NCHAR / NVARCHAR
596
+ # here are inconsistent and not very good
597
+ class _OracleChar(sqltypes.CHAR):
598
+ def get_dbapi_type(self, dbapi):
599
+ return dbapi.FIXED_CHAR
600
+
601
+
602
+ class _OracleNChar(sqltypes.NCHAR):
603
+ def get_dbapi_type(self, dbapi):
604
+ return dbapi.FIXED_NCHAR
605
+
606
+
607
+ class _OracleUnicodeStringNCHAR(oracle.NVARCHAR2):
608
+ def get_dbapi_type(self, dbapi):
609
+ return dbapi.NCHAR
610
+
611
+
612
+ class _OracleUnicodeStringCHAR(sqltypes.Unicode):
613
+ def get_dbapi_type(self, dbapi):
614
+ return dbapi.LONG_STRING
615
+
616
+
617
+ class _OracleUnicodeTextNCLOB(_LOBDataType, oracle.NCLOB):
618
+ def get_dbapi_type(self, dbapi):
619
+ # previously, this was dbapi.NCLOB.
620
+ # DB_TYPE_NVARCHAR will instead be passed to setinputsizes()
621
+ # when this datatype is used.
622
+ return dbapi.DB_TYPE_NVARCHAR
623
+
624
+
625
+ class _OracleUnicodeTextCLOB(_LOBDataType, sqltypes.UnicodeText):
626
+ def get_dbapi_type(self, dbapi):
627
+ # previously, this was dbapi.CLOB.
628
+ # DB_TYPE_NVARCHAR will instead be passed to setinputsizes()
629
+ # when this datatype is used.
630
+ return dbapi.DB_TYPE_NVARCHAR
631
+
632
+
633
+ class _OracleText(_LOBDataType, sqltypes.Text):
634
+ def get_dbapi_type(self, dbapi):
635
+ # previously, this was dbapi.CLOB.
636
+ # DB_TYPE_NVARCHAR will instead be passed to setinputsizes()
637
+ # when this datatype is used.
638
+ return dbapi.DB_TYPE_NVARCHAR
639
+
640
+
641
+ class _OracleLong(_LOBDataType, oracle.LONG):
642
+ def get_dbapi_type(self, dbapi):
643
+ return dbapi.LONG_STRING
644
+
645
+
646
+ class _OracleString(sqltypes.String):
647
+ pass
648
+
649
+
650
+ class _OracleEnum(sqltypes.Enum):
651
+ def bind_processor(self, dialect):
652
+ enum_proc = sqltypes.Enum.bind_processor(self, dialect)
653
+
654
+ def process(value):
655
+ raw_str = enum_proc(value)
656
+ return raw_str
657
+
658
+ return process
659
+
660
+
661
+ class _OracleBinary(_LOBDataType, sqltypes.LargeBinary):
662
+ def get_dbapi_type(self, dbapi):
663
+ # previously, this was dbapi.BLOB.
664
+ # DB_TYPE_RAW will instead be passed to setinputsizes()
665
+ # when this datatype is used.
666
+ return dbapi.DB_TYPE_RAW
667
+
668
+ def bind_processor(self, dialect):
669
+ return None
670
+
671
+ def result_processor(self, dialect, coltype):
672
+ if not dialect.auto_convert_lobs:
673
+ return None
674
+ else:
675
+ return super().result_processor(dialect, coltype)
676
+
677
+
678
+ class _OracleInterval(oracle.INTERVAL):
679
+ def get_dbapi_type(self, dbapi):
680
+ return dbapi.INTERVAL
681
+
682
+
683
+ class _OracleRaw(oracle.RAW):
684
+ pass
685
+
686
+
687
+ class _OracleRowid(oracle.ROWID):
688
+ def get_dbapi_type(self, dbapi):
689
+ return dbapi.ROWID
690
+
691
+
692
+ class OracleCompiler_cx_oracle(OracleCompiler):
693
+ _oracle_cx_sql_compiler = True
694
+
695
+ _oracle_returning = False
696
+
697
+ # Oracle bind names can't start with digits or underscores.
698
+ # currently we rely upon Oracle-specific quoting of bind names in most
699
+ # cases. however for expanding params, the escape chars are used.
700
+ # see #8708
701
+ bindname_escape_characters = util.immutabledict(
702
+ {
703
+ "%": "P",
704
+ "(": "A",
705
+ ")": "Z",
706
+ ":": "C",
707
+ ".": "C",
708
+ "[": "C",
709
+ "]": "C",
710
+ " ": "C",
711
+ "\\": "C",
712
+ "/": "C",
713
+ "?": "C",
714
+ }
715
+ )
716
+
717
+ def bindparam_string(self, name, **kw):
718
+ quote = getattr(name, "quote", None)
719
+ if (
720
+ quote is True
721
+ or quote is not False
722
+ and self.preparer._bindparam_requires_quotes(name)
723
+ # bind param quoting for Oracle doesn't work with post_compile
724
+ # params. For those, the default bindparam_string will escape
725
+ # special chars, and the appending of a number "_1" etc. will
726
+ # take care of reserved words
727
+ and not kw.get("post_compile", False)
728
+ ):
729
+ # interesting to note about expanding parameters - since the
730
+ # new parameters take the form <paramname>_<int>, at least if
731
+ # they are originally formed from reserved words, they no longer
732
+ # need quoting :). names that include illegal characters
733
+ # won't work however.
734
+ quoted_name = '"%s"' % name
735
+ kw["escaped_from"] = name
736
+ name = quoted_name
737
+ return OracleCompiler.bindparam_string(self, name, **kw)
738
+
739
+ # TODO: we could likely do away with quoting altogether for
740
+ # Oracle parameters and use the custom escaping here
741
+ escaped_from = kw.get("escaped_from", None)
742
+ if not escaped_from:
743
+ if self._bind_translate_re.search(name):
744
+ # not quite the translate use case as we want to
745
+ # also get a quick boolean if we even found
746
+ # unusual characters in the name
747
+ new_name = self._bind_translate_re.sub(
748
+ lambda m: self._bind_translate_chars[m.group(0)],
749
+ name,
750
+ )
751
+ if new_name[0].isdigit() or new_name[0] == "_":
752
+ new_name = "D" + new_name
753
+ kw["escaped_from"] = name
754
+ name = new_name
755
+ elif name[0].isdigit() or name[0] == "_":
756
+ new_name = "D" + name
757
+ kw["escaped_from"] = name
758
+ name = new_name
759
+
760
+ return OracleCompiler.bindparam_string(self, name, **kw)
761
+
762
+
763
+ class OracleExecutionContext_cx_oracle(OracleExecutionContext):
764
+ out_parameters = None
765
+
766
+ def _generate_out_parameter_vars(self):
767
+ # check for has_out_parameters or RETURNING, create cx_Oracle.var
768
+ # objects if so
769
+ if self.compiled.has_out_parameters or self.compiled._oracle_returning:
770
+ out_parameters = self.out_parameters
771
+ assert out_parameters is not None
772
+
773
+ len_params = len(self.parameters)
774
+
775
+ quoted_bind_names = self.compiled.escaped_bind_names
776
+ for bindparam in self.compiled.binds.values():
777
+ if bindparam.isoutparam:
778
+ name = self.compiled.bind_names[bindparam]
779
+ type_impl = bindparam.type.dialect_impl(self.dialect)
780
+
781
+ if hasattr(type_impl, "_cx_oracle_var"):
782
+ out_parameters[name] = type_impl._cx_oracle_var(
783
+ self.dialect, self.cursor, arraysize=len_params
784
+ )
785
+ else:
786
+ dbtype = type_impl.get_dbapi_type(self.dialect.dbapi)
787
+
788
+ cx_Oracle = self.dialect.dbapi
789
+
790
+ assert cx_Oracle is not None
791
+
792
+ if dbtype is None:
793
+ raise exc.InvalidRequestError(
794
+ "Cannot create out parameter for "
795
+ "parameter "
796
+ "%r - its type %r is not supported by"
797
+ " cx_oracle" % (bindparam.key, bindparam.type)
798
+ )
799
+
800
+ # note this is an OUT parameter. Using
801
+ # non-LOB datavalues with large unicode-holding
802
+ # values causes the failure (both cx_Oracle and
803
+ # oracledb):
804
+ # ORA-22835: Buffer too small for CLOB to CHAR or
805
+ # BLOB to RAW conversion (actual: 16507,
806
+ # maximum: 4000)
807
+ # [SQL: INSERT INTO long_text (x, y, z) VALUES
808
+ # (:x, :y, :z) RETURNING long_text.x, long_text.y,
809
+ # long_text.z INTO :ret_0, :ret_1, :ret_2]
810
+ # so even for DB_TYPE_NVARCHAR we convert to a LOB
811
+
812
+ if isinstance(type_impl, _LOBDataType):
813
+ if dbtype == cx_Oracle.DB_TYPE_NVARCHAR:
814
+ dbtype = cx_Oracle.NCLOB
815
+ elif dbtype == cx_Oracle.DB_TYPE_RAW:
816
+ dbtype = cx_Oracle.BLOB
817
+ # other LOB types go in directly
818
+
819
+ out_parameters[name] = self.cursor.var(
820
+ dbtype,
821
+ # this is fine also in oracledb_async since
822
+ # the driver will await the read coroutine
823
+ outconverter=lambda value: value.read(),
824
+ arraysize=len_params,
825
+ )
826
+ elif (
827
+ isinstance(type_impl, _OracleNumeric)
828
+ and type_impl.asdecimal
829
+ ):
830
+ out_parameters[name] = self.cursor.var(
831
+ decimal.Decimal,
832
+ arraysize=len_params,
833
+ )
834
+
835
+ else:
836
+ out_parameters[name] = self.cursor.var(
837
+ dbtype, arraysize=len_params
838
+ )
839
+
840
+ for param in self.parameters:
841
+ param[quoted_bind_names.get(name, name)] = (
842
+ out_parameters[name]
843
+ )
844
+
845
+ def _generate_cursor_outputtype_handler(self):
846
+ output_handlers = {}
847
+
848
+ for keyname, name, objects, type_ in self.compiled._result_columns:
849
+ handler = type_._cached_custom_processor(
850
+ self.dialect,
851
+ "cx_oracle_outputtypehandler",
852
+ self._get_cx_oracle_type_handler,
853
+ )
854
+
855
+ if handler:
856
+ denormalized_name = self.dialect.denormalize_name(keyname)
857
+ output_handlers[denormalized_name] = handler
858
+
859
+ if output_handlers:
860
+ default_handler = self._dbapi_connection.outputtypehandler
861
+
862
+ def output_type_handler(
863
+ cursor, name, default_type, size, precision, scale
864
+ ):
865
+ if name in output_handlers:
866
+ return output_handlers[name](
867
+ cursor, name, default_type, size, precision, scale
868
+ )
869
+ else:
870
+ return default_handler(
871
+ cursor, name, default_type, size, precision, scale
872
+ )
873
+
874
+ self.cursor.outputtypehandler = output_type_handler
875
+
876
+ def _get_cx_oracle_type_handler(self, impl):
877
+ if hasattr(impl, "_cx_oracle_outputtypehandler"):
878
+ return impl._cx_oracle_outputtypehandler(self.dialect)
879
+ else:
880
+ return None
881
+
882
+ def pre_exec(self):
883
+ super().pre_exec()
884
+ if not getattr(self.compiled, "_oracle_cx_sql_compiler", False):
885
+ return
886
+
887
+ self.out_parameters = {}
888
+
889
+ self._generate_out_parameter_vars()
890
+
891
+ self._generate_cursor_outputtype_handler()
892
+
893
+ def post_exec(self):
894
+ if (
895
+ self.compiled
896
+ and is_sql_compiler(self.compiled)
897
+ and self.compiled._oracle_returning
898
+ ):
899
+ initial_buffer = self.fetchall_for_returning(
900
+ self.cursor, _internal=True
901
+ )
902
+
903
+ fetch_strategy = _cursor.FullyBufferedCursorFetchStrategy(
904
+ self.cursor,
905
+ [
906
+ (entry.keyname, None)
907
+ for entry in self.compiled._result_columns
908
+ ],
909
+ initial_buffer=initial_buffer,
910
+ )
911
+
912
+ self.cursor_fetch_strategy = fetch_strategy
913
+
914
+ def create_cursor(self):
915
+ c = self._dbapi_connection.cursor()
916
+ if self.dialect.arraysize:
917
+ c.arraysize = self.dialect.arraysize
918
+
919
+ return c
920
+
921
+ def fetchall_for_returning(self, cursor, *, _internal=False):
922
+ compiled = self.compiled
923
+ if (
924
+ not _internal
925
+ and compiled is None
926
+ or not is_sql_compiler(compiled)
927
+ or not compiled._oracle_returning
928
+ ):
929
+ raise NotImplementedError(
930
+ "execution context was not prepared for Oracle RETURNING"
931
+ )
932
+
933
+ # create a fake cursor result from the out parameters. unlike
934
+ # get_out_parameter_values(), the result-row handlers here will be
935
+ # applied at the Result level
936
+
937
+ numcols = len(self.out_parameters)
938
+
939
+ # [stmt_result for stmt_result in outparam.values] == each
940
+ # statement in executemany
941
+ # [val for val in stmt_result] == each row for a particular
942
+ # statement
943
+ return list(
944
+ zip(
945
+ *[
946
+ [
947
+ val
948
+ for stmt_result in self.out_parameters[
949
+ f"ret_{j}"
950
+ ].values
951
+ for val in (stmt_result or ())
952
+ ]
953
+ for j in range(numcols)
954
+ ]
955
+ )
956
+ )
957
+
958
+ def get_out_parameter_values(self, out_param_names):
959
+ # this method should not be called when the compiler has
960
+ # RETURNING as we've turned the has_out_parameters flag set to
961
+ # False.
962
+ assert not self.compiled.returning
963
+
964
+ return [
965
+ self.dialect._paramval(self.out_parameters[name])
966
+ for name in out_param_names
967
+ ]
968
+
969
+
970
+ class OracleDialect_cx_oracle(OracleDialect):
971
+ supports_statement_cache = True
972
+ execution_ctx_cls = OracleExecutionContext_cx_oracle
973
+ statement_compiler = OracleCompiler_cx_oracle
974
+
975
+ supports_sane_rowcount = True
976
+ supports_sane_multi_rowcount = True
977
+
978
+ insert_executemany_returning = True
979
+ insert_executemany_returning_sort_by_parameter_order = True
980
+ update_executemany_returning = True
981
+ delete_executemany_returning = True
982
+
983
+ bind_typing = interfaces.BindTyping.SETINPUTSIZES
984
+
985
+ driver = "cx_oracle"
986
+
987
+ colspecs = util.update_copy(
988
+ OracleDialect.colspecs,
989
+ {
990
+ sqltypes.TIMESTAMP: _CXOracleTIMESTAMP,
991
+ sqltypes.Numeric: _OracleNumeric,
992
+ sqltypes.Float: _OracleNumeric,
993
+ oracle.BINARY_FLOAT: _OracleBINARY_FLOAT,
994
+ oracle.BINARY_DOUBLE: _OracleBINARY_DOUBLE,
995
+ sqltypes.Integer: _OracleInteger,
996
+ oracle.NUMBER: _OracleNUMBER,
997
+ sqltypes.Date: _CXOracleDate,
998
+ sqltypes.LargeBinary: _OracleBinary,
999
+ sqltypes.Boolean: oracle._OracleBoolean,
1000
+ sqltypes.Interval: _OracleInterval,
1001
+ oracle.INTERVAL: _OracleInterval,
1002
+ sqltypes.Text: _OracleText,
1003
+ sqltypes.String: _OracleString,
1004
+ sqltypes.UnicodeText: _OracleUnicodeTextCLOB,
1005
+ sqltypes.CHAR: _OracleChar,
1006
+ sqltypes.NCHAR: _OracleNChar,
1007
+ sqltypes.Enum: _OracleEnum,
1008
+ oracle.LONG: _OracleLong,
1009
+ oracle.RAW: _OracleRaw,
1010
+ sqltypes.Unicode: _OracleUnicodeStringCHAR,
1011
+ sqltypes.NVARCHAR: _OracleUnicodeStringNCHAR,
1012
+ sqltypes.Uuid: _OracleUUID,
1013
+ oracle.NCLOB: _OracleUnicodeTextNCLOB,
1014
+ oracle.ROWID: _OracleRowid,
1015
+ },
1016
+ )
1017
+
1018
+ execute_sequence_format = list
1019
+
1020
+ _cx_oracle_threaded = None
1021
+
1022
+ _cursor_var_unicode_kwargs = util.immutabledict()
1023
+
1024
+ @util.deprecated_params(
1025
+ threaded=(
1026
+ "1.3",
1027
+ "The 'threaded' parameter to the cx_oracle/oracledb dialect "
1028
+ "is deprecated as a dialect-level argument, and will be removed "
1029
+ "in a future release. As of version 1.3, it defaults to False "
1030
+ "rather than True. The 'threaded' option can be passed to "
1031
+ "cx_Oracle directly in the URL query string passed to "
1032
+ ":func:`_sa.create_engine`.",
1033
+ )
1034
+ )
1035
+ def __init__(
1036
+ self,
1037
+ auto_convert_lobs=True,
1038
+ coerce_to_decimal=True,
1039
+ arraysize=None,
1040
+ encoding_errors=None,
1041
+ threaded=None,
1042
+ **kwargs,
1043
+ ):
1044
+ OracleDialect.__init__(self, **kwargs)
1045
+ self.arraysize = arraysize
1046
+ self.encoding_errors = encoding_errors
1047
+ if encoding_errors:
1048
+ self._cursor_var_unicode_kwargs = {
1049
+ "encodingErrors": encoding_errors
1050
+ }
1051
+ if threaded is not None:
1052
+ self._cx_oracle_threaded = threaded
1053
+ self.auto_convert_lobs = auto_convert_lobs
1054
+ self.coerce_to_decimal = coerce_to_decimal
1055
+ if self._use_nchar_for_unicode:
1056
+ self.colspecs = self.colspecs.copy()
1057
+ self.colspecs[sqltypes.Unicode] = _OracleUnicodeStringNCHAR
1058
+ self.colspecs[sqltypes.UnicodeText] = _OracleUnicodeTextNCLOB
1059
+
1060
+ dbapi_module = self.dbapi
1061
+ self._load_version(dbapi_module)
1062
+
1063
+ if dbapi_module is not None:
1064
+ # these constants will first be seen in SQLAlchemy datatypes
1065
+ # coming from the get_dbapi_type() method. We then
1066
+ # will place the following types into setinputsizes() calls
1067
+ # on each statement. Oracle constants that are not in this
1068
+ # list will not be put into setinputsizes().
1069
+ self.include_set_input_sizes = {
1070
+ dbapi_module.DATETIME,
1071
+ dbapi_module.DB_TYPE_NVARCHAR, # used for CLOB, NCLOB
1072
+ dbapi_module.DB_TYPE_RAW, # used for BLOB
1073
+ dbapi_module.NCLOB, # not currently used except for OUT param
1074
+ dbapi_module.CLOB, # not currently used except for OUT param
1075
+ dbapi_module.LOB, # not currently used
1076
+ dbapi_module.BLOB, # not currently used except for OUT param
1077
+ dbapi_module.NCHAR,
1078
+ dbapi_module.FIXED_NCHAR,
1079
+ dbapi_module.FIXED_CHAR,
1080
+ dbapi_module.TIMESTAMP,
1081
+ int, # _OracleInteger,
1082
+ # _OracleBINARY_FLOAT, _OracleBINARY_DOUBLE,
1083
+ dbapi_module.NATIVE_FLOAT,
1084
+ }
1085
+
1086
+ self._paramval = lambda value: value.getvalue()
1087
+
1088
+ def _load_version(self, dbapi_module):
1089
+ version = (0, 0, 0)
1090
+ if dbapi_module is not None:
1091
+ m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", dbapi_module.version)
1092
+ if m:
1093
+ version = tuple(
1094
+ int(x) for x in m.group(1, 2, 3) if x is not None
1095
+ )
1096
+ self.cx_oracle_ver = version
1097
+ if self.cx_oracle_ver < (8,) and self.cx_oracle_ver > (0, 0, 0):
1098
+ raise exc.InvalidRequestError(
1099
+ "cx_Oracle version 8 and above are supported"
1100
+ )
1101
+
1102
+ @classmethod
1103
+ def import_dbapi(cls):
1104
+ import cx_Oracle
1105
+
1106
+ return cx_Oracle
1107
+
1108
+ def initialize(self, connection):
1109
+ super().initialize(connection)
1110
+ self._detect_decimal_char(connection)
1111
+
1112
+ def get_isolation_level(self, dbapi_connection):
1113
+ # sources:
1114
+
1115
+ # general idea of transaction id, have to start one, etc.
1116
+ # https://stackoverflow.com/questions/10711204/how-to-check-isoloation-level
1117
+
1118
+ # how to decode xid cols from v$transaction to match
1119
+ # https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:9532779900346079444
1120
+
1121
+ # Oracle tuple comparison without using IN:
1122
+ # https://www.sql-workbench.eu/comparison/tuple_comparison.html
1123
+
1124
+ with dbapi_connection.cursor() as cursor:
1125
+ # this is the only way to ensure a transaction is started without
1126
+ # actually running DML. There's no way to see the configured
1127
+ # isolation level without getting it from v$transaction which
1128
+ # means transaction has to be started.
1129
+ outval = cursor.var(str)
1130
+ cursor.execute(
1131
+ """
1132
+ begin
1133
+ :trans_id := dbms_transaction.local_transaction_id( TRUE );
1134
+ end;
1135
+ """,
1136
+ {"trans_id": outval},
1137
+ )
1138
+ trans_id = outval.getvalue()
1139
+ xidusn, xidslot, xidsqn = trans_id.split(".", 2)
1140
+
1141
+ cursor.execute(
1142
+ "SELECT CASE BITAND(t.flag, POWER(2, 28)) "
1143
+ "WHEN 0 THEN 'READ COMMITTED' "
1144
+ "ELSE 'SERIALIZABLE' END AS isolation_level "
1145
+ "FROM v$transaction t WHERE "
1146
+ "(t.xidusn, t.xidslot, t.xidsqn) = "
1147
+ "((:xidusn, :xidslot, :xidsqn))",
1148
+ {"xidusn": xidusn, "xidslot": xidslot, "xidsqn": xidsqn},
1149
+ )
1150
+ row = cursor.fetchone()
1151
+ if row is None:
1152
+ raise exc.InvalidRequestError(
1153
+ "could not retrieve isolation level"
1154
+ )
1155
+ result = row[0]
1156
+
1157
+ return result
1158
+
1159
+ def get_isolation_level_values(self, dbapi_connection):
1160
+ return super().get_isolation_level_values(dbapi_connection) + [
1161
+ "AUTOCOMMIT"
1162
+ ]
1163
+
1164
+ def set_isolation_level(self, dbapi_connection, level):
1165
+ if level == "AUTOCOMMIT":
1166
+ dbapi_connection.autocommit = True
1167
+ else:
1168
+ dbapi_connection.autocommit = False
1169
+ dbapi_connection.rollback()
1170
+ with dbapi_connection.cursor() as cursor:
1171
+ cursor.execute(f"ALTER SESSION SET ISOLATION_LEVEL={level}")
1172
+
1173
+ def _detect_decimal_char(self, connection):
1174
+ # we have the option to change this setting upon connect,
1175
+ # or just look at what it is upon connect and convert.
1176
+ # to minimize the chance of interference with changes to
1177
+ # NLS_TERRITORY or formatting behavior of the DB, we opt
1178
+ # to just look at it
1179
+
1180
+ dbapi_connection = connection.connection
1181
+
1182
+ with dbapi_connection.cursor() as cursor:
1183
+ # issue #8744
1184
+ # nls_session_parameters is not available in some Oracle
1185
+ # modes like "mount mode". But then, v$nls_parameters is not
1186
+ # available if the connection doesn't have SYSDBA priv.
1187
+ #
1188
+ # simplify the whole thing and just use the method that we were
1189
+ # doing in the test suite already, selecting a number
1190
+
1191
+ def output_type_handler(
1192
+ cursor, name, defaultType, size, precision, scale
1193
+ ):
1194
+ return cursor.var(
1195
+ self.dbapi.STRING, 255, arraysize=cursor.arraysize
1196
+ )
1197
+
1198
+ cursor.outputtypehandler = output_type_handler
1199
+ cursor.execute("SELECT 1.1 FROM DUAL")
1200
+ value = cursor.fetchone()[0]
1201
+
1202
+ decimal_char = value.lstrip("0")[1]
1203
+ assert not decimal_char[0].isdigit()
1204
+
1205
+ self._decimal_char = decimal_char
1206
+
1207
+ if self._decimal_char != ".":
1208
+ _detect_decimal = self._detect_decimal
1209
+ _to_decimal = self._to_decimal
1210
+
1211
+ self._detect_decimal = lambda value: _detect_decimal(
1212
+ value.replace(self._decimal_char, ".")
1213
+ )
1214
+ self._to_decimal = lambda value: _to_decimal(
1215
+ value.replace(self._decimal_char, ".")
1216
+ )
1217
+
1218
+ def _detect_decimal(self, value):
1219
+ if "." in value:
1220
+ return self._to_decimal(value)
1221
+ else:
1222
+ return int(value)
1223
+
1224
+ _to_decimal = decimal.Decimal
1225
+
1226
+ def _generate_connection_outputtype_handler(self):
1227
+ """establish the default outputtypehandler established at the
1228
+ connection level.
1229
+
1230
+ """
1231
+
1232
+ dialect = self
1233
+ cx_Oracle = dialect.dbapi
1234
+
1235
+ number_handler = _OracleNUMBER(
1236
+ asdecimal=True
1237
+ )._cx_oracle_outputtypehandler(dialect)
1238
+ float_handler = _OracleNUMBER(
1239
+ asdecimal=False
1240
+ )._cx_oracle_outputtypehandler(dialect)
1241
+
1242
+ def output_type_handler(
1243
+ cursor, name, default_type, size, precision, scale
1244
+ ):
1245
+ if (
1246
+ default_type == cx_Oracle.NUMBER
1247
+ and default_type is not cx_Oracle.NATIVE_FLOAT
1248
+ ):
1249
+ if not dialect.coerce_to_decimal:
1250
+ return None
1251
+ elif precision == 0 and scale in (0, -127):
1252
+ # ambiguous type, this occurs when selecting
1253
+ # numbers from deep subqueries
1254
+ return cursor.var(
1255
+ cx_Oracle.STRING,
1256
+ 255,
1257
+ outconverter=dialect._detect_decimal,
1258
+ arraysize=cursor.arraysize,
1259
+ )
1260
+ elif precision and scale > 0:
1261
+ return number_handler(
1262
+ cursor, name, default_type, size, precision, scale
1263
+ )
1264
+ else:
1265
+ return float_handler(
1266
+ cursor, name, default_type, size, precision, scale
1267
+ )
1268
+
1269
+ # if unicode options were specified, add a decoder, otherwise
1270
+ # cx_Oracle should return Unicode
1271
+ elif (
1272
+ dialect._cursor_var_unicode_kwargs
1273
+ and default_type
1274
+ in (
1275
+ cx_Oracle.STRING,
1276
+ cx_Oracle.FIXED_CHAR,
1277
+ )
1278
+ and default_type is not cx_Oracle.CLOB
1279
+ and default_type is not cx_Oracle.NCLOB
1280
+ ):
1281
+ return cursor.var(
1282
+ str,
1283
+ size,
1284
+ cursor.arraysize,
1285
+ **dialect._cursor_var_unicode_kwargs,
1286
+ )
1287
+
1288
+ elif dialect.auto_convert_lobs and default_type in (
1289
+ cx_Oracle.CLOB,
1290
+ cx_Oracle.NCLOB,
1291
+ ):
1292
+ return cursor.var(
1293
+ cx_Oracle.DB_TYPE_NVARCHAR,
1294
+ _CX_ORACLE_MAGIC_LOB_SIZE,
1295
+ cursor.arraysize,
1296
+ **dialect._cursor_var_unicode_kwargs,
1297
+ )
1298
+
1299
+ elif dialect.auto_convert_lobs and default_type in (
1300
+ cx_Oracle.BLOB,
1301
+ ):
1302
+ return cursor.var(
1303
+ cx_Oracle.DB_TYPE_RAW,
1304
+ _CX_ORACLE_MAGIC_LOB_SIZE,
1305
+ cursor.arraysize,
1306
+ )
1307
+
1308
+ return output_type_handler
1309
+
1310
+ def on_connect(self):
1311
+ output_type_handler = self._generate_connection_outputtype_handler()
1312
+
1313
+ def on_connect(conn):
1314
+ conn.outputtypehandler = output_type_handler
1315
+
1316
+ return on_connect
1317
+
1318
+ def create_connect_args(self, url):
1319
+ opts = dict(url.query)
1320
+
1321
+ for opt in ("use_ansi", "auto_convert_lobs"):
1322
+ if opt in opts:
1323
+ util.warn_deprecated(
1324
+ f"{self.driver} dialect option {opt!r} should only be "
1325
+ "passed to create_engine directly, not within the URL "
1326
+ "string",
1327
+ version="1.3",
1328
+ )
1329
+ util.coerce_kw_type(opts, opt, bool)
1330
+ setattr(self, opt, opts.pop(opt))
1331
+
1332
+ database = url.database
1333
+ service_name = opts.pop("service_name", None)
1334
+ if database or service_name:
1335
+ # if we have a database, then we have a remote host
1336
+ port = url.port
1337
+ if port:
1338
+ port = int(port)
1339
+ else:
1340
+ port = 1521
1341
+
1342
+ if database and service_name:
1343
+ raise exc.InvalidRequestError(
1344
+ '"service_name" option shouldn\'t '
1345
+ 'be used with a "database" part of the url'
1346
+ )
1347
+ if database:
1348
+ makedsn_kwargs = {"sid": database}
1349
+ if service_name:
1350
+ makedsn_kwargs = {"service_name": service_name}
1351
+
1352
+ dsn = self.dbapi.makedsn(url.host, port, **makedsn_kwargs)
1353
+ else:
1354
+ # we have a local tnsname
1355
+ dsn = url.host
1356
+
1357
+ if dsn is not None:
1358
+ opts["dsn"] = dsn
1359
+ if url.password is not None:
1360
+ opts["password"] = url.password
1361
+ if url.username is not None:
1362
+ opts["user"] = url.username
1363
+
1364
+ if self._cx_oracle_threaded is not None:
1365
+ opts.setdefault("threaded", self._cx_oracle_threaded)
1366
+
1367
+ def convert_cx_oracle_constant(value):
1368
+ if isinstance(value, str):
1369
+ try:
1370
+ int_val = int(value)
1371
+ except ValueError:
1372
+ value = value.upper()
1373
+ return getattr(self.dbapi, value)
1374
+ else:
1375
+ return int_val
1376
+ else:
1377
+ return value
1378
+
1379
+ util.coerce_kw_type(opts, "mode", convert_cx_oracle_constant)
1380
+ util.coerce_kw_type(opts, "threaded", bool)
1381
+ util.coerce_kw_type(opts, "events", bool)
1382
+ util.coerce_kw_type(opts, "purity", convert_cx_oracle_constant)
1383
+ return ([], opts)
1384
+
1385
+ def _get_server_version_info(self, connection):
1386
+ return tuple(int(x) for x in connection.connection.version.split("."))
1387
+
1388
+ def is_disconnect(self, e, connection, cursor):
1389
+ (error,) = e.args
1390
+ if isinstance(
1391
+ e, (self.dbapi.InterfaceError, self.dbapi.DatabaseError)
1392
+ ) and "not connected" in str(e):
1393
+ return True
1394
+
1395
+ if hasattr(error, "code") and error.code in {
1396
+ 28,
1397
+ 3114,
1398
+ 3113,
1399
+ 3135,
1400
+ 1033,
1401
+ 2396,
1402
+ }:
1403
+ # ORA-00028: your session has been killed
1404
+ # ORA-03114: not connected to ORACLE
1405
+ # ORA-03113: end-of-file on communication channel
1406
+ # ORA-03135: connection lost contact
1407
+ # ORA-01033: ORACLE initialization or shutdown in progress
1408
+ # ORA-02396: exceeded maximum idle time, please connect again
1409
+ # TODO: Others ?
1410
+ return True
1411
+
1412
+ if re.match(r"^(?:DPI-1010|DPI-1080|DPY-1001|DPY-4011)", str(e)):
1413
+ # DPI-1010: not connected
1414
+ # DPI-1080: connection was closed by ORA-3113
1415
+ # python-oracledb's DPY-1001: not connected to database
1416
+ # python-oracledb's DPY-4011: the database or network closed the
1417
+ # connection
1418
+ # TODO: others?
1419
+ return True
1420
+
1421
+ return False
1422
+
1423
+ def create_xid(self):
1424
+ id_ = random.randint(0, 2**128)
1425
+ return (0x1234, "%032x" % id_, "%032x" % 9)
1426
+
1427
+ def do_executemany(self, cursor, statement, parameters, context=None):
1428
+ if isinstance(parameters, tuple):
1429
+ parameters = list(parameters)
1430
+ cursor.executemany(statement, parameters)
1431
+
1432
+ def do_begin_twophase(self, connection, xid):
1433
+ connection.connection.begin(*xid)
1434
+ connection.connection.info["cx_oracle_xid"] = xid
1435
+
1436
+ def do_prepare_twophase(self, connection, xid):
1437
+ result = connection.connection.prepare()
1438
+ connection.info["cx_oracle_prepared"] = result
1439
+
1440
+ def do_rollback_twophase(
1441
+ self, connection, xid, is_prepared=True, recover=False
1442
+ ):
1443
+ self.do_rollback(connection.connection)
1444
+ # TODO: need to end XA state here
1445
+
1446
+ def do_commit_twophase(
1447
+ self, connection, xid, is_prepared=True, recover=False
1448
+ ):
1449
+ if not is_prepared:
1450
+ self.do_commit(connection.connection)
1451
+ else:
1452
+ if recover:
1453
+ raise NotImplementedError(
1454
+ "2pc recovery not implemented for cx_Oracle"
1455
+ )
1456
+ oci_prepared = connection.info["cx_oracle_prepared"]
1457
+ if oci_prepared:
1458
+ self.do_commit(connection.connection)
1459
+ # TODO: need to end XA state here
1460
+
1461
+ def do_set_input_sizes(self, cursor, list_of_tuples, context):
1462
+ if self.positional:
1463
+ # not usually used, here to support if someone is modifying
1464
+ # the dialect to use positional style
1465
+ cursor.setinputsizes(
1466
+ *[dbtype for key, dbtype, sqltype in list_of_tuples]
1467
+ )
1468
+ else:
1469
+ collection = (
1470
+ (key, dbtype)
1471
+ for key, dbtype, sqltype in list_of_tuples
1472
+ if dbtype
1473
+ )
1474
+
1475
+ cursor.setinputsizes(**{key: dbtype for key, dbtype in collection})
1476
+
1477
+ def do_recover_twophase(self, connection):
1478
+ raise NotImplementedError(
1479
+ "recover two phase query for cx_Oracle not implemented"
1480
+ )
1481
+
1482
+
1483
+ dialect = OracleDialect_cx_oracle