sqlspec 0.25.0__py3-none-any.whl → 0.27.0__py3-none-any.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.
Potentially problematic release.
This version of sqlspec might be problematic. Click here for more details.
- sqlspec/__init__.py +7 -15
- sqlspec/_serialization.py +256 -24
- sqlspec/_typing.py +71 -52
- sqlspec/adapters/adbc/_types.py +1 -1
- sqlspec/adapters/adbc/adk/__init__.py +5 -0
- sqlspec/adapters/adbc/adk/store.py +870 -0
- sqlspec/adapters/adbc/config.py +69 -12
- sqlspec/adapters/adbc/data_dictionary.py +340 -0
- sqlspec/adapters/adbc/driver.py +266 -58
- sqlspec/adapters/adbc/litestar/__init__.py +5 -0
- sqlspec/adapters/adbc/litestar/store.py +504 -0
- sqlspec/adapters/adbc/type_converter.py +153 -0
- sqlspec/adapters/aiosqlite/_types.py +1 -1
- sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/adk/store.py +527 -0
- sqlspec/adapters/aiosqlite/config.py +88 -15
- sqlspec/adapters/aiosqlite/data_dictionary.py +149 -0
- sqlspec/adapters/aiosqlite/driver.py +143 -40
- sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
- sqlspec/adapters/aiosqlite/pool.py +7 -7
- sqlspec/adapters/asyncmy/__init__.py +7 -1
- sqlspec/adapters/asyncmy/_types.py +2 -2
- sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
- sqlspec/adapters/asyncmy/adk/store.py +493 -0
- sqlspec/adapters/asyncmy/config.py +68 -23
- sqlspec/adapters/asyncmy/data_dictionary.py +161 -0
- sqlspec/adapters/asyncmy/driver.py +313 -58
- sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncmy/litestar/store.py +296 -0
- sqlspec/adapters/asyncpg/__init__.py +2 -1
- sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
- sqlspec/adapters/asyncpg/_types.py +11 -7
- sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
- sqlspec/adapters/asyncpg/adk/store.py +450 -0
- sqlspec/adapters/asyncpg/config.py +59 -35
- sqlspec/adapters/asyncpg/data_dictionary.py +173 -0
- sqlspec/adapters/asyncpg/driver.py +170 -25
- sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
- sqlspec/adapters/asyncpg/litestar/store.py +253 -0
- sqlspec/adapters/bigquery/_types.py +1 -1
- sqlspec/adapters/bigquery/adk/__init__.py +5 -0
- sqlspec/adapters/bigquery/adk/store.py +576 -0
- sqlspec/adapters/bigquery/config.py +27 -10
- sqlspec/adapters/bigquery/data_dictionary.py +149 -0
- sqlspec/adapters/bigquery/driver.py +368 -142
- sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
- sqlspec/adapters/bigquery/litestar/store.py +327 -0
- sqlspec/adapters/bigquery/type_converter.py +125 -0
- sqlspec/adapters/duckdb/_types.py +1 -1
- sqlspec/adapters/duckdb/adk/__init__.py +14 -0
- sqlspec/adapters/duckdb/adk/store.py +553 -0
- sqlspec/adapters/duckdb/config.py +80 -20
- sqlspec/adapters/duckdb/data_dictionary.py +163 -0
- sqlspec/adapters/duckdb/driver.py +167 -45
- sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
- sqlspec/adapters/duckdb/litestar/store.py +332 -0
- sqlspec/adapters/duckdb/pool.py +4 -4
- sqlspec/adapters/duckdb/type_converter.py +133 -0
- sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
- sqlspec/adapters/oracledb/_types.py +20 -2
- sqlspec/adapters/oracledb/adk/__init__.py +5 -0
- sqlspec/adapters/oracledb/adk/store.py +1745 -0
- sqlspec/adapters/oracledb/config.py +122 -32
- sqlspec/adapters/oracledb/data_dictionary.py +509 -0
- sqlspec/adapters/oracledb/driver.py +353 -91
- sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
- sqlspec/adapters/oracledb/litestar/store.py +767 -0
- sqlspec/adapters/oracledb/migrations.py +348 -73
- sqlspec/adapters/oracledb/type_converter.py +207 -0
- sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
- sqlspec/adapters/psqlpy/_types.py +2 -1
- sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
- sqlspec/adapters/psqlpy/adk/store.py +482 -0
- sqlspec/adapters/psqlpy/config.py +46 -17
- sqlspec/adapters/psqlpy/data_dictionary.py +172 -0
- sqlspec/adapters/psqlpy/driver.py +123 -209
- sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
- sqlspec/adapters/psqlpy/litestar/store.py +272 -0
- sqlspec/adapters/psqlpy/type_converter.py +102 -0
- sqlspec/adapters/psycopg/_type_handlers.py +80 -0
- sqlspec/adapters/psycopg/_types.py +2 -1
- sqlspec/adapters/psycopg/adk/__init__.py +5 -0
- sqlspec/adapters/psycopg/adk/store.py +944 -0
- sqlspec/adapters/psycopg/config.py +69 -35
- sqlspec/adapters/psycopg/data_dictionary.py +331 -0
- sqlspec/adapters/psycopg/driver.py +238 -81
- sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
- sqlspec/adapters/psycopg/litestar/store.py +554 -0
- sqlspec/adapters/sqlite/__init__.py +2 -1
- sqlspec/adapters/sqlite/_type_handlers.py +86 -0
- sqlspec/adapters/sqlite/_types.py +1 -1
- sqlspec/adapters/sqlite/adk/__init__.py +5 -0
- sqlspec/adapters/sqlite/adk/store.py +572 -0
- sqlspec/adapters/sqlite/config.py +87 -15
- sqlspec/adapters/sqlite/data_dictionary.py +149 -0
- sqlspec/adapters/sqlite/driver.py +137 -54
- sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/sqlite/litestar/store.py +318 -0
- sqlspec/adapters/sqlite/pool.py +18 -9
- sqlspec/base.py +45 -26
- sqlspec/builder/__init__.py +73 -4
- sqlspec/builder/_base.py +162 -89
- sqlspec/builder/_column.py +62 -29
- sqlspec/builder/_ddl.py +180 -121
- sqlspec/builder/_delete.py +5 -4
- sqlspec/builder/_dml.py +388 -0
- sqlspec/{_sql.py → builder/_factory.py} +53 -94
- sqlspec/builder/_insert.py +32 -131
- sqlspec/builder/_join.py +375 -0
- sqlspec/builder/_merge.py +446 -11
- sqlspec/builder/_parsing_utils.py +111 -17
- sqlspec/builder/_select.py +1457 -24
- sqlspec/builder/_update.py +11 -42
- sqlspec/cli.py +307 -194
- sqlspec/config.py +252 -67
- sqlspec/core/__init__.py +5 -4
- sqlspec/core/cache.py +17 -17
- sqlspec/core/compiler.py +62 -9
- sqlspec/core/filters.py +37 -37
- sqlspec/core/hashing.py +9 -9
- sqlspec/core/parameters.py +83 -48
- sqlspec/core/result.py +102 -46
- sqlspec/core/splitter.py +16 -17
- sqlspec/core/statement.py +36 -30
- sqlspec/core/type_conversion.py +235 -0
- sqlspec/driver/__init__.py +7 -6
- sqlspec/driver/_async.py +188 -151
- sqlspec/driver/_common.py +285 -80
- sqlspec/driver/_sync.py +188 -152
- sqlspec/driver/mixins/_result_tools.py +20 -236
- sqlspec/driver/mixins/_sql_translator.py +4 -4
- sqlspec/exceptions.py +75 -7
- sqlspec/extensions/adk/__init__.py +53 -0
- sqlspec/extensions/adk/_types.py +51 -0
- sqlspec/extensions/adk/converters.py +172 -0
- sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
- sqlspec/extensions/adk/migrations/__init__.py +0 -0
- sqlspec/extensions/adk/service.py +181 -0
- sqlspec/extensions/adk/store.py +536 -0
- sqlspec/extensions/aiosql/adapter.py +73 -53
- sqlspec/extensions/litestar/__init__.py +21 -4
- sqlspec/extensions/litestar/cli.py +54 -10
- sqlspec/extensions/litestar/config.py +59 -266
- sqlspec/extensions/litestar/handlers.py +46 -17
- sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
- sqlspec/extensions/litestar/migrations/__init__.py +3 -0
- sqlspec/extensions/litestar/plugin.py +324 -223
- sqlspec/extensions/litestar/providers.py +25 -25
- sqlspec/extensions/litestar/store.py +265 -0
- sqlspec/loader.py +30 -49
- sqlspec/migrations/__init__.py +4 -3
- sqlspec/migrations/base.py +302 -39
- sqlspec/migrations/commands.py +611 -144
- sqlspec/migrations/context.py +142 -0
- sqlspec/migrations/fix.py +199 -0
- sqlspec/migrations/loaders.py +68 -23
- sqlspec/migrations/runner.py +543 -107
- sqlspec/migrations/tracker.py +237 -21
- sqlspec/migrations/utils.py +51 -3
- sqlspec/migrations/validation.py +177 -0
- sqlspec/protocols.py +66 -36
- sqlspec/storage/_utils.py +98 -0
- sqlspec/storage/backends/fsspec.py +134 -106
- sqlspec/storage/backends/local.py +78 -51
- sqlspec/storage/backends/obstore.py +278 -162
- sqlspec/storage/registry.py +75 -39
- sqlspec/typing.py +16 -84
- sqlspec/utils/config_resolver.py +153 -0
- sqlspec/utils/correlation.py +4 -5
- sqlspec/utils/data_transformation.py +3 -2
- sqlspec/utils/deprecation.py +9 -8
- sqlspec/utils/fixtures.py +4 -4
- sqlspec/utils/logging.py +46 -6
- sqlspec/utils/module_loader.py +2 -2
- sqlspec/utils/schema.py +288 -0
- sqlspec/utils/serializers.py +50 -2
- sqlspec/utils/sync_tools.py +21 -17
- sqlspec/utils/text.py +1 -2
- sqlspec/utils/type_guards.py +111 -20
- sqlspec/utils/version.py +433 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
- sqlspec-0.27.0.dist-info/RECORD +207 -0
- sqlspec/builder/mixins/__init__.py +0 -55
- sqlspec/builder/mixins/_cte_and_set_ops.py +0 -254
- sqlspec/builder/mixins/_delete_operations.py +0 -50
- sqlspec/builder/mixins/_insert_operations.py +0 -282
- sqlspec/builder/mixins/_join_operations.py +0 -389
- sqlspec/builder/mixins/_merge_operations.py +0 -592
- sqlspec/builder/mixins/_order_limit_operations.py +0 -152
- sqlspec/builder/mixins/_pivot_operations.py +0 -157
- sqlspec/builder/mixins/_select_operations.py +0 -936
- sqlspec/builder/mixins/_update_operations.py +0 -218
- sqlspec/builder/mixins/_where_clause.py +0 -1304
- sqlspec-0.25.0.dist-info/RECORD +0 -139
- sqlspec-0.25.0.dist-info/licenses/NOTICE +0 -29
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
"""Oracle-specific data dictionary for metadata queries."""
|
|
2
|
+
# cspell:ignore pdbs
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
from contextlib import suppress
|
|
6
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
7
|
+
|
|
8
|
+
from sqlspec.driver import (
|
|
9
|
+
AsyncDataDictionaryBase,
|
|
10
|
+
AsyncDriverAdapterBase,
|
|
11
|
+
SyncDataDictionaryBase,
|
|
12
|
+
SyncDriverAdapterBase,
|
|
13
|
+
VersionInfo,
|
|
14
|
+
)
|
|
15
|
+
from sqlspec.utils.logging import get_logger
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from collections.abc import Callable
|
|
19
|
+
|
|
20
|
+
from sqlspec.adapters.oracledb.driver import OracleAsyncDriver, OracleSyncDriver
|
|
21
|
+
|
|
22
|
+
logger = get_logger("adapters.oracledb.data_dictionary")
|
|
23
|
+
|
|
24
|
+
# Oracle version constants
|
|
25
|
+
ORACLE_MIN_JSON_NATIVE_VERSION = 21
|
|
26
|
+
ORACLE_MIN_JSON_NATIVE_COMPATIBLE = 20
|
|
27
|
+
ORACLE_MIN_JSON_BLOB_VERSION = 12
|
|
28
|
+
ORACLE_MIN_OSON_VERSION = 19
|
|
29
|
+
|
|
30
|
+
# Compiled regex patterns
|
|
31
|
+
ORACLE_VERSION_PATTERN = re.compile(r"Oracle Database (\d+)c?.* Release (\d+)\.(\d+)\.(\d+)")
|
|
32
|
+
|
|
33
|
+
__all__ = ("OracleAsyncDataDictionary", "OracleSyncDataDictionary", "OracleVersionInfo")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class OracleVersionInfo(VersionInfo):
|
|
37
|
+
"""Oracle database version information."""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self, major: int, minor: int = 0, patch: int = 0, compatible: "str | None" = None, is_autonomous: bool = False
|
|
41
|
+
) -> None:
|
|
42
|
+
"""Initialize Oracle version info.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
major: Major version number (e.g., 19, 21, 23)
|
|
46
|
+
minor: Minor version number
|
|
47
|
+
patch: Patch version number
|
|
48
|
+
compatible: Compatible parameter value
|
|
49
|
+
is_autonomous: Whether this is an Autonomous Database
|
|
50
|
+
"""
|
|
51
|
+
super().__init__(major, minor, patch)
|
|
52
|
+
self.compatible = compatible
|
|
53
|
+
self.is_autonomous = is_autonomous
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def compatible_major(self) -> "int | None":
|
|
57
|
+
"""Get major version from compatible parameter."""
|
|
58
|
+
if not self.compatible:
|
|
59
|
+
return None
|
|
60
|
+
parts = self.compatible.split(".")
|
|
61
|
+
if not parts:
|
|
62
|
+
return None
|
|
63
|
+
return int(parts[0])
|
|
64
|
+
|
|
65
|
+
def supports_native_json(self) -> bool:
|
|
66
|
+
"""Check if database supports native JSON data type.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
True if Oracle 21c+ with compatible >= 20
|
|
70
|
+
"""
|
|
71
|
+
return (
|
|
72
|
+
self.major >= ORACLE_MIN_JSON_NATIVE_VERSION
|
|
73
|
+
and (self.compatible_major or 0) >= ORACLE_MIN_JSON_NATIVE_COMPATIBLE
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def supports_oson_blob(self) -> bool:
|
|
77
|
+
"""Check if database supports BLOB with OSON format.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
True if Oracle 19c+ (Autonomous) or 21c+
|
|
81
|
+
"""
|
|
82
|
+
if self.major >= ORACLE_MIN_JSON_NATIVE_VERSION:
|
|
83
|
+
return True
|
|
84
|
+
return self.major >= ORACLE_MIN_OSON_VERSION and self.is_autonomous
|
|
85
|
+
|
|
86
|
+
def supports_json_blob(self) -> bool:
|
|
87
|
+
"""Check if database supports BLOB with JSON validation.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
True if Oracle 12c+
|
|
91
|
+
"""
|
|
92
|
+
return self.major >= ORACLE_MIN_JSON_BLOB_VERSION
|
|
93
|
+
|
|
94
|
+
def __str__(self) -> str:
|
|
95
|
+
"""String representation of version info."""
|
|
96
|
+
version_str = f"{self.major}.{self.minor}.{self.patch}"
|
|
97
|
+
if self.compatible:
|
|
98
|
+
version_str += f" (compatible={self.compatible})"
|
|
99
|
+
if self.is_autonomous:
|
|
100
|
+
version_str += " [Autonomous]"
|
|
101
|
+
return version_str
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class OracleDataDictionaryMixin:
|
|
105
|
+
"""Mixin providing Oracle-specific metadata queries."""
|
|
106
|
+
|
|
107
|
+
__slots__ = ()
|
|
108
|
+
|
|
109
|
+
def _get_columns_sql(self, table: str, schema: "str | None" = None) -> str:
|
|
110
|
+
"""Get SQL to query column metadata from Oracle data dictionary.
|
|
111
|
+
|
|
112
|
+
Uses USER_TAB_COLUMNS which returns column names in UPPERCASE.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
table: Table name to query columns for
|
|
116
|
+
schema: Schema name (unused for USER_TAB_COLUMNS)
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
SQL string for Oracle's USER_TAB_COLUMNS query
|
|
120
|
+
"""
|
|
121
|
+
_ = schema
|
|
122
|
+
return f"""
|
|
123
|
+
SELECT
|
|
124
|
+
column_name AS "column_name",
|
|
125
|
+
data_type AS "data_type",
|
|
126
|
+
data_length AS "data_length",
|
|
127
|
+
nullable AS "nullable"
|
|
128
|
+
FROM user_tab_columns
|
|
129
|
+
WHERE table_name = '{table.upper()}'
|
|
130
|
+
ORDER BY column_id
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
def _get_oracle_version(self, driver: "OracleAsyncDriver | OracleSyncDriver") -> "OracleVersionInfo | None":
|
|
134
|
+
"""Get Oracle database version information.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
driver: Database driver instance
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Oracle version information or None if detection fails
|
|
141
|
+
"""
|
|
142
|
+
banner = driver.select_value("SELECT banner AS \"banner\" FROM v$version WHERE banner LIKE 'Oracle%'")
|
|
143
|
+
|
|
144
|
+
# Parse version from banner like "Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Production"
|
|
145
|
+
# or "Oracle Database 19c Standard Edition 2 Release 19.0.0.0.0 - Production"
|
|
146
|
+
version_match = ORACLE_VERSION_PATTERN.search(str(banner))
|
|
147
|
+
|
|
148
|
+
if not version_match:
|
|
149
|
+
logger.warning("Could not parse Oracle version from banner: %s", banner)
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
major = int(version_match.group(1))
|
|
153
|
+
release_major = int(version_match.group(2))
|
|
154
|
+
minor = int(version_match.group(3))
|
|
155
|
+
patch = int(version_match.group(4))
|
|
156
|
+
|
|
157
|
+
# For Oracle 21c+, the major version is in the first group
|
|
158
|
+
# For Oracle 19c and earlier, use the release version
|
|
159
|
+
if major >= ORACLE_MIN_JSON_NATIVE_VERSION:
|
|
160
|
+
version_info = OracleVersionInfo(major, minor, patch)
|
|
161
|
+
else:
|
|
162
|
+
version_info = OracleVersionInfo(release_major, minor, patch)
|
|
163
|
+
|
|
164
|
+
logger.debug("Detected Oracle version: %s", version_info)
|
|
165
|
+
return version_info
|
|
166
|
+
|
|
167
|
+
def _get_oracle_compatible(self, driver: "OracleAsyncDriver | OracleSyncDriver") -> "str | None":
|
|
168
|
+
"""Get Oracle compatible parameter value.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
driver: Database driver instance
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Compatible parameter value or None if detection fails
|
|
175
|
+
"""
|
|
176
|
+
try:
|
|
177
|
+
compatible = driver.select_value("SELECT value AS \"value\" FROM v$parameter WHERE name = 'compatible'")
|
|
178
|
+
logger.debug("Detected Oracle compatible parameter: %s", compatible)
|
|
179
|
+
return str(compatible)
|
|
180
|
+
except Exception:
|
|
181
|
+
logger.warning("Compatible parameter not found")
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
def _get_oracle_json_type(self, version_info: "OracleVersionInfo | None") -> str:
|
|
185
|
+
"""Determine the appropriate JSON column type for Oracle.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
version_info: Oracle version information
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Appropriate Oracle column type for JSON data
|
|
192
|
+
"""
|
|
193
|
+
if not version_info:
|
|
194
|
+
logger.warning("No version info provided, using CLOB fallback")
|
|
195
|
+
return "CLOB"
|
|
196
|
+
|
|
197
|
+
# Decision matrix for JSON column type
|
|
198
|
+
if version_info.supports_native_json():
|
|
199
|
+
logger.info("Using native JSON type for Oracle %s", version_info)
|
|
200
|
+
return "JSON"
|
|
201
|
+
if version_info.supports_oson_blob():
|
|
202
|
+
logger.info("Using BLOB with OSON format for Oracle %s", version_info)
|
|
203
|
+
return "BLOB CHECK (data IS JSON FORMAT OSON)"
|
|
204
|
+
if version_info.supports_json_blob():
|
|
205
|
+
logger.info("Using BLOB with JSON validation for Oracle %s", version_info)
|
|
206
|
+
return "BLOB CHECK (data IS JSON)"
|
|
207
|
+
logger.info("Using CLOB fallback for Oracle %s", version_info)
|
|
208
|
+
return "CLOB"
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class OracleSyncDataDictionary(OracleDataDictionaryMixin, SyncDataDictionaryBase):
|
|
212
|
+
"""Oracle-specific sync data dictionary."""
|
|
213
|
+
|
|
214
|
+
def _is_oracle_autonomous(self, driver: "OracleSyncDriver") -> bool:
|
|
215
|
+
"""Check if this is an Oracle Autonomous Database.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
driver: Database driver instance
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
True if this is an Autonomous Database, False otherwise
|
|
222
|
+
"""
|
|
223
|
+
result = driver.select_value_or_none('SELECT COUNT(1) AS "cnt" FROM v$pdbs WHERE cloud_identity IS NOT NULL')
|
|
224
|
+
return bool(result and int(result) > 0)
|
|
225
|
+
|
|
226
|
+
def get_version(self, driver: SyncDriverAdapterBase) -> "OracleVersionInfo | None":
|
|
227
|
+
"""Get Oracle database version information.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
driver: Database driver instance
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Oracle version information or None if detection fails
|
|
234
|
+
"""
|
|
235
|
+
oracle_driver = cast("OracleSyncDriver", driver)
|
|
236
|
+
version_info = self._get_oracle_version(oracle_driver)
|
|
237
|
+
if version_info:
|
|
238
|
+
# Enhance with additional information
|
|
239
|
+
compatible = self._get_oracle_compatible(oracle_driver)
|
|
240
|
+
is_autonomous = self._is_oracle_autonomous(oracle_driver)
|
|
241
|
+
|
|
242
|
+
version_info.compatible = compatible
|
|
243
|
+
version_info.is_autonomous = is_autonomous
|
|
244
|
+
|
|
245
|
+
return version_info
|
|
246
|
+
|
|
247
|
+
def get_feature_flag(self, driver: SyncDriverAdapterBase, feature: str) -> bool:
|
|
248
|
+
"""Check if Oracle database supports a specific feature.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
driver: Database driver instance
|
|
252
|
+
feature: Feature name to check
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
True if feature is supported, False otherwise
|
|
256
|
+
"""
|
|
257
|
+
if feature == "is_autonomous":
|
|
258
|
+
return self._is_oracle_autonomous(cast("OracleSyncDriver", driver))
|
|
259
|
+
|
|
260
|
+
version_info = self.get_version(driver)
|
|
261
|
+
if not version_info:
|
|
262
|
+
return False
|
|
263
|
+
|
|
264
|
+
feature_checks: dict[str, Callable[..., bool]] = {
|
|
265
|
+
"supports_native_json": version_info.supports_native_json,
|
|
266
|
+
"supports_oson_blob": version_info.supports_oson_blob,
|
|
267
|
+
"supports_json_blob": version_info.supports_json_blob,
|
|
268
|
+
"supports_json": version_info.supports_json_blob, # Any JSON support
|
|
269
|
+
"supports_transactions": lambda: True,
|
|
270
|
+
"supports_prepared_statements": lambda: True,
|
|
271
|
+
"supports_schemas": lambda: True,
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if feature in feature_checks:
|
|
275
|
+
return bool(feature_checks[feature]())
|
|
276
|
+
|
|
277
|
+
return False
|
|
278
|
+
|
|
279
|
+
def get_optimal_type(self, driver: SyncDriverAdapterBase, type_category: str) -> str:
|
|
280
|
+
"""Get optimal Oracle type for a category.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
driver: Database driver instance
|
|
284
|
+
type_category: Type category
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Oracle-specific type name
|
|
288
|
+
"""
|
|
289
|
+
type_map = {
|
|
290
|
+
"json": self._get_oracle_json_type(self.get_version(driver)),
|
|
291
|
+
"uuid": "RAW(16)",
|
|
292
|
+
"boolean": "NUMBER(1)",
|
|
293
|
+
"timestamp": "TIMESTAMP",
|
|
294
|
+
"text": "CLOB",
|
|
295
|
+
"blob": "BLOB",
|
|
296
|
+
}
|
|
297
|
+
return type_map.get(type_category, "VARCHAR2(255)")
|
|
298
|
+
|
|
299
|
+
def get_columns(
|
|
300
|
+
self, driver: SyncDriverAdapterBase, table: str, schema: "str | None" = None
|
|
301
|
+
) -> "list[dict[str, Any]]":
|
|
302
|
+
"""Get column information for a table from Oracle data dictionary.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
driver: Database driver instance
|
|
306
|
+
table: Table name to query columns for
|
|
307
|
+
schema: Schema name (ignored for USER_TAB_COLUMNS)
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
List of column metadata dictionaries with keys:
|
|
311
|
+
- column_name: Name of the column
|
|
312
|
+
- data_type: Oracle data type
|
|
313
|
+
- data_length: Maximum length (for character types)
|
|
314
|
+
- nullable: 'Y' or 'N'
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
oracle_driver = cast("OracleSyncDriver", driver)
|
|
318
|
+
result = oracle_driver.execute(self._get_columns_sql(table, schema))
|
|
319
|
+
return result.get_data()
|
|
320
|
+
|
|
321
|
+
def list_available_features(self) -> "list[str]":
|
|
322
|
+
"""List available Oracle feature flags.
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
List of supported feature names
|
|
326
|
+
"""
|
|
327
|
+
return [
|
|
328
|
+
"is_autonomous",
|
|
329
|
+
"supports_native_json",
|
|
330
|
+
"supports_oson_blob",
|
|
331
|
+
"supports_json_blob",
|
|
332
|
+
"supports_json",
|
|
333
|
+
"supports_transactions",
|
|
334
|
+
"supports_prepared_statements",
|
|
335
|
+
"supports_schemas",
|
|
336
|
+
]
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
class OracleAsyncDataDictionary(OracleDataDictionaryMixin, AsyncDataDictionaryBase):
|
|
340
|
+
"""Oracle-specific async data dictionary."""
|
|
341
|
+
|
|
342
|
+
async def get_version(self, driver: AsyncDriverAdapterBase) -> "OracleVersionInfo | None":
|
|
343
|
+
"""Get Oracle database version information.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
driver: Async database driver instance
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
Oracle version information or None if detection fails
|
|
350
|
+
"""
|
|
351
|
+
banner = await cast("OracleAsyncDriver", driver).select_value(
|
|
352
|
+
"SELECT banner AS \"banner\" FROM v$version WHERE banner LIKE 'Oracle%'"
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
version_match = ORACLE_VERSION_PATTERN.search(str(banner))
|
|
356
|
+
|
|
357
|
+
if not version_match:
|
|
358
|
+
logger.warning("Could not parse Oracle version from banner: %s", banner)
|
|
359
|
+
return None
|
|
360
|
+
|
|
361
|
+
major = int(version_match.group(1))
|
|
362
|
+
release_major = int(version_match.group(2))
|
|
363
|
+
minor = int(version_match.group(3))
|
|
364
|
+
patch = int(version_match.group(4))
|
|
365
|
+
|
|
366
|
+
if major >= ORACLE_MIN_JSON_NATIVE_VERSION:
|
|
367
|
+
version_info = OracleVersionInfo(major, minor, patch)
|
|
368
|
+
else:
|
|
369
|
+
version_info = OracleVersionInfo(release_major, minor, patch)
|
|
370
|
+
|
|
371
|
+
# Enhance with additional information
|
|
372
|
+
oracle_driver = cast("OracleAsyncDriver", driver)
|
|
373
|
+
compatible = await self._get_oracle_compatible_async(oracle_driver)
|
|
374
|
+
is_autonomous = await self._is_oracle_autonomous_async(oracle_driver)
|
|
375
|
+
|
|
376
|
+
version_info.compatible = compatible
|
|
377
|
+
version_info.is_autonomous = is_autonomous
|
|
378
|
+
|
|
379
|
+
logger.debug("Detected Oracle version: %s", version_info)
|
|
380
|
+
return version_info
|
|
381
|
+
|
|
382
|
+
async def _get_oracle_compatible_async(self, driver: "OracleAsyncDriver") -> "str | None":
|
|
383
|
+
"""Get Oracle compatible parameter value (async version).
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
driver: Async database driver instance
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
Compatible parameter value or None if detection fails
|
|
390
|
+
"""
|
|
391
|
+
try:
|
|
392
|
+
compatible = await driver.select_value(
|
|
393
|
+
"SELECT value AS \"value\" FROM v$parameter WHERE name = 'compatible'"
|
|
394
|
+
)
|
|
395
|
+
logger.debug("Detected Oracle compatible parameter: %s", compatible)
|
|
396
|
+
return str(compatible)
|
|
397
|
+
except Exception:
|
|
398
|
+
logger.warning("Compatible parameter not found")
|
|
399
|
+
return None
|
|
400
|
+
|
|
401
|
+
async def _is_oracle_autonomous_async(self, driver: "OracleAsyncDriver") -> bool:
|
|
402
|
+
"""Check if this is an Oracle Autonomous Database (async version).
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
driver: Async database driver instance
|
|
406
|
+
|
|
407
|
+
Returns:
|
|
408
|
+
True if this is an Autonomous Database, False otherwise
|
|
409
|
+
"""
|
|
410
|
+
# Check for cloud_identity in v$pdbs (most reliable for Autonomous)
|
|
411
|
+
with suppress(Exception):
|
|
412
|
+
result = await driver.execute('SELECT COUNT(1) AS "cnt" FROM v$pdbs WHERE cloud_identity IS NOT NULL')
|
|
413
|
+
if result.data:
|
|
414
|
+
count = result.data[0]["cnt"] if isinstance(result.data[0], dict) else result.data[0][0]
|
|
415
|
+
if int(count) > 0:
|
|
416
|
+
logger.debug("Detected Oracle Autonomous Database via v$pdbs")
|
|
417
|
+
return True
|
|
418
|
+
|
|
419
|
+
logger.debug("Oracle Autonomous Database not detected")
|
|
420
|
+
return False
|
|
421
|
+
|
|
422
|
+
async def get_feature_flag(self, driver: AsyncDriverAdapterBase, feature: str) -> bool:
|
|
423
|
+
"""Check if Oracle database supports a specific feature.
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
driver: Async database driver instance
|
|
427
|
+
feature: Feature name to check
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
True if feature is supported, False otherwise
|
|
431
|
+
"""
|
|
432
|
+
if feature == "is_autonomous":
|
|
433
|
+
return await self._is_oracle_autonomous_async(cast("OracleAsyncDriver", driver))
|
|
434
|
+
|
|
435
|
+
version_info = await self.get_version(driver)
|
|
436
|
+
if not version_info:
|
|
437
|
+
return False
|
|
438
|
+
|
|
439
|
+
feature_checks: dict[str, Callable[..., bool]] = {
|
|
440
|
+
"supports_native_json": version_info.supports_native_json,
|
|
441
|
+
"supports_oson_blob": version_info.supports_oson_blob,
|
|
442
|
+
"supports_json_blob": version_info.supports_json_blob,
|
|
443
|
+
"supports_json": version_info.supports_json_blob, # Any JSON support
|
|
444
|
+
"supports_transactions": lambda: True,
|
|
445
|
+
"supports_prepared_statements": lambda: True,
|
|
446
|
+
"supports_schemas": lambda: True,
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if feature in feature_checks:
|
|
450
|
+
return bool(feature_checks[feature]())
|
|
451
|
+
|
|
452
|
+
return False
|
|
453
|
+
|
|
454
|
+
async def get_optimal_type(self, driver: AsyncDriverAdapterBase, type_category: str) -> str:
|
|
455
|
+
"""Get optimal Oracle type for a category.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
driver: Async database driver instance
|
|
459
|
+
type_category: Type category
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
Oracle-specific type name
|
|
463
|
+
"""
|
|
464
|
+
if type_category == "json":
|
|
465
|
+
version_info = await self.get_version(driver)
|
|
466
|
+
return self._get_oracle_json_type(version_info)
|
|
467
|
+
|
|
468
|
+
# Other Oracle-specific type mappings
|
|
469
|
+
type_map = {"uuid": "RAW(16)", "boolean": "NUMBER(1)", "timestamp": "TIMESTAMP", "text": "CLOB", "blob": "BLOB"}
|
|
470
|
+
return type_map.get(type_category, "VARCHAR2(255)")
|
|
471
|
+
|
|
472
|
+
async def get_columns(
|
|
473
|
+
self, driver: AsyncDriverAdapterBase, table: str, schema: "str | None" = None
|
|
474
|
+
) -> "list[dict[str, Any]]":
|
|
475
|
+
"""Get column information for a table from Oracle data dictionary.
|
|
476
|
+
|
|
477
|
+
Args:
|
|
478
|
+
driver: Async database driver instance
|
|
479
|
+
table: Table name to query columns for
|
|
480
|
+
schema: Schema name (ignored for USER_TAB_COLUMNS)
|
|
481
|
+
|
|
482
|
+
Returns:
|
|
483
|
+
List of column metadata dictionaries with keys:
|
|
484
|
+
- column_name: Name of the column
|
|
485
|
+
- data_type: Oracle data type
|
|
486
|
+
- data_length: Maximum length (for character types)
|
|
487
|
+
- nullable: 'Y' or 'N'
|
|
488
|
+
"""
|
|
489
|
+
|
|
490
|
+
oracle_driver = cast("OracleAsyncDriver", driver)
|
|
491
|
+
result = await oracle_driver.execute(self._get_columns_sql(table, schema))
|
|
492
|
+
return result.get_data()
|
|
493
|
+
|
|
494
|
+
def list_available_features(self) -> "list[str]":
|
|
495
|
+
"""List available Oracle feature flags.
|
|
496
|
+
|
|
497
|
+
Returns:
|
|
498
|
+
List of supported feature names
|
|
499
|
+
"""
|
|
500
|
+
return [
|
|
501
|
+
"is_autonomous",
|
|
502
|
+
"supports_native_json",
|
|
503
|
+
"supports_oson_blob",
|
|
504
|
+
"supports_json_blob",
|
|
505
|
+
"supports_json",
|
|
506
|
+
"supports_transactions",
|
|
507
|
+
"supports_prepared_statements",
|
|
508
|
+
"supports_schemas",
|
|
509
|
+
]
|