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,431 @@
1
+ # dialects/oracle/oracledb.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
+ r"""
10
+ .. dialect:: oracle+oracledb
11
+ :name: python-oracledb
12
+ :dbapi: oracledb
13
+ :connectstring: oracle+oracledb://user:pass@hostname:port[/dbname][?service_name=<service>[&key=value&key=value...]]
14
+ :url: https://oracle.github.io/python-oracledb/
15
+
16
+ Description
17
+ -----------
18
+
19
+ python-oracledb is released by Oracle to supersede the cx_Oracle driver.
20
+ It is fully compatible with cx_Oracle and features both a "thin" client
21
+ mode that requires no dependencies, as well as a "thick" mode that uses
22
+ the Oracle Client Interface in the same way as cx_Oracle.
23
+
24
+ .. seealso::
25
+
26
+ :ref:`cx_oracle` - all of cx_Oracle's notes apply to the oracledb driver
27
+ as well, with the exception that oracledb supports two phase transactions.
28
+
29
+ The SQLAlchemy ``oracledb`` dialect provides both a sync and an async
30
+ implementation under the same dialect name. The proper version is
31
+ selected depending on how the engine is created:
32
+
33
+ * calling :func:`_sa.create_engine` with ``oracle+oracledb://...`` will
34
+ automatically select the sync version, e.g.::
35
+
36
+ from sqlalchemy import create_engine
37
+ sync_engine = create_engine("oracle+oracledb://scott:tiger@localhost/?service_name=XEPDB1")
38
+
39
+ * calling :func:`_asyncio.create_async_engine` with
40
+ ``oracle+oracledb://...`` will automatically select the async version,
41
+ e.g.::
42
+
43
+ from sqlalchemy.ext.asyncio import create_async_engine
44
+ asyncio_engine = create_async_engine("oracle+oracledb://scott:tiger@localhost/?service_name=XEPDB1")
45
+
46
+ The asyncio version of the dialect may also be specified explicitly using the
47
+ ``oracledb_async`` suffix, as::
48
+
49
+ from sqlalchemy.ext.asyncio import create_async_engine
50
+ asyncio_engine = create_async_engine("oracle+oracledb_async://scott:tiger@localhost/?service_name=XEPDB1")
51
+
52
+ .. versionadded:: 2.0.25 added support for the async version of oracledb.
53
+
54
+ Thick mode support
55
+ ------------------
56
+
57
+ By default the ``python-oracledb`` is started in thin mode, that does not
58
+ require oracle client libraries to be installed in the system. The
59
+ ``python-oracledb`` driver also support a "thick" mode, that behaves
60
+ similarly to ``cx_oracle`` and requires that Oracle Client Interface (OCI)
61
+ is installed.
62
+
63
+ To enable this mode, the user may call ``oracledb.init_oracle_client``
64
+ manually, or by passing the parameter ``thick_mode=True`` to
65
+ :func:`_sa.create_engine`. To pass custom arguments to ``init_oracle_client``,
66
+ like the ``lib_dir`` path, a dict may be passed to this parameter, as in::
67
+
68
+ engine = sa.create_engine("oracle+oracledb://...", thick_mode={
69
+ "lib_dir": "/path/to/oracle/client/lib", "driver_name": "my-app"
70
+ })
71
+
72
+ .. seealso::
73
+
74
+ https://python-oracledb.readthedocs.io/en/latest/api_manual/module.html#oracledb.init_oracle_client
75
+
76
+ Two Phase Transactions Supported
77
+ --------------------------------
78
+
79
+ Two phase transactions are fully supported under oracledb. Starting with
80
+ oracledb 2.3 two phase transactions are supported also in thin mode. APIs
81
+ for two phase transactions are provided at the Core level via
82
+ :meth:`_engine.Connection.begin_twophase` and :paramref:`_orm.Session.twophase`
83
+ for transparent ORM use.
84
+
85
+ .. versionchanged:: 2.0.32 added support for two phase transactions
86
+
87
+ .. versionadded:: 2.0.0 added support for oracledb driver.
88
+
89
+ """ # noqa
90
+ from __future__ import annotations
91
+
92
+ import collections
93
+ import re
94
+ from typing import Any
95
+ from typing import TYPE_CHECKING
96
+
97
+ from . import cx_oracle as _cx_oracle
98
+ from ... import exc
99
+ from ... import pool
100
+ from ...connectors.asyncio import AsyncAdapt_dbapi_connection
101
+ from ...connectors.asyncio import AsyncAdapt_dbapi_cursor
102
+ from ...connectors.asyncio import AsyncAdapt_dbapi_ss_cursor
103
+ from ...connectors.asyncio import AsyncAdaptFallback_dbapi_connection
104
+ from ...engine import default
105
+ from ...util import asbool
106
+ from ...util import await_fallback
107
+ from ...util import await_only
108
+
109
+ if TYPE_CHECKING:
110
+ from oracledb import AsyncConnection
111
+ from oracledb import AsyncCursor
112
+
113
+
114
+ class OracleExecutionContext_oracledb(
115
+ _cx_oracle.OracleExecutionContext_cx_oracle
116
+ ):
117
+ pass
118
+
119
+
120
+ class OracleDialect_oracledb(_cx_oracle.OracleDialect_cx_oracle):
121
+ supports_statement_cache = True
122
+ execution_ctx_cls = OracleExecutionContext_oracledb
123
+
124
+ driver = "oracledb"
125
+ _min_version = (1,)
126
+
127
+ def __init__(
128
+ self,
129
+ auto_convert_lobs=True,
130
+ coerce_to_decimal=True,
131
+ arraysize=None,
132
+ encoding_errors=None,
133
+ thick_mode=None,
134
+ **kwargs,
135
+ ):
136
+ super().__init__(
137
+ auto_convert_lobs,
138
+ coerce_to_decimal,
139
+ arraysize,
140
+ encoding_errors,
141
+ **kwargs,
142
+ )
143
+
144
+ if self.dbapi is not None and (
145
+ thick_mode or isinstance(thick_mode, dict)
146
+ ):
147
+ kw = thick_mode if isinstance(thick_mode, dict) else {}
148
+ self.dbapi.init_oracle_client(**kw)
149
+
150
+ @classmethod
151
+ def import_dbapi(cls):
152
+ import oracledb
153
+
154
+ return oracledb
155
+
156
+ @classmethod
157
+ def is_thin_mode(cls, connection):
158
+ return connection.connection.dbapi_connection.thin
159
+
160
+ @classmethod
161
+ def get_async_dialect_cls(cls, url):
162
+ return OracleDialectAsync_oracledb
163
+
164
+ def _load_version(self, dbapi_module):
165
+ version = (0, 0, 0)
166
+ if dbapi_module is not None:
167
+ m = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", dbapi_module.version)
168
+ if m:
169
+ version = tuple(
170
+ int(x) for x in m.group(1, 2, 3) if x is not None
171
+ )
172
+ self.oracledb_ver = version
173
+ if (
174
+ self.oracledb_ver > (0, 0, 0)
175
+ and self.oracledb_ver < self._min_version
176
+ ):
177
+ raise exc.InvalidRequestError(
178
+ f"oracledb version {self._min_version} and above are supported"
179
+ )
180
+
181
+ def do_begin_twophase(self, connection, xid):
182
+ conn_xis = connection.connection.xid(*xid)
183
+ connection.connection.tpc_begin(conn_xis)
184
+ connection.connection.info["oracledb_xid"] = conn_xis
185
+
186
+ def do_prepare_twophase(self, connection, xid):
187
+ should_commit = connection.connection.tpc_prepare()
188
+ connection.info["oracledb_should_commit"] = should_commit
189
+
190
+ def do_rollback_twophase(
191
+ self, connection, xid, is_prepared=True, recover=False
192
+ ):
193
+ if recover:
194
+ conn_xid = connection.connection.xid(*xid)
195
+ else:
196
+ conn_xid = None
197
+ connection.connection.tpc_rollback(conn_xid)
198
+
199
+ def do_commit_twophase(
200
+ self, connection, xid, is_prepared=True, recover=False
201
+ ):
202
+ conn_xid = None
203
+ if not is_prepared:
204
+ should_commit = connection.connection.tpc_prepare()
205
+ elif recover:
206
+ conn_xid = connection.connection.xid(*xid)
207
+ should_commit = True
208
+ else:
209
+ should_commit = connection.info["oracledb_should_commit"]
210
+ if should_commit:
211
+ connection.connection.tpc_commit(conn_xid)
212
+
213
+ def do_recover_twophase(self, connection):
214
+ return [
215
+ # oracledb seems to return bytes
216
+ (
217
+ fi,
218
+ gti.decode() if isinstance(gti, bytes) else gti,
219
+ bq.decode() if isinstance(bq, bytes) else bq,
220
+ )
221
+ for fi, gti, bq in connection.connection.tpc_recover()
222
+ ]
223
+
224
+
225
+ class AsyncAdapt_oracledb_cursor(AsyncAdapt_dbapi_cursor):
226
+ _cursor: AsyncCursor
227
+ __slots__ = ()
228
+
229
+ @property
230
+ def outputtypehandler(self):
231
+ return self._cursor.outputtypehandler
232
+
233
+ @outputtypehandler.setter
234
+ def outputtypehandler(self, value):
235
+ self._cursor.outputtypehandler = value
236
+
237
+ def var(self, *args, **kwargs):
238
+ return self._cursor.var(*args, **kwargs)
239
+
240
+ def close(self):
241
+ self._rows.clear()
242
+ self._cursor.close()
243
+
244
+ def setinputsizes(self, *args: Any, **kwargs: Any) -> Any:
245
+ return self._cursor.setinputsizes(*args, **kwargs)
246
+
247
+ def _aenter_cursor(self, cursor: AsyncCursor) -> AsyncCursor:
248
+ try:
249
+ return cursor.__enter__()
250
+ except Exception as error:
251
+ self._adapt_connection._handle_exception(error)
252
+
253
+ async def _execute_async(self, operation, parameters):
254
+ # override to not use mutex, oracledb already has mutex
255
+
256
+ if parameters is None:
257
+ result = await self._cursor.execute(operation)
258
+ else:
259
+ result = await self._cursor.execute(operation, parameters)
260
+
261
+ if self._cursor.description and not self.server_side:
262
+ self._rows = collections.deque(await self._cursor.fetchall())
263
+ return result
264
+
265
+ async def _executemany_async(
266
+ self,
267
+ operation,
268
+ seq_of_parameters,
269
+ ):
270
+ # override to not use mutex, oracledb already has mutex
271
+ return await self._cursor.executemany(operation, seq_of_parameters)
272
+
273
+ def __enter__(self):
274
+ return self
275
+
276
+ def __exit__(self, type_: Any, value: Any, traceback: Any) -> None:
277
+ self.close()
278
+
279
+
280
+ class AsyncAdapt_oracledb_ss_cursor(
281
+ AsyncAdapt_dbapi_ss_cursor, AsyncAdapt_oracledb_cursor
282
+ ):
283
+ __slots__ = ()
284
+
285
+ def close(self) -> None:
286
+ if self._cursor is not None:
287
+ self._cursor.close()
288
+ self._cursor = None # type: ignore
289
+
290
+
291
+ class AsyncAdapt_oracledb_connection(AsyncAdapt_dbapi_connection):
292
+ _connection: AsyncConnection
293
+ __slots__ = ()
294
+
295
+ thin = True
296
+
297
+ _cursor_cls = AsyncAdapt_oracledb_cursor
298
+ _ss_cursor_cls = None
299
+
300
+ @property
301
+ def autocommit(self):
302
+ return self._connection.autocommit
303
+
304
+ @autocommit.setter
305
+ def autocommit(self, value):
306
+ self._connection.autocommit = value
307
+
308
+ @property
309
+ def outputtypehandler(self):
310
+ return self._connection.outputtypehandler
311
+
312
+ @outputtypehandler.setter
313
+ def outputtypehandler(self, value):
314
+ self._connection.outputtypehandler = value
315
+
316
+ @property
317
+ def version(self):
318
+ return self._connection.version
319
+
320
+ @property
321
+ def stmtcachesize(self):
322
+ return self._connection.stmtcachesize
323
+
324
+ @stmtcachesize.setter
325
+ def stmtcachesize(self, value):
326
+ self._connection.stmtcachesize = value
327
+
328
+ def cursor(self):
329
+ return AsyncAdapt_oracledb_cursor(self)
330
+
331
+ def ss_cursor(self):
332
+ return AsyncAdapt_oracledb_ss_cursor(self)
333
+
334
+ def xid(self, *args: Any, **kwargs: Any) -> Any:
335
+ return self._connection.xid(*args, **kwargs)
336
+
337
+ def tpc_begin(self, *args: Any, **kwargs: Any) -> Any:
338
+ return self.await_(self._connection.tpc_begin(*args, **kwargs))
339
+
340
+ def tpc_commit(self, *args: Any, **kwargs: Any) -> Any:
341
+ return self.await_(self._connection.tpc_commit(*args, **kwargs))
342
+
343
+ def tpc_prepare(self, *args: Any, **kwargs: Any) -> Any:
344
+ return self.await_(self._connection.tpc_prepare(*args, **kwargs))
345
+
346
+ def tpc_recover(self, *args: Any, **kwargs: Any) -> Any:
347
+ return self.await_(self._connection.tpc_recover(*args, **kwargs))
348
+
349
+ def tpc_rollback(self, *args: Any, **kwargs: Any) -> Any:
350
+ return self.await_(self._connection.tpc_rollback(*args, **kwargs))
351
+
352
+
353
+ class AsyncAdaptFallback_oracledb_connection(
354
+ AsyncAdaptFallback_dbapi_connection, AsyncAdapt_oracledb_connection
355
+ ):
356
+ __slots__ = ()
357
+
358
+
359
+ class OracledbAdaptDBAPI:
360
+ def __init__(self, oracledb) -> None:
361
+ self.oracledb = oracledb
362
+
363
+ for k, v in self.oracledb.__dict__.items():
364
+ if k != "connect":
365
+ self.__dict__[k] = v
366
+
367
+ def connect(self, *arg, **kw):
368
+ async_fallback = kw.pop("async_fallback", False)
369
+ creator_fn = kw.pop("async_creator_fn", self.oracledb.connect_async)
370
+
371
+ if asbool(async_fallback):
372
+ return AsyncAdaptFallback_oracledb_connection(
373
+ self, await_fallback(creator_fn(*arg, **kw))
374
+ )
375
+
376
+ else:
377
+ return AsyncAdapt_oracledb_connection(
378
+ self, await_only(creator_fn(*arg, **kw))
379
+ )
380
+
381
+
382
+ class OracleExecutionContextAsync_oracledb(OracleExecutionContext_oracledb):
383
+ # restore default create cursor
384
+ create_cursor = default.DefaultExecutionContext.create_cursor
385
+
386
+ def create_default_cursor(self):
387
+ # copy of OracleExecutionContext_cx_oracle.create_cursor
388
+ c = self._dbapi_connection.cursor()
389
+ if self.dialect.arraysize:
390
+ c.arraysize = self.dialect.arraysize
391
+
392
+ return c
393
+
394
+ def create_server_side_cursor(self):
395
+ c = self._dbapi_connection.ss_cursor()
396
+ if self.dialect.arraysize:
397
+ c.arraysize = self.dialect.arraysize
398
+
399
+ return c
400
+
401
+
402
+ class OracleDialectAsync_oracledb(OracleDialect_oracledb):
403
+ is_async = True
404
+ supports_server_side_cursors = True
405
+ supports_statement_cache = True
406
+ execution_ctx_cls = OracleExecutionContextAsync_oracledb
407
+
408
+ _min_version = (2,)
409
+
410
+ # thick_mode mode is not supported by asyncio, oracledb will raise
411
+ @classmethod
412
+ def import_dbapi(cls):
413
+ import oracledb
414
+
415
+ return OracledbAdaptDBAPI(oracledb)
416
+
417
+ @classmethod
418
+ def get_pool_class(cls, url):
419
+ async_fallback = url.query.get("async_fallback", False)
420
+
421
+ if asbool(async_fallback):
422
+ return pool.FallbackAsyncAdaptedQueuePool
423
+ else:
424
+ return pool.AsyncAdaptedQueuePool
425
+
426
+ def get_driver_connection(self, connection):
427
+ return connection._connection
428
+
429
+
430
+ dialect = OracleDialect_oracledb
431
+ dialect_async = OracleDialectAsync_oracledb
@@ -0,0 +1,220 @@
1
+ # dialects/oracle/provision.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
+ from ... import create_engine
10
+ from ... import exc
11
+ from ... import inspect
12
+ from ...engine import url as sa_url
13
+ from ...testing.provision import configure_follower
14
+ from ...testing.provision import create_db
15
+ from ...testing.provision import drop_all_schema_objects_post_tables
16
+ from ...testing.provision import drop_all_schema_objects_pre_tables
17
+ from ...testing.provision import drop_db
18
+ from ...testing.provision import follower_url_from_main
19
+ from ...testing.provision import log
20
+ from ...testing.provision import post_configure_engine
21
+ from ...testing.provision import run_reap_dbs
22
+ from ...testing.provision import set_default_schema_on_connection
23
+ from ...testing.provision import stop_test_class_outside_fixtures
24
+ from ...testing.provision import temp_table_keyword_args
25
+ from ...testing.provision import update_db_opts
26
+
27
+
28
+ @create_db.for_db("oracle")
29
+ def _oracle_create_db(cfg, eng, ident):
30
+ # NOTE: make sure you've run "ALTER DATABASE default tablespace users" or
31
+ # similar, so that the default tablespace is not "system"; reflection will
32
+ # fail otherwise
33
+ with eng.begin() as conn:
34
+ conn.exec_driver_sql("create user %s identified by xe" % ident)
35
+ conn.exec_driver_sql("create user %s_ts1 identified by xe" % ident)
36
+ conn.exec_driver_sql("create user %s_ts2 identified by xe" % ident)
37
+ conn.exec_driver_sql("grant dba to %s" % (ident,))
38
+ conn.exec_driver_sql("grant unlimited tablespace to %s" % ident)
39
+ conn.exec_driver_sql("grant unlimited tablespace to %s_ts1" % ident)
40
+ conn.exec_driver_sql("grant unlimited tablespace to %s_ts2" % ident)
41
+ # these are needed to create materialized views
42
+ conn.exec_driver_sql("grant create table to %s" % ident)
43
+ conn.exec_driver_sql("grant create table to %s_ts1" % ident)
44
+ conn.exec_driver_sql("grant create table to %s_ts2" % ident)
45
+
46
+
47
+ @configure_follower.for_db("oracle")
48
+ def _oracle_configure_follower(config, ident):
49
+ config.test_schema = "%s_ts1" % ident
50
+ config.test_schema_2 = "%s_ts2" % ident
51
+
52
+
53
+ def _ora_drop_ignore(conn, dbname):
54
+ try:
55
+ conn.exec_driver_sql("drop user %s cascade" % dbname)
56
+ log.info("Reaped db: %s", dbname)
57
+ return True
58
+ except exc.DatabaseError as err:
59
+ log.warning("couldn't drop db: %s", err)
60
+ return False
61
+
62
+
63
+ @drop_all_schema_objects_pre_tables.for_db("oracle")
64
+ def _ora_drop_all_schema_objects_pre_tables(cfg, eng):
65
+ _purge_recyclebin(eng)
66
+ _purge_recyclebin(eng, cfg.test_schema)
67
+
68
+
69
+ @drop_all_schema_objects_post_tables.for_db("oracle")
70
+ def _ora_drop_all_schema_objects_post_tables(cfg, eng):
71
+ with eng.begin() as conn:
72
+ for syn in conn.dialect._get_synonyms(conn, None, None, None):
73
+ conn.exec_driver_sql(f"drop synonym {syn['synonym_name']}")
74
+
75
+ for syn in conn.dialect._get_synonyms(
76
+ conn, cfg.test_schema, None, None
77
+ ):
78
+ conn.exec_driver_sql(
79
+ f"drop synonym {cfg.test_schema}.{syn['synonym_name']}"
80
+ )
81
+
82
+ for tmp_table in inspect(conn).get_temp_table_names():
83
+ conn.exec_driver_sql(f"drop table {tmp_table}")
84
+
85
+
86
+ @drop_db.for_db("oracle")
87
+ def _oracle_drop_db(cfg, eng, ident):
88
+ with eng.begin() as conn:
89
+ # cx_Oracle seems to occasionally leak open connections when a large
90
+ # suite it run, even if we confirm we have zero references to
91
+ # connection objects.
92
+ # while there is a "kill session" command in Oracle,
93
+ # it unfortunately does not release the connection sufficiently.
94
+ _ora_drop_ignore(conn, ident)
95
+ _ora_drop_ignore(conn, "%s_ts1" % ident)
96
+ _ora_drop_ignore(conn, "%s_ts2" % ident)
97
+
98
+
99
+ @stop_test_class_outside_fixtures.for_db("oracle")
100
+ def _ora_stop_test_class_outside_fixtures(config, db, cls):
101
+ try:
102
+ _purge_recyclebin(db)
103
+ except exc.DatabaseError as err:
104
+ log.warning("purge recyclebin command failed: %s", err)
105
+
106
+ # clear statement cache on all connections that were used
107
+ # https://github.com/oracle/python-cx_Oracle/issues/519
108
+
109
+ for cx_oracle_conn in _all_conns:
110
+ try:
111
+ sc = cx_oracle_conn.stmtcachesize
112
+ except db.dialect.dbapi.InterfaceError:
113
+ # connection closed
114
+ pass
115
+ else:
116
+ cx_oracle_conn.stmtcachesize = 0
117
+ cx_oracle_conn.stmtcachesize = sc
118
+ _all_conns.clear()
119
+
120
+
121
+ def _purge_recyclebin(eng, schema=None):
122
+ with eng.begin() as conn:
123
+ if schema is None:
124
+ # run magic command to get rid of identity sequences
125
+ # https://floo.bar/2019/11/29/drop-the-underlying-sequence-of-an-identity-column/ # noqa: E501
126
+ conn.exec_driver_sql("purge recyclebin")
127
+ else:
128
+ # per user: https://community.oracle.com/tech/developers/discussion/2255402/how-to-clear-dba-recyclebin-for-a-particular-user # noqa: E501
129
+ for owner, object_name, type_ in conn.exec_driver_sql(
130
+ "select owner, object_name,type from "
131
+ "dba_recyclebin where owner=:schema and type='TABLE'",
132
+ {"schema": conn.dialect.denormalize_name(schema)},
133
+ ).all():
134
+ conn.exec_driver_sql(f'purge {type_} {owner}."{object_name}"')
135
+
136
+
137
+ _all_conns = set()
138
+
139
+
140
+ @post_configure_engine.for_db("oracle")
141
+ def _oracle_post_configure_engine(url, engine, follower_ident):
142
+ from sqlalchemy import event
143
+
144
+ @event.listens_for(engine, "checkout")
145
+ def checkout(dbapi_con, con_record, con_proxy):
146
+ _all_conns.add(dbapi_con)
147
+
148
+ @event.listens_for(engine, "checkin")
149
+ def checkin(dbapi_connection, connection_record):
150
+ # work around cx_Oracle issue:
151
+ # https://github.com/oracle/python-cx_Oracle/issues/530
152
+ # invalidate oracle connections that had 2pc set up
153
+ if "cx_oracle_xid" in connection_record.info:
154
+ connection_record.invalidate()
155
+
156
+
157
+ @run_reap_dbs.for_db("oracle")
158
+ def _reap_oracle_dbs(url, idents):
159
+ log.info("db reaper connecting to %r", url)
160
+ eng = create_engine(url)
161
+ with eng.begin() as conn:
162
+ log.info("identifiers in file: %s", ", ".join(idents))
163
+
164
+ to_reap = conn.exec_driver_sql(
165
+ "select u.username from all_users u where username "
166
+ "like 'TEST_%' and not exists (select username "
167
+ "from v$session where username=u.username)"
168
+ )
169
+ all_names = {username.lower() for (username,) in to_reap}
170
+ to_drop = set()
171
+ for name in all_names:
172
+ if name.endswith("_ts1") or name.endswith("_ts2"):
173
+ continue
174
+ elif name in idents:
175
+ to_drop.add(name)
176
+ if "%s_ts1" % name in all_names:
177
+ to_drop.add("%s_ts1" % name)
178
+ if "%s_ts2" % name in all_names:
179
+ to_drop.add("%s_ts2" % name)
180
+
181
+ dropped = total = 0
182
+ for total, username in enumerate(to_drop, 1):
183
+ if _ora_drop_ignore(conn, username):
184
+ dropped += 1
185
+ log.info(
186
+ "Dropped %d out of %d stale databases detected", dropped, total
187
+ )
188
+
189
+
190
+ @follower_url_from_main.for_db("oracle")
191
+ def _oracle_follower_url_from_main(url, ident):
192
+ url = sa_url.make_url(url)
193
+ return url.set(username=ident, password="xe")
194
+
195
+
196
+ @temp_table_keyword_args.for_db("oracle")
197
+ def _oracle_temp_table_keyword_args(cfg, eng):
198
+ return {
199
+ "prefixes": ["GLOBAL TEMPORARY"],
200
+ "oracle_on_commit": "PRESERVE ROWS",
201
+ }
202
+
203
+
204
+ @set_default_schema_on_connection.for_db("oracle")
205
+ def _oracle_set_default_schema_on_connection(
206
+ cfg, dbapi_connection, schema_name
207
+ ):
208
+ cursor = dbapi_connection.cursor()
209
+ cursor.execute("ALTER SESSION SET CURRENT_SCHEMA=%s" % schema_name)
210
+ cursor.close()
211
+
212
+
213
+ @update_db_opts.for_db("oracle")
214
+ def _update_db_opts(db_url, db_opts, options):
215
+ """Set database options (db_opts) for a test database that we created."""
216
+ if (
217
+ options.oracledb_thick_mode
218
+ and sa_url.make_url(db_url).get_driver_name() == "oracledb"
219
+ ):
220
+ db_opts["thick_mode"] = True