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.
- SQLAlchemy-2.0.36.dist-info/LICENSE +19 -0
- SQLAlchemy-2.0.36.dist-info/METADATA +243 -0
- SQLAlchemy-2.0.36.dist-info/RECORD +273 -0
- SQLAlchemy-2.0.36.dist-info/WHEEL +5 -0
- SQLAlchemy-2.0.36.dist-info/top_level.txt +1 -0
- sqlalchemy/__init__.py +294 -0
- sqlalchemy/connectors/__init__.py +18 -0
- sqlalchemy/connectors/aioodbc.py +174 -0
- sqlalchemy/connectors/asyncio.py +213 -0
- sqlalchemy/connectors/pyodbc.py +249 -0
- sqlalchemy/cyextension/__init__.py +6 -0
- sqlalchemy/cyextension/collections.cp313-win32.pyd +0 -0
- sqlalchemy/cyextension/collections.pyx +409 -0
- sqlalchemy/cyextension/immutabledict.cp313-win32.pyd +0 -0
- sqlalchemy/cyextension/immutabledict.pxd +8 -0
- sqlalchemy/cyextension/immutabledict.pyx +133 -0
- sqlalchemy/cyextension/processors.cp313-win32.pyd +0 -0
- sqlalchemy/cyextension/processors.pyx +68 -0
- sqlalchemy/cyextension/resultproxy.cp313-win32.pyd +0 -0
- sqlalchemy/cyextension/resultproxy.pyx +102 -0
- sqlalchemy/cyextension/util.cp313-win32.pyd +0 -0
- sqlalchemy/cyextension/util.pyx +91 -0
- sqlalchemy/dialects/__init__.py +61 -0
- sqlalchemy/dialects/_typing.py +25 -0
- sqlalchemy/dialects/mssql/__init__.py +88 -0
- sqlalchemy/dialects/mssql/aioodbc.py +64 -0
- sqlalchemy/dialects/mssql/base.py +4010 -0
- sqlalchemy/dialects/mssql/information_schema.py +254 -0
- sqlalchemy/dialects/mssql/json.py +133 -0
- sqlalchemy/dialects/mssql/provision.py +162 -0
- sqlalchemy/dialects/mssql/pymssql.py +126 -0
- sqlalchemy/dialects/mssql/pyodbc.py +745 -0
- sqlalchemy/dialects/mysql/__init__.py +101 -0
- sqlalchemy/dialects/mysql/aiomysql.py +333 -0
- sqlalchemy/dialects/mysql/asyncmy.py +337 -0
- sqlalchemy/dialects/mysql/base.py +3494 -0
- sqlalchemy/dialects/mysql/cymysql.py +84 -0
- sqlalchemy/dialects/mysql/dml.py +219 -0
- sqlalchemy/dialects/mysql/enumerated.py +244 -0
- sqlalchemy/dialects/mysql/expression.py +141 -0
- sqlalchemy/dialects/mysql/json.py +81 -0
- sqlalchemy/dialects/mysql/mariadb.py +32 -0
- sqlalchemy/dialects/mysql/mariadbconnector.py +277 -0
- sqlalchemy/dialects/mysql/mysqlconnector.py +180 -0
- sqlalchemy/dialects/mysql/mysqldb.py +303 -0
- sqlalchemy/dialects/mysql/provision.py +110 -0
- sqlalchemy/dialects/mysql/pymysql.py +137 -0
- sqlalchemy/dialects/mysql/pyodbc.py +138 -0
- sqlalchemy/dialects/mysql/reflection.py +677 -0
- sqlalchemy/dialects/mysql/reserved_words.py +571 -0
- sqlalchemy/dialects/mysql/types.py +774 -0
- sqlalchemy/dialects/oracle/__init__.py +67 -0
- sqlalchemy/dialects/oracle/base.py +3271 -0
- sqlalchemy/dialects/oracle/cx_oracle.py +1483 -0
- sqlalchemy/dialects/oracle/dictionary.py +507 -0
- sqlalchemy/dialects/oracle/oracledb.py +431 -0
- sqlalchemy/dialects/oracle/provision.py +220 -0
- sqlalchemy/dialects/oracle/types.py +287 -0
- sqlalchemy/dialects/postgresql/__init__.py +167 -0
- sqlalchemy/dialects/postgresql/_psycopg_common.py +187 -0
- sqlalchemy/dialects/postgresql/array.py +425 -0
- sqlalchemy/dialects/postgresql/asyncpg.py +1274 -0
- sqlalchemy/dialects/postgresql/base.py +5008 -0
- sqlalchemy/dialects/postgresql/dml.py +310 -0
- sqlalchemy/dialects/postgresql/ext.py +496 -0
- sqlalchemy/dialects/postgresql/hstore.py +397 -0
- sqlalchemy/dialects/postgresql/json.py +333 -0
- sqlalchemy/dialects/postgresql/named_types.py +509 -0
- sqlalchemy/dialects/postgresql/operators.py +129 -0
- sqlalchemy/dialects/postgresql/pg8000.py +662 -0
- sqlalchemy/dialects/postgresql/pg_catalog.py +300 -0
- sqlalchemy/dialects/postgresql/provision.py +175 -0
- sqlalchemy/dialects/postgresql/psycopg.py +772 -0
- sqlalchemy/dialects/postgresql/psycopg2.py +886 -0
- sqlalchemy/dialects/postgresql/psycopg2cffi.py +61 -0
- sqlalchemy/dialects/postgresql/ranges.py +1029 -0
- sqlalchemy/dialects/postgresql/types.py +303 -0
- sqlalchemy/dialects/sqlite/__init__.py +57 -0
- sqlalchemy/dialects/sqlite/aiosqlite.py +396 -0
- sqlalchemy/dialects/sqlite/base.py +2805 -0
- sqlalchemy/dialects/sqlite/dml.py +240 -0
- sqlalchemy/dialects/sqlite/json.py +92 -0
- sqlalchemy/dialects/sqlite/provision.py +198 -0
- sqlalchemy/dialects/sqlite/pysqlcipher.py +155 -0
- sqlalchemy/dialects/sqlite/pysqlite.py +756 -0
- sqlalchemy/dialects/type_migration_guidelines.txt +145 -0
- sqlalchemy/engine/__init__.py +62 -0
- sqlalchemy/engine/_py_processors.py +136 -0
- sqlalchemy/engine/_py_row.py +128 -0
- sqlalchemy/engine/_py_util.py +74 -0
- sqlalchemy/engine/base.py +3375 -0
- sqlalchemy/engine/characteristics.py +155 -0
- sqlalchemy/engine/create.py +875 -0
- sqlalchemy/engine/cursor.py +2181 -0
- sqlalchemy/engine/default.py +2365 -0
- sqlalchemy/engine/events.py +951 -0
- sqlalchemy/engine/interfaces.py +3403 -0
- sqlalchemy/engine/mock.py +131 -0
- sqlalchemy/engine/processors.py +61 -0
- sqlalchemy/engine/reflection.py +2098 -0
- sqlalchemy/engine/result.py +2382 -0
- sqlalchemy/engine/row.py +401 -0
- sqlalchemy/engine/strategies.py +19 -0
- sqlalchemy/engine/url.py +910 -0
- sqlalchemy/engine/util.py +167 -0
- sqlalchemy/event/__init__.py +25 -0
- sqlalchemy/event/api.py +225 -0
- sqlalchemy/event/attr.py +655 -0
- sqlalchemy/event/base.py +470 -0
- sqlalchemy/event/legacy.py +246 -0
- sqlalchemy/event/registry.py +386 -0
- sqlalchemy/events.py +17 -0
- sqlalchemy/exc.py +830 -0
- sqlalchemy/ext/__init__.py +11 -0
- sqlalchemy/ext/associationproxy.py +2013 -0
- sqlalchemy/ext/asyncio/__init__.py +25 -0
- sqlalchemy/ext/asyncio/base.py +279 -0
- sqlalchemy/ext/asyncio/engine.py +1466 -0
- sqlalchemy/ext/asyncio/exc.py +21 -0
- sqlalchemy/ext/asyncio/result.py +961 -0
- sqlalchemy/ext/asyncio/scoping.py +1614 -0
- sqlalchemy/ext/asyncio/session.py +1936 -0
- sqlalchemy/ext/automap.py +1691 -0
- sqlalchemy/ext/baked.py +574 -0
- sqlalchemy/ext/compiler.py +570 -0
- sqlalchemy/ext/declarative/__init__.py +65 -0
- sqlalchemy/ext/declarative/extensions.py +548 -0
- sqlalchemy/ext/horizontal_shard.py +481 -0
- sqlalchemy/ext/hybrid.py +1514 -0
- sqlalchemy/ext/indexable.py +341 -0
- sqlalchemy/ext/instrumentation.py +450 -0
- sqlalchemy/ext/mutable.py +1073 -0
- sqlalchemy/ext/mypy/__init__.py +6 -0
- sqlalchemy/ext/mypy/apply.py +320 -0
- sqlalchemy/ext/mypy/decl_class.py +515 -0
- sqlalchemy/ext/mypy/infer.py +590 -0
- sqlalchemy/ext/mypy/names.py +335 -0
- sqlalchemy/ext/mypy/plugin.py +303 -0
- sqlalchemy/ext/mypy/util.py +357 -0
- sqlalchemy/ext/orderinglist.py +416 -0
- sqlalchemy/ext/serializer.py +181 -0
- sqlalchemy/future/__init__.py +16 -0
- sqlalchemy/future/engine.py +15 -0
- sqlalchemy/inspection.py +174 -0
- sqlalchemy/log.py +288 -0
- sqlalchemy/orm/__init__.py +170 -0
- sqlalchemy/orm/_orm_constructors.py +2571 -0
- sqlalchemy/orm/_typing.py +179 -0
- sqlalchemy/orm/attributes.py +2835 -0
- sqlalchemy/orm/base.py +973 -0
- sqlalchemy/orm/bulk_persistence.py +2123 -0
- sqlalchemy/orm/clsregistry.py +571 -0
- sqlalchemy/orm/collections.py +1620 -0
- sqlalchemy/orm/context.py +3268 -0
- sqlalchemy/orm/decl_api.py +1883 -0
- sqlalchemy/orm/decl_base.py +2190 -0
- sqlalchemy/orm/dependency.py +1304 -0
- sqlalchemy/orm/descriptor_props.py +1076 -0
- sqlalchemy/orm/dynamic.py +300 -0
- sqlalchemy/orm/evaluator.py +379 -0
- sqlalchemy/orm/events.py +3261 -0
- sqlalchemy/orm/exc.py +228 -0
- sqlalchemy/orm/identity.py +302 -0
- sqlalchemy/orm/instrumentation.py +754 -0
- sqlalchemy/orm/interfaces.py +1474 -0
- sqlalchemy/orm/loading.py +1682 -0
- sqlalchemy/orm/mapped_collection.py +557 -0
- sqlalchemy/orm/mapper.py +4432 -0
- sqlalchemy/orm/path_registry.py +811 -0
- sqlalchemy/orm/persistence.py +1782 -0
- sqlalchemy/orm/properties.py +886 -0
- sqlalchemy/orm/query.py +3396 -0
- sqlalchemy/orm/relationships.py +3500 -0
- sqlalchemy/orm/scoping.py +2165 -0
- sqlalchemy/orm/session.py +5301 -0
- sqlalchemy/orm/state.py +1143 -0
- sqlalchemy/orm/state_changes.py +198 -0
- sqlalchemy/orm/strategies.py +3473 -0
- sqlalchemy/orm/strategy_options.py +2569 -0
- sqlalchemy/orm/sync.py +164 -0
- sqlalchemy/orm/unitofwork.py +796 -0
- sqlalchemy/orm/util.py +2424 -0
- sqlalchemy/orm/writeonly.py +678 -0
- sqlalchemy/pool/__init__.py +44 -0
- sqlalchemy/pool/base.py +1515 -0
- sqlalchemy/pool/events.py +370 -0
- sqlalchemy/pool/impl.py +581 -0
- sqlalchemy/py.typed +0 -0
- sqlalchemy/schema.py +70 -0
- sqlalchemy/sql/__init__.py +145 -0
- sqlalchemy/sql/_dml_constructors.py +140 -0
- sqlalchemy/sql/_elements_constructors.py +1850 -0
- sqlalchemy/sql/_orm_types.py +20 -0
- sqlalchemy/sql/_py_util.py +75 -0
- sqlalchemy/sql/_selectable_constructors.py +635 -0
- sqlalchemy/sql/_typing.py +460 -0
- sqlalchemy/sql/annotation.py +585 -0
- sqlalchemy/sql/base.py +2185 -0
- sqlalchemy/sql/cache_key.py +1057 -0
- sqlalchemy/sql/coercions.py +1405 -0
- sqlalchemy/sql/compiler.py +7818 -0
- sqlalchemy/sql/crud.py +1669 -0
- sqlalchemy/sql/ddl.py +1378 -0
- sqlalchemy/sql/default_comparator.py +552 -0
- sqlalchemy/sql/dml.py +1817 -0
- sqlalchemy/sql/elements.py +5499 -0
- sqlalchemy/sql/events.py +455 -0
- sqlalchemy/sql/expression.py +162 -0
- sqlalchemy/sql/functions.py +2055 -0
- sqlalchemy/sql/lambdas.py +1449 -0
- sqlalchemy/sql/naming.py +212 -0
- sqlalchemy/sql/operators.py +2579 -0
- sqlalchemy/sql/roles.py +323 -0
- sqlalchemy/sql/schema.py +6158 -0
- sqlalchemy/sql/selectable.py +7004 -0
- sqlalchemy/sql/sqltypes.py +3827 -0
- sqlalchemy/sql/traversals.py +1024 -0
- sqlalchemy/sql/type_api.py +2339 -0
- sqlalchemy/sql/util.py +1486 -0
- sqlalchemy/sql/visitors.py +1165 -0
- sqlalchemy/testing/__init__.py +96 -0
- sqlalchemy/testing/assertions.py +989 -0
- sqlalchemy/testing/assertsql.py +516 -0
- sqlalchemy/testing/asyncio.py +135 -0
- sqlalchemy/testing/config.py +427 -0
- sqlalchemy/testing/engines.py +472 -0
- sqlalchemy/testing/entities.py +117 -0
- sqlalchemy/testing/exclusions.py +435 -0
- sqlalchemy/testing/fixtures/__init__.py +28 -0
- sqlalchemy/testing/fixtures/base.py +366 -0
- sqlalchemy/testing/fixtures/mypy.py +312 -0
- sqlalchemy/testing/fixtures/orm.py +227 -0
- sqlalchemy/testing/fixtures/sql.py +503 -0
- sqlalchemy/testing/pickleable.py +155 -0
- sqlalchemy/testing/plugin/__init__.py +6 -0
- sqlalchemy/testing/plugin/bootstrap.py +51 -0
- sqlalchemy/testing/plugin/plugin_base.py +779 -0
- sqlalchemy/testing/plugin/pytestplugin.py +868 -0
- sqlalchemy/testing/profiling.py +324 -0
- sqlalchemy/testing/provision.py +496 -0
- sqlalchemy/testing/requirements.py +1818 -0
- sqlalchemy/testing/schema.py +224 -0
- sqlalchemy/testing/suite/__init__.py +19 -0
- sqlalchemy/testing/suite/test_cte.py +211 -0
- sqlalchemy/testing/suite/test_ddl.py +389 -0
- sqlalchemy/testing/suite/test_deprecations.py +153 -0
- sqlalchemy/testing/suite/test_dialect.py +740 -0
- sqlalchemy/testing/suite/test_insert.py +630 -0
- sqlalchemy/testing/suite/test_reflection.py +3225 -0
- sqlalchemy/testing/suite/test_results.py +502 -0
- sqlalchemy/testing/suite/test_rowcount.py +258 -0
- sqlalchemy/testing/suite/test_select.py +1999 -0
- sqlalchemy/testing/suite/test_sequence.py +317 -0
- sqlalchemy/testing/suite/test_types.py +2141 -0
- sqlalchemy/testing/suite/test_unicode_ddl.py +189 -0
- sqlalchemy/testing/suite/test_update_delete.py +139 -0
- sqlalchemy/testing/util.py +537 -0
- sqlalchemy/testing/warnings.py +52 -0
- sqlalchemy/types.py +76 -0
- sqlalchemy/util/__init__.py +160 -0
- sqlalchemy/util/_collections.py +715 -0
- sqlalchemy/util/_concurrency_py3k.py +288 -0
- sqlalchemy/util/_has_cy.py +40 -0
- sqlalchemy/util/_py_collections.py +541 -0
- sqlalchemy/util/compat.py +301 -0
- sqlalchemy/util/concurrency.py +108 -0
- sqlalchemy/util/deprecations.py +401 -0
- sqlalchemy/util/langhelpers.py +2218 -0
- sqlalchemy/util/preloaded.py +150 -0
- sqlalchemy/util/queue.py +322 -0
- sqlalchemy/util/tool_support.py +201 -0
- sqlalchemy/util/topological.py +120 -0
- 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
|