SQLAlchemy 2.0.47__cp313-cp313t-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 (274) hide show
  1. sqlalchemy/__init__.py +283 -0
  2. sqlalchemy/connectors/__init__.py +18 -0
  3. sqlalchemy/connectors/aioodbc.py +184 -0
  4. sqlalchemy/connectors/asyncio.py +429 -0
  5. sqlalchemy/connectors/pyodbc.py +250 -0
  6. sqlalchemy/cyextension/__init__.py +6 -0
  7. sqlalchemy/cyextension/collections.cp313t-win32.pyd +0 -0
  8. sqlalchemy/cyextension/collections.pyx +409 -0
  9. sqlalchemy/cyextension/immutabledict.cp313t-win32.pyd +0 -0
  10. sqlalchemy/cyextension/immutabledict.pxd +8 -0
  11. sqlalchemy/cyextension/immutabledict.pyx +133 -0
  12. sqlalchemy/cyextension/processors.cp313t-win32.pyd +0 -0
  13. sqlalchemy/cyextension/processors.pyx +68 -0
  14. sqlalchemy/cyextension/resultproxy.cp313t-win32.pyd +0 -0
  15. sqlalchemy/cyextension/resultproxy.pyx +102 -0
  16. sqlalchemy/cyextension/util.cp313t-win32.pyd +0 -0
  17. sqlalchemy/cyextension/util.pyx +90 -0
  18. sqlalchemy/dialects/__init__.py +62 -0
  19. sqlalchemy/dialects/_typing.py +30 -0
  20. sqlalchemy/dialects/mssql/__init__.py +88 -0
  21. sqlalchemy/dialects/mssql/aioodbc.py +63 -0
  22. sqlalchemy/dialects/mssql/base.py +4093 -0
  23. sqlalchemy/dialects/mssql/information_schema.py +285 -0
  24. sqlalchemy/dialects/mssql/json.py +129 -0
  25. sqlalchemy/dialects/mssql/provision.py +185 -0
  26. sqlalchemy/dialects/mssql/pymssql.py +126 -0
  27. sqlalchemy/dialects/mssql/pyodbc.py +760 -0
  28. sqlalchemy/dialects/mysql/__init__.py +104 -0
  29. sqlalchemy/dialects/mysql/aiomysql.py +250 -0
  30. sqlalchemy/dialects/mysql/asyncmy.py +231 -0
  31. sqlalchemy/dialects/mysql/base.py +3949 -0
  32. sqlalchemy/dialects/mysql/cymysql.py +106 -0
  33. sqlalchemy/dialects/mysql/dml.py +225 -0
  34. sqlalchemy/dialects/mysql/enumerated.py +282 -0
  35. sqlalchemy/dialects/mysql/expression.py +146 -0
  36. sqlalchemy/dialects/mysql/json.py +91 -0
  37. sqlalchemy/dialects/mysql/mariadb.py +72 -0
  38. sqlalchemy/dialects/mysql/mariadbconnector.py +322 -0
  39. sqlalchemy/dialects/mysql/mysqlconnector.py +302 -0
  40. sqlalchemy/dialects/mysql/mysqldb.py +314 -0
  41. sqlalchemy/dialects/mysql/provision.py +153 -0
  42. sqlalchemy/dialects/mysql/pymysql.py +158 -0
  43. sqlalchemy/dialects/mysql/pyodbc.py +157 -0
  44. sqlalchemy/dialects/mysql/reflection.py +727 -0
  45. sqlalchemy/dialects/mysql/reserved_words.py +570 -0
  46. sqlalchemy/dialects/mysql/types.py +835 -0
  47. sqlalchemy/dialects/oracle/__init__.py +81 -0
  48. sqlalchemy/dialects/oracle/base.py +3802 -0
  49. sqlalchemy/dialects/oracle/cx_oracle.py +1555 -0
  50. sqlalchemy/dialects/oracle/dictionary.py +507 -0
  51. sqlalchemy/dialects/oracle/oracledb.py +941 -0
  52. sqlalchemy/dialects/oracle/provision.py +297 -0
  53. sqlalchemy/dialects/oracle/types.py +316 -0
  54. sqlalchemy/dialects/oracle/vector.py +365 -0
  55. sqlalchemy/dialects/postgresql/__init__.py +167 -0
  56. sqlalchemy/dialects/postgresql/_psycopg_common.py +189 -0
  57. sqlalchemy/dialects/postgresql/array.py +519 -0
  58. sqlalchemy/dialects/postgresql/asyncpg.py +1284 -0
  59. sqlalchemy/dialects/postgresql/base.py +5378 -0
  60. sqlalchemy/dialects/postgresql/dml.py +339 -0
  61. sqlalchemy/dialects/postgresql/ext.py +540 -0
  62. sqlalchemy/dialects/postgresql/hstore.py +406 -0
  63. sqlalchemy/dialects/postgresql/json.py +404 -0
  64. sqlalchemy/dialects/postgresql/named_types.py +524 -0
  65. sqlalchemy/dialects/postgresql/operators.py +129 -0
  66. sqlalchemy/dialects/postgresql/pg8000.py +669 -0
  67. sqlalchemy/dialects/postgresql/pg_catalog.py +326 -0
  68. sqlalchemy/dialects/postgresql/provision.py +183 -0
  69. sqlalchemy/dialects/postgresql/psycopg.py +862 -0
  70. sqlalchemy/dialects/postgresql/psycopg2.py +892 -0
  71. sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
  72. sqlalchemy/dialects/postgresql/ranges.py +1031 -0
  73. sqlalchemy/dialects/postgresql/types.py +313 -0
  74. sqlalchemy/dialects/sqlite/__init__.py +57 -0
  75. sqlalchemy/dialects/sqlite/aiosqlite.py +482 -0
  76. sqlalchemy/dialects/sqlite/base.py +3056 -0
  77. sqlalchemy/dialects/sqlite/dml.py +263 -0
  78. sqlalchemy/dialects/sqlite/json.py +92 -0
  79. sqlalchemy/dialects/sqlite/provision.py +229 -0
  80. sqlalchemy/dialects/sqlite/pysqlcipher.py +157 -0
  81. sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
  82. sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
  83. sqlalchemy/engine/__init__.py +62 -0
  84. sqlalchemy/engine/_py_processors.py +136 -0
  85. sqlalchemy/engine/_py_row.py +128 -0
  86. sqlalchemy/engine/_py_util.py +74 -0
  87. sqlalchemy/engine/base.py +3390 -0
  88. sqlalchemy/engine/characteristics.py +155 -0
  89. sqlalchemy/engine/create.py +893 -0
  90. sqlalchemy/engine/cursor.py +2298 -0
  91. sqlalchemy/engine/default.py +2394 -0
  92. sqlalchemy/engine/events.py +965 -0
  93. sqlalchemy/engine/interfaces.py +3471 -0
  94. sqlalchemy/engine/mock.py +134 -0
  95. sqlalchemy/engine/processors.py +61 -0
  96. sqlalchemy/engine/reflection.py +2102 -0
  97. sqlalchemy/engine/result.py +2399 -0
  98. sqlalchemy/engine/row.py +400 -0
  99. sqlalchemy/engine/strategies.py +16 -0
  100. sqlalchemy/engine/url.py +924 -0
  101. sqlalchemy/engine/util.py +167 -0
  102. sqlalchemy/event/__init__.py +26 -0
  103. sqlalchemy/event/api.py +220 -0
  104. sqlalchemy/event/attr.py +676 -0
  105. sqlalchemy/event/base.py +472 -0
  106. sqlalchemy/event/legacy.py +258 -0
  107. sqlalchemy/event/registry.py +390 -0
  108. sqlalchemy/events.py +17 -0
  109. sqlalchemy/exc.py +832 -0
  110. sqlalchemy/ext/__init__.py +11 -0
  111. sqlalchemy/ext/associationproxy.py +2027 -0
  112. sqlalchemy/ext/asyncio/__init__.py +25 -0
  113. sqlalchemy/ext/asyncio/base.py +281 -0
  114. sqlalchemy/ext/asyncio/engine.py +1471 -0
  115. sqlalchemy/ext/asyncio/exc.py +21 -0
  116. sqlalchemy/ext/asyncio/result.py +965 -0
  117. sqlalchemy/ext/asyncio/scoping.py +1599 -0
  118. sqlalchemy/ext/asyncio/session.py +1947 -0
  119. sqlalchemy/ext/automap.py +1701 -0
  120. sqlalchemy/ext/baked.py +570 -0
  121. sqlalchemy/ext/compiler.py +600 -0
  122. sqlalchemy/ext/declarative/__init__.py +65 -0
  123. sqlalchemy/ext/declarative/extensions.py +564 -0
  124. sqlalchemy/ext/horizontal_shard.py +478 -0
  125. sqlalchemy/ext/hybrid.py +1535 -0
  126. sqlalchemy/ext/indexable.py +364 -0
  127. sqlalchemy/ext/instrumentation.py +450 -0
  128. sqlalchemy/ext/mutable.py +1085 -0
  129. sqlalchemy/ext/mypy/__init__.py +6 -0
  130. sqlalchemy/ext/mypy/apply.py +324 -0
  131. sqlalchemy/ext/mypy/decl_class.py +515 -0
  132. sqlalchemy/ext/mypy/infer.py +590 -0
  133. sqlalchemy/ext/mypy/names.py +335 -0
  134. sqlalchemy/ext/mypy/plugin.py +303 -0
  135. sqlalchemy/ext/mypy/util.py +357 -0
  136. sqlalchemy/ext/orderinglist.py +439 -0
  137. sqlalchemy/ext/serializer.py +185 -0
  138. sqlalchemy/future/__init__.py +16 -0
  139. sqlalchemy/future/engine.py +15 -0
  140. sqlalchemy/inspection.py +174 -0
  141. sqlalchemy/log.py +288 -0
  142. sqlalchemy/orm/__init__.py +171 -0
  143. sqlalchemy/orm/_orm_constructors.py +2661 -0
  144. sqlalchemy/orm/_typing.py +179 -0
  145. sqlalchemy/orm/attributes.py +2845 -0
  146. sqlalchemy/orm/base.py +971 -0
  147. sqlalchemy/orm/bulk_persistence.py +2135 -0
  148. sqlalchemy/orm/clsregistry.py +571 -0
  149. sqlalchemy/orm/collections.py +1627 -0
  150. sqlalchemy/orm/context.py +3334 -0
  151. sqlalchemy/orm/decl_api.py +2004 -0
  152. sqlalchemy/orm/decl_base.py +2192 -0
  153. sqlalchemy/orm/dependency.py +1302 -0
  154. sqlalchemy/orm/descriptor_props.py +1092 -0
  155. sqlalchemy/orm/dynamic.py +300 -0
  156. sqlalchemy/orm/evaluator.py +379 -0
  157. sqlalchemy/orm/events.py +3252 -0
  158. sqlalchemy/orm/exc.py +237 -0
  159. sqlalchemy/orm/identity.py +302 -0
  160. sqlalchemy/orm/instrumentation.py +754 -0
  161. sqlalchemy/orm/interfaces.py +1496 -0
  162. sqlalchemy/orm/loading.py +1686 -0
  163. sqlalchemy/orm/mapped_collection.py +557 -0
  164. sqlalchemy/orm/mapper.py +4444 -0
  165. sqlalchemy/orm/path_registry.py +809 -0
  166. sqlalchemy/orm/persistence.py +1788 -0
  167. sqlalchemy/orm/properties.py +935 -0
  168. sqlalchemy/orm/query.py +3459 -0
  169. sqlalchemy/orm/relationships.py +3508 -0
  170. sqlalchemy/orm/scoping.py +2148 -0
  171. sqlalchemy/orm/session.py +5280 -0
  172. sqlalchemy/orm/state.py +1168 -0
  173. sqlalchemy/orm/state_changes.py +196 -0
  174. sqlalchemy/orm/strategies.py +3470 -0
  175. sqlalchemy/orm/strategy_options.py +2568 -0
  176. sqlalchemy/orm/sync.py +164 -0
  177. sqlalchemy/orm/unitofwork.py +796 -0
  178. sqlalchemy/orm/util.py +2403 -0
  179. sqlalchemy/orm/writeonly.py +674 -0
  180. sqlalchemy/pool/__init__.py +44 -0
  181. sqlalchemy/pool/base.py +1524 -0
  182. sqlalchemy/pool/events.py +375 -0
  183. sqlalchemy/pool/impl.py +588 -0
  184. sqlalchemy/py.typed +0 -0
  185. sqlalchemy/schema.py +69 -0
  186. sqlalchemy/sql/__init__.py +145 -0
  187. sqlalchemy/sql/_dml_constructors.py +132 -0
  188. sqlalchemy/sql/_elements_constructors.py +1872 -0
  189. sqlalchemy/sql/_orm_types.py +20 -0
  190. sqlalchemy/sql/_py_util.py +75 -0
  191. sqlalchemy/sql/_selectable_constructors.py +763 -0
  192. sqlalchemy/sql/_typing.py +482 -0
  193. sqlalchemy/sql/annotation.py +587 -0
  194. sqlalchemy/sql/base.py +2293 -0
  195. sqlalchemy/sql/cache_key.py +1057 -0
  196. sqlalchemy/sql/coercions.py +1404 -0
  197. sqlalchemy/sql/compiler.py +8081 -0
  198. sqlalchemy/sql/crud.py +1752 -0
  199. sqlalchemy/sql/ddl.py +1444 -0
  200. sqlalchemy/sql/default_comparator.py +551 -0
  201. sqlalchemy/sql/dml.py +1850 -0
  202. sqlalchemy/sql/elements.py +5589 -0
  203. sqlalchemy/sql/events.py +458 -0
  204. sqlalchemy/sql/expression.py +159 -0
  205. sqlalchemy/sql/functions.py +2158 -0
  206. sqlalchemy/sql/lambdas.py +1442 -0
  207. sqlalchemy/sql/naming.py +209 -0
  208. sqlalchemy/sql/operators.py +2623 -0
  209. sqlalchemy/sql/roles.py +323 -0
  210. sqlalchemy/sql/schema.py +6222 -0
  211. sqlalchemy/sql/selectable.py +7265 -0
  212. sqlalchemy/sql/sqltypes.py +3930 -0
  213. sqlalchemy/sql/traversals.py +1024 -0
  214. sqlalchemy/sql/type_api.py +2368 -0
  215. sqlalchemy/sql/util.py +1485 -0
  216. sqlalchemy/sql/visitors.py +1164 -0
  217. sqlalchemy/testing/__init__.py +96 -0
  218. sqlalchemy/testing/assertions.py +994 -0
  219. sqlalchemy/testing/assertsql.py +520 -0
  220. sqlalchemy/testing/asyncio.py +135 -0
  221. sqlalchemy/testing/config.py +434 -0
  222. sqlalchemy/testing/engines.py +483 -0
  223. sqlalchemy/testing/entities.py +117 -0
  224. sqlalchemy/testing/exclusions.py +476 -0
  225. sqlalchemy/testing/fixtures/__init__.py +28 -0
  226. sqlalchemy/testing/fixtures/base.py +384 -0
  227. sqlalchemy/testing/fixtures/mypy.py +332 -0
  228. sqlalchemy/testing/fixtures/orm.py +227 -0
  229. sqlalchemy/testing/fixtures/sql.py +482 -0
  230. sqlalchemy/testing/pickleable.py +155 -0
  231. sqlalchemy/testing/plugin/__init__.py +6 -0
  232. sqlalchemy/testing/plugin/bootstrap.py +51 -0
  233. sqlalchemy/testing/plugin/plugin_base.py +828 -0
  234. sqlalchemy/testing/plugin/pytestplugin.py +892 -0
  235. sqlalchemy/testing/profiling.py +329 -0
  236. sqlalchemy/testing/provision.py +603 -0
  237. sqlalchemy/testing/requirements.py +1945 -0
  238. sqlalchemy/testing/schema.py +198 -0
  239. sqlalchemy/testing/suite/__init__.py +19 -0
  240. sqlalchemy/testing/suite/test_cte.py +237 -0
  241. sqlalchemy/testing/suite/test_ddl.py +389 -0
  242. sqlalchemy/testing/suite/test_deprecations.py +153 -0
  243. sqlalchemy/testing/suite/test_dialect.py +776 -0
  244. sqlalchemy/testing/suite/test_insert.py +630 -0
  245. sqlalchemy/testing/suite/test_reflection.py +3557 -0
  246. sqlalchemy/testing/suite/test_results.py +504 -0
  247. sqlalchemy/testing/suite/test_rowcount.py +258 -0
  248. sqlalchemy/testing/suite/test_select.py +2010 -0
  249. sqlalchemy/testing/suite/test_sequence.py +317 -0
  250. sqlalchemy/testing/suite/test_types.py +2147 -0
  251. sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
  252. sqlalchemy/testing/suite/test_update_delete.py +139 -0
  253. sqlalchemy/testing/util.py +535 -0
  254. sqlalchemy/testing/warnings.py +52 -0
  255. sqlalchemy/types.py +74 -0
  256. sqlalchemy/util/__init__.py +162 -0
  257. sqlalchemy/util/_collections.py +712 -0
  258. sqlalchemy/util/_concurrency_py3k.py +288 -0
  259. sqlalchemy/util/_has_cy.py +40 -0
  260. sqlalchemy/util/_py_collections.py +541 -0
  261. sqlalchemy/util/compat.py +421 -0
  262. sqlalchemy/util/concurrency.py +110 -0
  263. sqlalchemy/util/deprecations.py +401 -0
  264. sqlalchemy/util/langhelpers.py +2203 -0
  265. sqlalchemy/util/preloaded.py +150 -0
  266. sqlalchemy/util/queue.py +322 -0
  267. sqlalchemy/util/tool_support.py +201 -0
  268. sqlalchemy/util/topological.py +120 -0
  269. sqlalchemy/util/typing.py +734 -0
  270. sqlalchemy-2.0.47.dist-info/METADATA +243 -0
  271. sqlalchemy-2.0.47.dist-info/RECORD +274 -0
  272. sqlalchemy-2.0.47.dist-info/WHEEL +5 -0
  273. sqlalchemy-2.0.47.dist-info/licenses/LICENSE +19 -0
  274. sqlalchemy-2.0.47.dist-info/top_level.txt +1 -0
