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.

Files changed (199) hide show
  1. sqlspec/__init__.py +7 -15
  2. sqlspec/_serialization.py +256 -24
  3. sqlspec/_typing.py +71 -52
  4. sqlspec/adapters/adbc/_types.py +1 -1
  5. sqlspec/adapters/adbc/adk/__init__.py +5 -0
  6. sqlspec/adapters/adbc/adk/store.py +870 -0
  7. sqlspec/adapters/adbc/config.py +69 -12
  8. sqlspec/adapters/adbc/data_dictionary.py +340 -0
  9. sqlspec/adapters/adbc/driver.py +266 -58
  10. sqlspec/adapters/adbc/litestar/__init__.py +5 -0
  11. sqlspec/adapters/adbc/litestar/store.py +504 -0
  12. sqlspec/adapters/adbc/type_converter.py +153 -0
  13. sqlspec/adapters/aiosqlite/_types.py +1 -1
  14. sqlspec/adapters/aiosqlite/adk/__init__.py +5 -0
  15. sqlspec/adapters/aiosqlite/adk/store.py +527 -0
  16. sqlspec/adapters/aiosqlite/config.py +88 -15
  17. sqlspec/adapters/aiosqlite/data_dictionary.py +149 -0
  18. sqlspec/adapters/aiosqlite/driver.py +143 -40
  19. sqlspec/adapters/aiosqlite/litestar/__init__.py +5 -0
  20. sqlspec/adapters/aiosqlite/litestar/store.py +281 -0
  21. sqlspec/adapters/aiosqlite/pool.py +7 -7
  22. sqlspec/adapters/asyncmy/__init__.py +7 -1
  23. sqlspec/adapters/asyncmy/_types.py +2 -2
  24. sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
  25. sqlspec/adapters/asyncmy/adk/store.py +493 -0
  26. sqlspec/adapters/asyncmy/config.py +68 -23
  27. sqlspec/adapters/asyncmy/data_dictionary.py +161 -0
  28. sqlspec/adapters/asyncmy/driver.py +313 -58
  29. sqlspec/adapters/asyncmy/litestar/__init__.py +5 -0
  30. sqlspec/adapters/asyncmy/litestar/store.py +296 -0
  31. sqlspec/adapters/asyncpg/__init__.py +2 -1
  32. sqlspec/adapters/asyncpg/_type_handlers.py +71 -0
  33. sqlspec/adapters/asyncpg/_types.py +11 -7
  34. sqlspec/adapters/asyncpg/adk/__init__.py +5 -0
  35. sqlspec/adapters/asyncpg/adk/store.py +450 -0
  36. sqlspec/adapters/asyncpg/config.py +59 -35
  37. sqlspec/adapters/asyncpg/data_dictionary.py +173 -0
  38. sqlspec/adapters/asyncpg/driver.py +170 -25
  39. sqlspec/adapters/asyncpg/litestar/__init__.py +5 -0
  40. sqlspec/adapters/asyncpg/litestar/store.py +253 -0
  41. sqlspec/adapters/bigquery/_types.py +1 -1
  42. sqlspec/adapters/bigquery/adk/__init__.py +5 -0
  43. sqlspec/adapters/bigquery/adk/store.py +576 -0
  44. sqlspec/adapters/bigquery/config.py +27 -10
  45. sqlspec/adapters/bigquery/data_dictionary.py +149 -0
  46. sqlspec/adapters/bigquery/driver.py +368 -142
  47. sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
  48. sqlspec/adapters/bigquery/litestar/store.py +327 -0
  49. sqlspec/adapters/bigquery/type_converter.py +125 -0
  50. sqlspec/adapters/duckdb/_types.py +1 -1
  51. sqlspec/adapters/duckdb/adk/__init__.py +14 -0
  52. sqlspec/adapters/duckdb/adk/store.py +553 -0
  53. sqlspec/adapters/duckdb/config.py +80 -20
  54. sqlspec/adapters/duckdb/data_dictionary.py +163 -0
  55. sqlspec/adapters/duckdb/driver.py +167 -45
  56. sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
  57. sqlspec/adapters/duckdb/litestar/store.py +332 -0
  58. sqlspec/adapters/duckdb/pool.py +4 -4
  59. sqlspec/adapters/duckdb/type_converter.py +133 -0
  60. sqlspec/adapters/oracledb/_numpy_handlers.py +133 -0
  61. sqlspec/adapters/oracledb/_types.py +20 -2
  62. sqlspec/adapters/oracledb/adk/__init__.py +5 -0
  63. sqlspec/adapters/oracledb/adk/store.py +1745 -0
  64. sqlspec/adapters/oracledb/config.py +122 -32
  65. sqlspec/adapters/oracledb/data_dictionary.py +509 -0
  66. sqlspec/adapters/oracledb/driver.py +353 -91
  67. sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
  68. sqlspec/adapters/oracledb/litestar/store.py +767 -0
  69. sqlspec/adapters/oracledb/migrations.py +348 -73
  70. sqlspec/adapters/oracledb/type_converter.py +207 -0
  71. sqlspec/adapters/psqlpy/_type_handlers.py +44 -0
  72. sqlspec/adapters/psqlpy/_types.py +2 -1
  73. sqlspec/adapters/psqlpy/adk/__init__.py +5 -0
  74. sqlspec/adapters/psqlpy/adk/store.py +482 -0
  75. sqlspec/adapters/psqlpy/config.py +46 -17
  76. sqlspec/adapters/psqlpy/data_dictionary.py +172 -0
  77. sqlspec/adapters/psqlpy/driver.py +123 -209
  78. sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
  79. sqlspec/adapters/psqlpy/litestar/store.py +272 -0
  80. sqlspec/adapters/psqlpy/type_converter.py +102 -0
  81. sqlspec/adapters/psycopg/_type_handlers.py +80 -0
  82. sqlspec/adapters/psycopg/_types.py +2 -1
  83. sqlspec/adapters/psycopg/adk/__init__.py +5 -0
  84. sqlspec/adapters/psycopg/adk/store.py +944 -0
  85. sqlspec/adapters/psycopg/config.py +69 -35
  86. sqlspec/adapters/psycopg/data_dictionary.py +331 -0
  87. sqlspec/adapters/psycopg/driver.py +238 -81
  88. sqlspec/adapters/psycopg/litestar/__init__.py +5 -0
  89. sqlspec/adapters/psycopg/litestar/store.py +554 -0
  90. sqlspec/adapters/sqlite/__init__.py +2 -1
  91. sqlspec/adapters/sqlite/_type_handlers.py +86 -0
  92. sqlspec/adapters/sqlite/_types.py +1 -1
  93. sqlspec/adapters/sqlite/adk/__init__.py +5 -0
  94. sqlspec/adapters/sqlite/adk/store.py +572 -0
  95. sqlspec/adapters/sqlite/config.py +87 -15
  96. sqlspec/adapters/sqlite/data_dictionary.py +149 -0
  97. sqlspec/adapters/sqlite/driver.py +137 -54
  98. sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
  99. sqlspec/adapters/sqlite/litestar/store.py +318 -0
  100. sqlspec/adapters/sqlite/pool.py +18 -9
  101. sqlspec/base.py +45 -26
  102. sqlspec/builder/__init__.py +73 -4
  103. sqlspec/builder/_base.py +162 -89
  104. sqlspec/builder/_column.py +62 -29
  105. sqlspec/builder/_ddl.py +180 -121
  106. sqlspec/builder/_delete.py +5 -4
  107. sqlspec/builder/_dml.py +388 -0
  108. sqlspec/{_sql.py → builder/_factory.py} +53 -94
  109. sqlspec/builder/_insert.py +32 -131
  110. sqlspec/builder/_join.py +375 -0
  111. sqlspec/builder/_merge.py +446 -11
  112. sqlspec/builder/_parsing_utils.py +111 -17
  113. sqlspec/builder/_select.py +1457 -24
  114. sqlspec/builder/_update.py +11 -42
  115. sqlspec/cli.py +307 -194
  116. sqlspec/config.py +252 -67
  117. sqlspec/core/__init__.py +5 -4
  118. sqlspec/core/cache.py +17 -17
  119. sqlspec/core/compiler.py +62 -9
  120. sqlspec/core/filters.py +37 -37
  121. sqlspec/core/hashing.py +9 -9
  122. sqlspec/core/parameters.py +83 -48
  123. sqlspec/core/result.py +102 -46
  124. sqlspec/core/splitter.py +16 -17
  125. sqlspec/core/statement.py +36 -30
  126. sqlspec/core/type_conversion.py +235 -0
  127. sqlspec/driver/__init__.py +7 -6
  128. sqlspec/driver/_async.py +188 -151
  129. sqlspec/driver/_common.py +285 -80
  130. sqlspec/driver/_sync.py +188 -152
  131. sqlspec/driver/mixins/_result_tools.py +20 -236
  132. sqlspec/driver/mixins/_sql_translator.py +4 -4
  133. sqlspec/exceptions.py +75 -7
  134. sqlspec/extensions/adk/__init__.py +53 -0
  135. sqlspec/extensions/adk/_types.py +51 -0
  136. sqlspec/extensions/adk/converters.py +172 -0
  137. sqlspec/extensions/adk/migrations/0001_create_adk_tables.py +144 -0
  138. sqlspec/extensions/adk/migrations/__init__.py +0 -0
  139. sqlspec/extensions/adk/service.py +181 -0
  140. sqlspec/extensions/adk/store.py +536 -0
  141. sqlspec/extensions/aiosql/adapter.py +73 -53
  142. sqlspec/extensions/litestar/__init__.py +21 -4
  143. sqlspec/extensions/litestar/cli.py +54 -10
  144. sqlspec/extensions/litestar/config.py +59 -266
  145. sqlspec/extensions/litestar/handlers.py +46 -17
  146. sqlspec/extensions/litestar/migrations/0001_create_session_table.py +137 -0
  147. sqlspec/extensions/litestar/migrations/__init__.py +3 -0
  148. sqlspec/extensions/litestar/plugin.py +324 -223
  149. sqlspec/extensions/litestar/providers.py +25 -25
  150. sqlspec/extensions/litestar/store.py +265 -0
  151. sqlspec/loader.py +30 -49
  152. sqlspec/migrations/__init__.py +4 -3
  153. sqlspec/migrations/base.py +302 -39
  154. sqlspec/migrations/commands.py +611 -144
  155. sqlspec/migrations/context.py +142 -0
  156. sqlspec/migrations/fix.py +199 -0
  157. sqlspec/migrations/loaders.py +68 -23
  158. sqlspec/migrations/runner.py +543 -107
  159. sqlspec/migrations/tracker.py +237 -21
  160. sqlspec/migrations/utils.py +51 -3
  161. sqlspec/migrations/validation.py +177 -0
  162. sqlspec/protocols.py +66 -36
  163. sqlspec/storage/_utils.py +98 -0
  164. sqlspec/storage/backends/fsspec.py +134 -106
  165. sqlspec/storage/backends/local.py +78 -51
  166. sqlspec/storage/backends/obstore.py +278 -162
  167. sqlspec/storage/registry.py +75 -39
  168. sqlspec/typing.py +16 -84
  169. sqlspec/utils/config_resolver.py +153 -0
  170. sqlspec/utils/correlation.py +4 -5
  171. sqlspec/utils/data_transformation.py +3 -2
  172. sqlspec/utils/deprecation.py +9 -8
  173. sqlspec/utils/fixtures.py +4 -4
  174. sqlspec/utils/logging.py +46 -6
  175. sqlspec/utils/module_loader.py +2 -2
  176. sqlspec/utils/schema.py +288 -0
  177. sqlspec/utils/serializers.py +50 -2
  178. sqlspec/utils/sync_tools.py +21 -17
  179. sqlspec/utils/text.py +1 -2
  180. sqlspec/utils/type_guards.py +111 -20
  181. sqlspec/utils/version.py +433 -0
  182. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/METADATA +40 -21
  183. sqlspec-0.27.0.dist-info/RECORD +207 -0
  184. sqlspec/builder/mixins/__init__.py +0 -55
  185. sqlspec/builder/mixins/_cte_and_set_ops.py +0 -254
  186. sqlspec/builder/mixins/_delete_operations.py +0 -50
  187. sqlspec/builder/mixins/_insert_operations.py +0 -282
  188. sqlspec/builder/mixins/_join_operations.py +0 -389
  189. sqlspec/builder/mixins/_merge_operations.py +0 -592
  190. sqlspec/builder/mixins/_order_limit_operations.py +0 -152
  191. sqlspec/builder/mixins/_pivot_operations.py +0 -157
  192. sqlspec/builder/mixins/_select_operations.py +0 -936
  193. sqlspec/builder/mixins/_update_operations.py +0 -218
  194. sqlspec/builder/mixins/_where_clause.py +0 -1304
  195. sqlspec-0.25.0.dist-info/RECORD +0 -139
  196. sqlspec-0.25.0.dist-info/licenses/NOTICE +0 -29
  197. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
  198. {sqlspec-0.25.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
  199. {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
+ ]