@@ -0,0 +1,941 @@
1
+ # dialects/oracle/oracledb.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
+ r""".. dialect:: oracle+oracledb
10
+ :name: python-oracledb
11
+ :dbapi: oracledb
12
+ :connectstring: oracle+oracledb://user:pass@hostname:port[/dbname][?service_name=<service>[&key=value&key=value...]]
13
+ :url: https://oracle.github.io/python-oracledb/
14
+
15
+ Description
16
+ -----------
17
+
18
+ Python-oracledb is the Oracle Database driver for Python. It features a default
19
+ "thin" client mode that requires no dependencies, and an optional "thick" mode
20
+ that uses Oracle Client libraries. It supports SQLAlchemy features including
21
+ two phase transactions and Asyncio.
22
+
23
+ Python-oracle is the renamed, updated cx_Oracle driver. Oracle is no longer
24
+ doing any releases in the cx_Oracle namespace.
25
+
26
+ The SQLAlchemy ``oracledb`` dialect provides both a sync and an async
27
+ implementation under the same dialect name. The proper version is
28
+ selected depending on how the engine is created:
29
+
30
+ * calling :func:`_sa.create_engine` with ``oracle+oracledb://...`` will
31
+ automatically select the sync version::
32
+
33
+ from sqlalchemy import create_engine
34
+
35
+ sync_engine = create_engine(
36
+ "oracle+oracledb://scott:tiger@localhost?service_name=FREEPDB1"
37
+ )
38
+
39
+ * calling :func:`_asyncio.create_async_engine` with ``oracle+oracledb://...``
40
+ will automatically select the async version::
41
+
42
+ from sqlalchemy.ext.asyncio import create_async_engine
43
+
44
+ asyncio_engine = create_async_engine(
45
+ "oracle+oracledb://scott:tiger@localhost?service_name=FREEPDB1"
46
+ )
47
+
48
+ The asyncio version of the dialect may also be specified explicitly using the
49
+ ``oracledb_async`` suffix::
50
+
51
+ from sqlalchemy.ext.asyncio import create_async_engine
52
+
53
+ asyncio_engine = create_async_engine(
54
+ "oracle+oracledb_async://scott:tiger@localhost?service_name=FREEPDB1"
55
+ )
56
+
57
+ .. versionadded:: 2.0.25 added support for the async version of oracledb.
58
+
59
+ Thick mode support
60
+ ------------------
61
+
62
+ By default, the python-oracledb driver runs in a "thin" mode that does not
63
+ require Oracle Client libraries to be installed. The driver also supports a
64
+ "thick" mode that uses Oracle Client libraries to get functionality such as
65
+ Oracle Application Continuity.
66
+
67
+ To enable thick mode, call `oracledb.init_oracle_client()
68
+ <https://python-oracledb.readthedocs.io/en/latest/api_manual/module.html#oracledb.init_oracle_client>`_
69
+ explicitly, or pass the parameter ``thick_mode=True`` to
70
+ :func:`_sa.create_engine`. To pass custom arguments to
71
+ ``init_oracle_client()``, like the ``lib_dir`` path, a dict may be passed, for
72
+ example::
73
+
74
+ engine = sa.create_engine(
75
+ "oracle+oracledb://...",
76
+ thick_mode={
77
+ "lib_dir": "/path/to/oracle/client/lib",
78
+ "config_dir": "/path/to/network_config_file_directory",
79
+ "driver_name": "my-app : 1.0.0",
80
+ },
81
+ )
82
+
83
+ Note that passing a ``lib_dir`` path should only be done on macOS or
84
+ Windows. On Linux it does not behave as you might expect.
85
+
86
+ .. seealso::
87
+
88
+ python-oracledb documentation `Enabling python-oracledb Thick mode
89
+ <https://python-oracledb.readthedocs.io/en/latest/user_guide/initialization.html#enabling-python-oracledb-thick-mode>`_
90
+
91
+ Connecting to Oracle Database
92
+ -----------------------------
93
+
94
+ python-oracledb provides several methods of indicating the target database.
95
+ The dialect translates from a series of different URL forms.
96
+
97
+ Given the hostname, port and service name of the target database, you can
98
+ connect in SQLAlchemy using the ``service_name`` query string parameter::
99
+
100
+ engine = create_engine(
101
+ "oracle+oracledb://scott:tiger@hostname:port?service_name=myservice"
102
+ )
103
+
104
+ Connecting with Easy Connect strings
105
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
106
+
107
+ You can pass any valid python-oracledb connection string as the ``dsn`` key
108
+ value in a :paramref:`_sa.create_engine.connect_args` dictionary. See
109
+ python-oracledb documentation `Oracle Net Services Connection Strings
110
+ <https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#oracle-net-services-connection-strings>`_.
111
+
112
+ For example to use an `Easy Connect string
113
+ <https://download.oracle.com/ocomdocs/global/Oracle-Net-Easy-Connect-Plus.pdf>`_
114
+ with a timeout to prevent connection establishment from hanging if the network
115
+ transport to the database cannot be established in 30 seconds, and also setting
116
+ a keep-alive time of 60 seconds to stop idle network connections from being
117
+ terminated by a firewall::
118
+
119
+ e = create_engine(
120
+ "oracle+oracledb://@",
121
+ connect_args={
122
+ "user": "scott",
123
+ "password": "tiger",
124
+ "dsn": "hostname:port/myservice?transport_connect_timeout=30&expire_time=60",
125
+ },
126
+ )
127
+
128
+ The Easy Connect syntax has been enhanced during the life of Oracle Database.
129
+ Review the documentation for your database version. The current documentation
130
+ is at `Understanding the Easy Connect Naming Method
131
+ <https://www.oracle.com/pls/topic/lookup?ctx=dblatest&id=GUID-B0437826-43C1-49EC-A94D-B650B6A4A6EE>`_.
132
+
133
+ The general syntax is similar to:
134
+
135
+ .. sourcecode:: text
136
+
137
+ [[protocol:]//]host[:port][/[service_name]][?parameter_name=value{&parameter_name=value}]
138
+
139
+ Note that although the SQLAlchemy URL syntax ``hostname:port/dbname`` looks
140
+ like Oracle's Easy Connect syntax, it is different. SQLAlchemy's URL requires a
141
+ system identifier (SID) for the ``dbname`` component::
142
+
143
+ engine = create_engine("oracle+oracledb://scott:tiger@hostname:port/sid")
144
+
145
+ Easy Connect syntax does not support SIDs. It uses services names, which are
146
+ the preferred choice for connecting to Oracle Database.
147
+
148
+ Passing python-oracledb connect arguments
149
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
150
+
151
+ Other python-oracledb driver `connection options
152
+ <https://python-oracledb.readthedocs.io/en/latest/api_manual/module.html#oracledb.connect>`_
153
+ can be passed in ``connect_args``. For example::
154
+
155
+ e = create_engine(
156
+ "oracle+oracledb://@",
157
+ connect_args={
158
+ "user": "scott",
159
+ "password": "tiger",
160
+ "dsn": "hostname:port/myservice",
161
+ "events": True,
162
+ "mode": oracledb.AUTH_MODE_SYSDBA,
163
+ },
164
+ )
165
+
166
+ Connecting with tnsnames.ora TNS aliases
167
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
168
+
169
+ If no port, database name, or service name is provided, the dialect will use an
170
+ Oracle Database DSN "connection string". This takes the "hostname" portion of
171
+ the URL as the data source name. For example, if the ``tnsnames.ora`` file
172
+ contains a `TNS Alias
173
+ <https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#tns-aliases-for-connection-strings>`_
174
+ of ``myalias`` as below:
175
+
176
+ .. sourcecode:: text
177
+
178
+ myalias =
179
+ (DESCRIPTION =
180
+ (ADDRESS = (PROTOCOL = TCP)(HOST = mymachine.example.com)(PORT = 1521))
181
+ (CONNECT_DATA =
182
+ (SERVER = DEDICATED)
183
+ (SERVICE_NAME = orclpdb1)
184
+ )
185
+ )
186
+
187
+ The python-oracledb dialect connects to this database service when ``myalias`` is the
188
+ hostname portion of the URL, without specifying a port, database name or
189
+ ``service_name``::
190
+
191
+ engine = create_engine("oracle+oracledb://scott:tiger@myalias")
192
+
193
+ Connecting to Oracle Autonomous Database
194
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
195
+
196
+ Users of Oracle Autonomous Database should use either use the TNS Alias URL
197
+ shown above, or pass the TNS Alias as the ``dsn`` key value in a
198
+ :paramref:`_sa.create_engine.connect_args` dictionary.
199
+
200
+ If Oracle Autonomous Database is configured for mutual TLS ("mTLS")
201
+ connections, then additional configuration is required as shown in `Connecting
202
+ to Oracle Cloud Autonomous Databases
203
+ <https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#connecting-to-oracle-cloud-autonomous-databases>`_. In
204
+ summary, Thick mode users should configure file locations and set the wallet
205
+ path in ``sqlnet.ora`` appropriately::
206
+
207
+ e = create_engine(
208
+ "oracle+oracledb://@",
209
+ thick_mode={
210
+ # directory containing tnsnames.ora and cwallet.so
211
+ "config_dir": "/opt/oracle/wallet_dir",
212
+ },
213
+ connect_args={
214
+ "user": "scott",
215
+ "password": "tiger",
216
+ "dsn": "mydb_high",
217
+ },
218
+ )
219
+
220
+ Thin mode users of mTLS should pass the appropriate directories and PEM wallet
221
+ password when creating the engine, similar to::
222
+
223
+ e = create_engine(
224
+ "oracle+oracledb://@",
225
+ connect_args={
226
+ "user": "scott",
227
+ "password": "tiger",
228
+ "dsn": "mydb_high",
229
+ "config_dir": "/opt/oracle/wallet_dir", # directory containing tnsnames.ora
230
+ "wallet_location": "/opt/oracle/wallet_dir", # directory containing ewallet.pem
231
+ "wallet_password": "top secret", # password for the PEM file
232
+ },
233
+ )
234
+
235
+ Typically ``config_dir`` and ``wallet_location`` are the same directory, which
236
+ is where the Oracle Autonomous Database wallet zip file was extracted. Note
237
+ this directory should be protected.
238
+
239
+ Using python-oracledb Connection Pooling
240
+ ----------------------------------------
241
+
242
+ The python-oracledb driver provides its own connection pool implementation that
243
+ may be used in place of SQLAlchemy's pooling functionality. The driver pool
244
+ gives support for high availability features such as dead connection detection,
245
+ connection draining for planned database downtime, support for Oracle
246
+ Application Continuity and Transparent Application Continuity, and gives
247
+ support for `Database Resident Connection Pooling (DRCP)
248
+ <https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#database-resident-connection-pooling-drcp>`_.
249
+
250
+ To take advantage of python-oracledb's pool, use the
251
+ :paramref:`_sa.create_engine.creator` parameter to provide a function that
252
+ returns a new connection, along with setting
253
+ :paramref:`_sa.create_engine.pool_class` to ``NullPool`` to disable
254
+ SQLAlchemy's pooling::
255
+
256
+ import oracledb
257
+ from sqlalchemy import create_engine
258
+ from sqlalchemy import text
259
+ from sqlalchemy.pool import NullPool
260
+
261
+ # Uncomment to use the optional python-oracledb Thick mode.
262
+ # Review the python-oracledb doc for the appropriate parameters
263
+ # oracledb.init_oracle_client(<your parameters>)
264
+
265
+ pool = oracledb.create_pool(
266
+ user="scott",
267
+ password="tiger",
268
+ dsn="localhost:1521/freepdb1",
269
+ min=1,
270
+ max=4,
271
+ increment=1,
272
+ )
273
+ engine = create_engine(
274
+ "oracle+oracledb://", creator=pool.acquire, poolclass=NullPool
275
+ )
276
+
277
+ The above engine may then be used normally. Internally, python-oracledb handles
278
+ connection pooling::
279
+
280
+ with engine.connect() as conn:
281
+ print(conn.scalar(text("select 1 from dual")))
282
+
283
+ Refer to the python-oracledb documentation for `oracledb.create_pool()
284
+ <https://python-oracledb.readthedocs.io/en/latest/api_manual/module.html#oracledb.create_pool>`_
285
+ for the arguments that can be used when creating a connection pool.
286
+
287
+ .. _drcp:
288
+
289
+ Using Oracle Database Resident Connection Pooling (DRCP)
290
+ --------------------------------------------------------
291
+
292
+ When using Oracle Database's Database Resident Connection Pooling (DRCP), the
293
+ best practice is to specify a connection class and "purity". Refer to the
294
+ `python-oracledb documentation on DRCP
295
+ <https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#database-resident-connection-pooling-drcp>`_.
296
+ For example::
297
+
298
+ import oracledb
299
+ from sqlalchemy import create_engine
300
+ from sqlalchemy import text
301
+ from sqlalchemy.pool import NullPool
302
+
303
+ # Uncomment to use the optional python-oracledb Thick mode.
304
+ # Review the python-oracledb doc for the appropriate parameters
305
+ # oracledb.init_oracle_client(<your parameters>)
306
+
307
+ pool = oracledb.create_pool(
308
+ user="scott",
309
+ password="tiger",
310
+ dsn="localhost:1521/freepdb1",
311
+ min=1,
312
+ max=4,
313
+ increment=1,
314
+ cclass="MYCLASS",
315
+ purity=oracledb.PURITY_SELF,
316
+ )
317
+ engine = create_engine(
318
+ "oracle+oracledb://", creator=pool.acquire, poolclass=NullPool
319
+ )
320
+
321
+ The above engine may then be used normally where python-oracledb handles
322
+ application connection pooling and Oracle Database additionally uses DRCP::
323
+
324
+ with engine.connect() as conn:
325
+ print(conn.scalar(text("select 1 from dual")))
326
+
327
+ If you wish to use different connection classes or purities for different
328
+ connections, then wrap ``pool.acquire()``::
329
+
330
+ import oracledb
331
+ from sqlalchemy import create_engine
332
+ from sqlalchemy import text
333
+ from sqlalchemy.pool import NullPool
334
+
335
+ # Uncomment to use python-oracledb Thick mode.
336
+ # Review the python-oracledb doc for the appropriate parameters
337
+ # oracledb.init_oracle_client(<your parameters>)
338
+
339
+ pool = oracledb.create_pool(
340
+ user="scott",
341
+ password="tiger",
342
+ dsn="localhost:1521/freepdb1",
343
+ min=1,
344
+ max=4,
345
+ increment=1,
346
+ cclass="MYCLASS",
347
+ purity=oracledb.PURITY_SELF,
348
+ )
349
+
350
+
351
+ def creator():
352
+ return pool.acquire(cclass="MYOTHERCLASS", purity=oracledb.PURITY_NEW)
353
+
354
+
355
+ engine = create_engine(
356
+ "oracle+oracledb://", creator=creator, poolclass=NullPool
357
+ )
358
+
359
+ Engine Options consumed by the SQLAlchemy oracledb dialect outside of the driver
360
+ --------------------------------------------------------------------------------
361
+
362
+ There are also options that are consumed by the SQLAlchemy oracledb dialect
363
+ itself. These options are always passed directly to :func:`_sa.create_engine`,
364
+ such as::
365
+
366
+ e = create_engine("oracle+oracledb://user:pass@tnsalias", arraysize=500)
367
+
368
+ The parameters accepted by the oracledb dialect are as follows:
369
+
370
+ * ``arraysize`` - set the driver cursor.arraysize value. It defaults to
371
+ ``None``, indicating that the driver default value of 100 should be used.
372
+ This setting controls how many rows are buffered when fetching rows, and can
373
+ have a significant effect on performance if increased for queries that return
374
+ large numbers of rows.
375
+
376
+ .. versionchanged:: 2.0.26 - changed the default value from 50 to None,
377
+ to use the default value of the driver itself.
378
+
379
+ * ``auto_convert_lobs`` - defaults to True; See :ref:`oracledb_lob`.
380
+
381
+ * ``coerce_to_decimal`` - see :ref:`oracledb_numeric` for detail.
382
+
383
+ * ``encoding_errors`` - see :ref:`oracledb_unicode_encoding_errors` for detail.
384
+
385
+ .. _oracledb_unicode:
386
+
387
+ Unicode
388
+ -------
389
+
390
+ As is the case for all DBAPIs under Python 3, all strings are inherently
391
+ Unicode strings.
392
+
393
+ Ensuring the Correct Client Encoding
394
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
395
+
396
+ In python-oracledb, the encoding used for all character data is "UTF-8".
397
+
398
+ Unicode-specific Column datatypes
399
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
400
+
401
+ The Core expression language handles unicode data by use of the
402
+ :class:`.Unicode` and :class:`.UnicodeText` datatypes. These types correspond
403
+ to the VARCHAR2 and CLOB Oracle Database datatypes by default. When using
404
+ these datatypes with Unicode data, it is expected that the database is
405
+ configured with a Unicode-aware character set so that the VARCHAR2 and CLOB
406
+ datatypes can accommodate the data.
407
+
408
+ In the case that Oracle Database is not configured with a Unicode character
409
+ set, the two options are to use the :class:`_types.NCHAR` and
410
+ :class:`_oracle.NCLOB` datatypes explicitly, or to pass the flag
411
+ ``use_nchar_for_unicode=True`` to :func:`_sa.create_engine`, which will cause
412
+ the SQLAlchemy dialect to use NCHAR/NCLOB for the :class:`.Unicode` /
413
+ :class:`.UnicodeText` datatypes instead of VARCHAR/CLOB.
414
+
415
+ .. versionchanged:: 1.3 The :class:`.Unicode` and :class:`.UnicodeText`
416
+ datatypes now correspond to the ``VARCHAR2`` and ``CLOB`` Oracle Database
417
+ datatypes unless the ``use_nchar_for_unicode=True`` is passed to the dialect
418
+ when :func:`_sa.create_engine` is called.
419
+
420
+
421
+ .. _oracledb_unicode_encoding_errors:
422
+
423
+ Encoding Errors
424
+ ^^^^^^^^^^^^^^^
425
+
426
+ For the unusual case that data in Oracle Database is present with a broken
427
+ encoding, the dialect accepts a parameter ``encoding_errors`` which will be
428
+ passed to Unicode decoding functions in order to affect how decoding errors are
429
+ handled. The value is ultimately consumed by the Python `decode
430
+ <https://docs.python.org/3/library/stdtypes.html#bytes.decode>`_ function, and
431
+ is passed both via python-oracledb's ``encodingErrors`` parameter consumed by
432
+ ``Cursor.var()``, as well as SQLAlchemy's own decoding function, as the
433
+ python-oracledb dialect makes use of both under different circumstances.
434
+
435
+ .. versionadded:: 1.3.11
436
+
437
+
438
+ .. _oracledb_setinputsizes:
439
+
440
+ Fine grained control over python-oracledb data binding with setinputsizes
441
+ -------------------------------------------------------------------------
442
+
443
+ The python-oracle DBAPI has a deep and fundamental reliance upon the usage of
444
+ the DBAPI ``setinputsizes()`` call. The purpose of this call is to establish
445
+ the datatypes that are bound to a SQL statement for Python values being passed
446
+ as parameters. While virtually no other DBAPI assigns any use to the
447
+ ``setinputsizes()`` call, the python-oracledb DBAPI relies upon it heavily in
448
+ its interactions with the Oracle Database, and in some scenarios it is not
449
+ possible for SQLAlchemy to know exactly how data should be bound, as some
450
+ settings can cause profoundly different performance characteristics, while
451
+ altering the type coercion behavior at the same time.
452
+
453
+ Users of the oracledb dialect are **strongly encouraged** to read through
454
+ python-oracledb's list of built-in datatype symbols at `Database Types
455
+ <https://python-oracledb.readthedocs.io/en/latest/api_manual/module.html#database-types>`_
456
+ Note that in some cases, significant performance degradation can occur when
457
+ using these types vs. not.
458
+
459
+ On the SQLAlchemy side, the :meth:`.DialectEvents.do_setinputsizes` event can
460
+ be used both for runtime visibility (e.g. logging) of the setinputsizes step as
461
+ well as to fully control how ``setinputsizes()`` is used on a per-statement
462
+ basis.
463
+
464
+ .. versionadded:: 1.2.9 Added :meth:`.DialectEvents.setinputsizes`
465
+
466
+
467
+ Example 1 - logging all setinputsizes calls
468
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
469
+
470
+ The following example illustrates how to log the intermediary values from a
471
+ SQLAlchemy perspective before they are converted to the raw ``setinputsizes()``
472
+ parameter dictionary. The keys of the dictionary are :class:`.BindParameter`
473
+ objects which have a ``.key`` and a ``.type`` attribute::
474
+
475
+ from sqlalchemy import create_engine, event
476
+
477
+ engine = create_engine(
478
+ "oracle+oracledb://scott:tiger@localhost:1521?service_name=freepdb1"
479
+ )
480
+
481
+
482
+ @event.listens_for(engine, "do_setinputsizes")
483
+ def _log_setinputsizes(inputsizes, cursor, statement, parameters, context):
484
+ for bindparam, dbapitype in inputsizes.items():
485
+ log.info(
486
+ "Bound parameter name: %s SQLAlchemy type: %r DBAPI object: %s",
487
+ bindparam.key,
488
+ bindparam.type,
489
+ dbapitype,
490
+ )
491
+
492
+ Example 2 - remove all bindings to CLOB
493
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
494
+
495
+ For performance, fetching LOB datatypes from Oracle Database is set by default
496
+ for the ``Text`` type within SQLAlchemy. This setting can be modified as
497
+ follows::
498
+
499
+
500
+ from sqlalchemy import create_engine, event
501
+ from oracledb import CLOB
502
+
503
+ engine = create_engine(
504
+ "oracle+oracledb://scott:tiger@localhost:1521?service_name=freepdb1"
505
+ )
506
+
507
+
508
+ @event.listens_for(engine, "do_setinputsizes")
509
+ def _remove_clob(inputsizes, cursor, statement, parameters, context):
510
+ for bindparam, dbapitype in list(inputsizes.items()):
511
+ if dbapitype is CLOB:
512
+ del inputsizes[bindparam]
513
+
514
+ .. _oracledb_lob:
515
+
516
+ LOB Datatypes
517
+ --------------
518
+
519
+ LOB datatypes refer to the "large object" datatypes such as CLOB, NCLOB and
520
+ BLOB. Oracle Database can efficiently return these datatypes as a single
521
+ buffer. SQLAlchemy makes use of type handlers to do this by default.
522
+
523
+ To disable the use of the type handlers and deliver LOB objects as classic
524
+ buffered objects with a ``read()`` method, the parameter
525
+ ``auto_convert_lobs=False`` may be passed to :func:`_sa.create_engine`.
526
+
527
+ .. _oracledb_returning:
528
+
529
+ RETURNING Support
530
+ -----------------
531
+
532
+ The oracledb dialect implements RETURNING using OUT parameters. The dialect
533
+ supports RETURNING fully.
534
+
535
+ Two Phase Transaction Support
536
+ -----------------------------
537
+
538
+ Two phase transactions are fully supported with python-oracledb. (Thin mode
539
+ requires python-oracledb 2.3). APIs for two phase transactions are provided at
540
+ the Core level via :meth:`_engine.Connection.begin_twophase` and
541
+ :paramref:`_orm.Session.twophase` for transparent ORM use.
542
+
543
+ .. versionchanged:: 2.0.32 added support for two phase transactions
544
+
545
+ .. _oracledb_numeric:
546
+
547
+ Precision Numerics
548
+ ------------------
549
+
550
+ SQLAlchemy's numeric types can handle receiving and returning values as Python
551
+ ``Decimal`` objects or float objects. When a :class:`.Numeric` object, or a
552
+ subclass such as :class:`.Float`, :class:`_oracle.DOUBLE_PRECISION` etc. is in
553
+ use, the :paramref:`.Numeric.asdecimal` flag determines if values should be
554
+ coerced to ``Decimal`` upon return, or returned as float objects. To make
555
+ matters more complicated under Oracle Database, the ``NUMBER`` type can also
556
+ represent integer values if the "scale" is zero, so the Oracle
557
+ Database-specific :class:`_oracle.NUMBER` type takes this into account as well.
558
+
559
+ The oracledb dialect makes extensive use of connection- and cursor-level
560
+ "outputtypehandler" callables in order to coerce numeric values as requested.
561
+ These callables are specific to the specific flavor of :class:`.Numeric` in
562
+ use, as well as if no SQLAlchemy typing objects are present. There are
563
+ observed scenarios where Oracle Database may send incomplete or ambiguous
564
+ information about the numeric types being returned, such as a query where the
565
+ numeric types are buried under multiple levels of subquery. The type handlers
566
+ do their best to make the right decision in all cases, deferring to the
567
+ underlying python-oracledb DBAPI for all those cases where the driver can make
568
+ the best decision.
569
+
570
+ When no typing objects are present, as when executing plain SQL strings, a
571
+ default "outputtypehandler" is present which will generally return numeric
572
+ values which specify precision and scale as Python ``Decimal`` objects. To
573
+ disable this coercion to decimal for performance reasons, pass the flag
574
+ ``coerce_to_decimal=False`` to :func:`_sa.create_engine`::
575
+
576
+ engine = create_engine(
577
+ "oracle+oracledb://scott:tiger@tnsalias", coerce_to_decimal=False
578
+ )
579
+
580
+ The ``coerce_to_decimal`` flag only impacts the results of plain string
581
+ SQL statements that are not otherwise associated with a :class:`.Numeric`
582
+ SQLAlchemy type (or a subclass of such).
583
+
584
+ .. versionchanged:: 1.2 The numeric handling system for the oracle dialects has
585
+ been reworked to take advantage of newer driver features as well as better
586
+ integration of outputtypehandlers.
587
+
588
+ .. versionadded:: 2.0.0 added support for the python-oracledb driver.
589
+
590
+ """ # noqa
591
+ from __future__ import annotations
592
+
593
+ import collections
594
+ import re
595
+ from typing import Any
596
+ from typing import TYPE_CHECKING
597
+
598
+ from . import cx_oracle as _cx_oracle
599
+ from ... import exc
600
+ from ... import pool
601
+ from ...connectors.asyncio import AsyncAdapt_dbapi_connection
602
+ from ...connectors.asyncio import AsyncAdapt_dbapi_cursor
603
+ from ...connectors.asyncio import AsyncAdapt_dbapi_ss_cursor
604
+ from ...connectors.asyncio import AsyncAdaptFallback_dbapi_connection
605
+ from ...engine import default
606
+ from ...util import asbool
607
+ from ...util import await_fallback
608
+ from ...util import await_only
609
+
610
+ if TYPE_CHECKING:
611
+ from oracledb import AsyncConnection
612
+ from oracledb import AsyncCursor
613
+
614
+
615
+ class OracleExecutionContext_oracledb(
616
+ _cx_oracle.OracleExecutionContext_cx_oracle
617
+ ):
618
+ pass
619
+
620
+
621
+ class OracleDialect_oracledb(_cx_oracle.OracleDialect_cx_oracle):
622
+ supports_statement_cache = True
623
+ execution_ctx_cls = OracleExecutionContext_oracledb
624
+
625
+ driver = "oracledb"
626
+ _min_version = (1,)
627
+
628
+ def __init__(
629
+ self,
630
+ auto_convert_lobs=True,
631
+ coerce_to_decimal=True,
632
+ arraysize=None,
633
+ encoding_errors=None,
634
+ thick_mode=None,
635
+ **kwargs,
636
+ ):
637
+ super().__init__(
638
+ auto_convert_lobs,
639
+ coerce_to_decimal,
640
+ arraysize,
641
+ encoding_errors,
642
+ **kwargs,
643
+ )
644
+
645
+ if self.dbapi is not None and (
646
+ thick_mode or isinstance(thick_mode, dict)
647
+ ):
648
+ kw = thick_mode if isinstance(thick_mode, dict) else {}
649
+ self.dbapi.init_oracle_client(**kw)
650
+
651
+ @classmethod
652
+ def import_dbapi(cls):
653
+ import oracledb
654
+
655
+ return oracledb
656
+
657
+ @classmethod
658
+ def is_thin_mode(cls, connection):
659
+ return connection.connection.dbapi_connection.thin
660
+
661
+ @classmethod
662
+ def get_async_dialect_cls(cls, url):
663
+ return OracleDialectAsync_oracledb
664
+
665
+ def _load_version(self, dbapi_module):
666
+ version = (0, 0, 0)
667
+ if dbapi_module is not None:
668
+ m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", dbapi_module.version)
669
+ if m:
670
+ version = tuple(
671
+ int(x) for x in m.group(1, 2, 3) if x is not None
672
+ )
673
+ self.oracledb_ver = version
674
+ if (
675
+ self.oracledb_ver > (0, 0, 0)
676
+ and self.oracledb_ver < self._min_version
677
+ ):
678
+ raise exc.InvalidRequestError(
679
+ f"oracledb version {self._min_version} and above are supported"
680
+ )
681
+
682
+ def do_begin_twophase(self, connection, xid):
683
+ conn_xis = connection.connection.xid(*xid)
684
+ connection.connection.tpc_begin(conn_xis)
685
+ connection.connection.info["oracledb_xid"] = conn_xis
686
+
687
+ def do_prepare_twophase(self, connection, xid):
688
+ should_commit = connection.connection.tpc_prepare()
689
+ connection.info["oracledb_should_commit"] = should_commit
690
+
691
+ def do_rollback_twophase(
692
+ self, connection, xid, is_prepared=True, recover=False
693
+ ):
694
+ if recover:
695
+ conn_xid = connection.connection.xid(*xid)
696
+ else:
697
+ conn_xid = None
698
+ connection.connection.tpc_rollback(conn_xid)
699
+
700
+ def do_commit_twophase(
701
+ self, connection, xid, is_prepared=True, recover=False
702
+ ):
703
+ conn_xid = None
704
+ if not is_prepared:
705
+ should_commit = connection.connection.tpc_prepare()
706
+ elif recover:
707
+ conn_xid = connection.connection.xid(*xid)
708
+ should_commit = True
709
+ else:
710
+ should_commit = connection.info["oracledb_should_commit"]
711
+ if should_commit:
712
+ connection.connection.tpc_commit(conn_xid)
713
+
714
+ def do_recover_twophase(self, connection):
715
+ return [
716
+ # oracledb seems to return bytes
717
+ (
718
+ fi,
719
+ gti.decode() if isinstance(gti, bytes) else gti,
720
+ bq.decode() if isinstance(bq, bytes) else bq,
721
+ )
722
+ for fi, gti, bq in connection.connection.tpc_recover()
723
+ ]
724
+
725
+ def _check_max_identifier_length(self, connection):
726
+ if self.oracledb_ver >= (2, 5):
727
+ max_len = connection.connection.max_identifier_length
728
+ if max_len is not None:
729
+ return max_len
730
+ return super()._check_max_identifier_length(connection)
731
+
732
+
733
+ class AsyncAdapt_oracledb_cursor(AsyncAdapt_dbapi_cursor):
734
+ _cursor: AsyncCursor
735
+ _awaitable_cursor_close: bool = False
736
+
737
+ __slots__ = ()
738
+
739
+ @property
740
+ def outputtypehandler(self):
741
+ return self._cursor.outputtypehandler
742
+
743
+ @outputtypehandler.setter
744
+ def outputtypehandler(self, value):
745
+ self._cursor.outputtypehandler = value
746
+
747
+ def var(self, *args, **kwargs):
748
+ return self._cursor.var(*args, **kwargs)
749
+
750
+ def setinputsizes(self, *args: Any, **kwargs: Any) -> Any:
751
+ return self._cursor.setinputsizes(*args, **kwargs)
752
+
753
+ def _aenter_cursor(self, cursor: AsyncCursor) -> AsyncCursor:
754
+ try:
755
+ return cursor.__enter__()
756
+ except Exception as error:
757
+ self._adapt_connection._handle_exception(error)
758
+
759
+ async def _execute_async(self, operation, parameters):
760
+ # override to not use mutex, oracledb already has a mutex
761
+
762
+ if parameters is None:
763
+ result = await self._cursor.execute(operation)
764
+ else:
765
+ result = await self._cursor.execute(operation, parameters)
766
+
767
+ if self._cursor.description and not self.server_side:
768
+ self._rows = collections.deque(await self._cursor.fetchall())
769
+ return result
770
+
771
+ async def _executemany_async(
772
+ self,
773
+ operation,
774
+ seq_of_parameters,
775
+ ):
776
+ # override to not use mutex, oracledb already has a mutex
777
+ return await self._cursor.executemany(operation, seq_of_parameters)
778
+
779
+ def __enter__(self):
780
+ return self
781
+
782
+ def __exit__(self, type_: Any, value: Any, traceback: Any) -> None:
783
+ self.close()
784
+
785
+
786
+ class AsyncAdapt_oracledb_ss_cursor(
787
+ AsyncAdapt_dbapi_ss_cursor, AsyncAdapt_oracledb_cursor
788
+ ):
789
+ __slots__ = ()
790
+
791
+ def close(self) -> None:
792
+ if self._cursor is not None:
793
+ self._cursor.close()
794
+ self._cursor = None # type: ignore
795
+
796
+
797
+ class AsyncAdapt_oracledb_connection(AsyncAdapt_dbapi_connection):
798
+ _connection: AsyncConnection
799
+ __slots__ = ()
800
+
801
+ thin = True
802
+
803
+ _cursor_cls = AsyncAdapt_oracledb_cursor
804
+ _ss_cursor_cls = None
805
+
806
+ @property
807
+ def autocommit(self):
808
+ return self._connection.autocommit
809
+
810
+ @autocommit.setter
811
+ def autocommit(self, value):
812
+ self._connection.autocommit = value
813
+
814
+ @property
815
+ def outputtypehandler(self):
816
+ return self._connection.outputtypehandler
817
+
818
+ @outputtypehandler.setter
819
+ def outputtypehandler(self, value):
820
+ self._connection.outputtypehandler = value
821
+
822
+ @property
823
+ def version(self):
824
+ return self._connection.version
825
+
826
+ @property
827
+ def stmtcachesize(self):
828
+ return self._connection.stmtcachesize
829
+
830
+ @stmtcachesize.setter
831
+ def stmtcachesize(self, value):
832
+ self._connection.stmtcachesize = value
833
+
834
+ @property
835
+ def max_identifier_length(self):
836
+ return self._connection.max_identifier_length
837
+
838
+ def cursor(self):
839
+ return AsyncAdapt_oracledb_cursor(self)
840
+
841
+ def ss_cursor(self):
842
+ return AsyncAdapt_oracledb_ss_cursor(self)
843
+
844
+ def xid(self, *args: Any, **kwargs: Any) -> Any:
845
+ return self._connection.xid(*args, **kwargs)
846
+
847
+ def tpc_begin(self, *args: Any, **kwargs: Any) -> Any:
848
+ return self.await_(self._connection.tpc_begin(*args, **kwargs))
849
+
850
+ def tpc_commit(self, *args: Any, **kwargs: Any) -> Any:
851
+ return self.await_(self._connection.tpc_commit(*args, **kwargs))
852
+
853
+ def tpc_prepare(self, *args: Any, **kwargs: Any) -> Any:
854
+ return self.await_(self._connection.tpc_prepare(*args, **kwargs))
855
+
856
+ def tpc_recover(self, *args: Any, **kwargs: Any) -> Any:
857
+ return self.await_(self._connection.tpc_recover(*args, **kwargs))
858
+
859
+ def tpc_rollback(self, *args: Any, **kwargs: Any) -> Any:
860
+ return self.await_(self._connection.tpc_rollback(*args, **kwargs))
861
+
862
+
863
+ class AsyncAdaptFallback_oracledb_connection(
864
+ AsyncAdaptFallback_dbapi_connection, AsyncAdapt_oracledb_connection
865
+ ):
866
+ __slots__ = ()
867
+
868
+
869
+ class OracledbAdaptDBAPI:
870
+ def __init__(self, oracledb) -> None:
871
+ self.oracledb = oracledb
872
+
873
+ for k, v in self.oracledb.__dict__.items():
874
+ if k != "connect":
875
+ self.__dict__[k] = v
876
+
877
+ def connect(self, *arg, **kw):
878
+ async_fallback = kw.pop("async_fallback", False)
879
+ creator_fn = kw.pop("async_creator_fn", self.oracledb.connect_async)
880
+
881
+ if asbool(async_fallback):
882
+ return AsyncAdaptFallback_oracledb_connection(
883
+ self, await_fallback(creator_fn(*arg, **kw))
884
+ )
885
+
886
+ else:
887
+ return AsyncAdapt_oracledb_connection(
888
+ self, await_only(creator_fn(*arg, **kw))
889
+ )
890
+
891
+
892
+ class OracleExecutionContextAsync_oracledb(OracleExecutionContext_oracledb):
893
+ # restore default create cursor
894
+ create_cursor = default.DefaultExecutionContext.create_cursor
895
+
896
+ def create_default_cursor(self):
897
+ # copy of OracleExecutionContext_cx_oracle.create_cursor
898
+ c = self._dbapi_connection.cursor()
899
+ if self.dialect.arraysize:
900
+ c.arraysize = self.dialect.arraysize
901
+
902
+ return c
903
+
904
+ def create_server_side_cursor(self):
905
+ c = self._dbapi_connection.ss_cursor()
906
+ if self.dialect.arraysize:
907
+ c.arraysize = self.dialect.arraysize
908
+
909
+ return c
910
+
911
+
912
+ class OracleDialectAsync_oracledb(OracleDialect_oracledb):
913
+ is_async = True
914
+ supports_server_side_cursors = True
915
+ supports_statement_cache = True
916
+ execution_ctx_cls = OracleExecutionContextAsync_oracledb
917
+
918
+ _min_version = (2,)
919
+
920
+ # thick_mode mode is not supported by asyncio, oracledb will raise
921
+ @classmethod
922
+ def import_dbapi(cls):
923
+ import oracledb
924
+
925
+ return OracledbAdaptDBAPI(oracledb)
926
+
927
+ @classmethod
928
+ def get_pool_class(cls, url):
929
+ async_fallback = url.query.get("async_fallback", False)
930
+
931
+ if asbool(async_fallback):
932
+ return pool.FallbackAsyncAdaptedQueuePool
933
+ else:
934
+ return pool.AsyncAdaptedQueuePool
935
+
936
+ def get_driver_connection(self, connection):
937
+ return connection._connection
938
+
939
+
940
+ dialect = OracleDialect_oracledb
941
+ dialect_async = OracleDialectAsync_oracledb