sqlspec 0.26.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 +55 -25
- sqlspec/_typing.py +62 -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 +62 -12
- sqlspec/adapters/adbc/data_dictionary.py +52 -2
- sqlspec/adapters/adbc/driver.py +144 -45
- sqlspec/adapters/adbc/litestar/__init__.py +5 -0
- sqlspec/adapters/adbc/litestar/store.py +504 -0
- sqlspec/adapters/adbc/type_converter.py +44 -50
- 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 +86 -16
- sqlspec/adapters/aiosqlite/data_dictionary.py +34 -2
- sqlspec/adapters/aiosqlite/driver.py +127 -38
- 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 +1 -1
- sqlspec/adapters/asyncmy/adk/__init__.py +5 -0
- sqlspec/adapters/asyncmy/adk/store.py +493 -0
- sqlspec/adapters/asyncmy/config.py +59 -17
- sqlspec/adapters/asyncmy/data_dictionary.py +41 -2
- sqlspec/adapters/asyncmy/driver.py +293 -62
- 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 +57 -36
- sqlspec/adapters/asyncpg/data_dictionary.py +41 -2
- sqlspec/adapters/asyncpg/driver.py +153 -23
- 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 +25 -11
- sqlspec/adapters/bigquery/data_dictionary.py +42 -2
- sqlspec/adapters/bigquery/driver.py +352 -144
- sqlspec/adapters/bigquery/litestar/__init__.py +5 -0
- sqlspec/adapters/bigquery/litestar/store.py +327 -0
- sqlspec/adapters/bigquery/type_converter.py +55 -23
- sqlspec/adapters/duckdb/_types.py +2 -2
- sqlspec/adapters/duckdb/adk/__init__.py +14 -0
- sqlspec/adapters/duckdb/adk/store.py +553 -0
- sqlspec/adapters/duckdb/config.py +79 -21
- sqlspec/adapters/duckdb/data_dictionary.py +41 -2
- sqlspec/adapters/duckdb/driver.py +138 -43
- sqlspec/adapters/duckdb/litestar/__init__.py +5 -0
- sqlspec/adapters/duckdb/litestar/store.py +332 -0
- sqlspec/adapters/duckdb/pool.py +5 -5
- sqlspec/adapters/duckdb/type_converter.py +51 -21
- 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 +120 -36
- sqlspec/adapters/oracledb/data_dictionary.py +87 -20
- sqlspec/adapters/oracledb/driver.py +292 -84
- sqlspec/adapters/oracledb/litestar/__init__.py +5 -0
- sqlspec/adapters/oracledb/litestar/store.py +767 -0
- sqlspec/adapters/oracledb/migrations.py +316 -25
- sqlspec/adapters/oracledb/type_converter.py +91 -16
- 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 +45 -19
- sqlspec/adapters/psqlpy/data_dictionary.py +41 -2
- sqlspec/adapters/psqlpy/driver.py +101 -31
- sqlspec/adapters/psqlpy/litestar/__init__.py +5 -0
- sqlspec/adapters/psqlpy/litestar/store.py +272 -0
- sqlspec/adapters/psqlpy/type_converter.py +40 -11
- 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 +65 -37
- sqlspec/adapters/psycopg/data_dictionary.py +77 -3
- sqlspec/adapters/psycopg/driver.py +200 -78
- 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 +85 -16
- sqlspec/adapters/sqlite/data_dictionary.py +34 -2
- sqlspec/adapters/sqlite/driver.py +120 -52
- sqlspec/adapters/sqlite/litestar/__init__.py +5 -0
- sqlspec/adapters/sqlite/litestar/store.py +318 -0
- sqlspec/adapters/sqlite/pool.py +5 -5
- sqlspec/base.py +45 -26
- sqlspec/builder/__init__.py +73 -4
- sqlspec/builder/_base.py +91 -58
- sqlspec/builder/_column.py +5 -5
- sqlspec/builder/_ddl.py +98 -89
- sqlspec/builder/_delete.py +5 -4
- sqlspec/builder/_dml.py +388 -0
- sqlspec/{_sql.py → builder/_factory.py} +41 -44
- sqlspec/builder/_insert.py +5 -82
- sqlspec/builder/{mixins/_join_operations.py → _join.py} +145 -143
- sqlspec/builder/_merge.py +446 -11
- sqlspec/builder/_parsing_utils.py +9 -11
- sqlspec/builder/_select.py +1313 -25
- sqlspec/builder/_update.py +11 -42
- sqlspec/cli.py +76 -69
- sqlspec/config.py +231 -60
- sqlspec/core/__init__.py +5 -4
- sqlspec/core/cache.py +18 -18
- sqlspec/core/compiler.py +6 -8
- sqlspec/core/filters.py +37 -37
- sqlspec/core/hashing.py +9 -9
- sqlspec/core/parameters.py +76 -45
- sqlspec/core/result.py +102 -46
- sqlspec/core/splitter.py +16 -17
- sqlspec/core/statement.py +32 -31
- sqlspec/core/type_conversion.py +3 -2
- sqlspec/driver/__init__.py +1 -3
- sqlspec/driver/_async.py +95 -161
- sqlspec/driver/_common.py +133 -80
- sqlspec/driver/_sync.py +95 -162
- sqlspec/driver/mixins/_result_tools.py +20 -236
- sqlspec/driver/mixins/_sql_translator.py +4 -4
- sqlspec/exceptions.py +70 -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/base.py +200 -76
- sqlspec/migrations/commands.py +591 -62
- sqlspec/migrations/context.py +6 -9
- sqlspec/migrations/fix.py +199 -0
- sqlspec/migrations/loaders.py +47 -19
- sqlspec/migrations/runner.py +241 -75
- 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 +14 -84
- sqlspec/utils/config_resolver.py +6 -6
- 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 +3 -3
- 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.26.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 -253
- sqlspec/builder/mixins/_delete_operations.py +0 -50
- sqlspec/builder/mixins/_insert_operations.py +0 -282
- sqlspec/builder/mixins/_merge_operations.py +0 -698
- sqlspec/builder/mixins/_order_limit_operations.py +0 -145
- sqlspec/builder/mixins/_pivot_operations.py +0 -157
- sqlspec/builder/mixins/_select_operations.py +0 -930
- sqlspec/builder/mixins/_update_operations.py +0 -199
- sqlspec/builder/mixins/_where_clause.py +0 -1298
- sqlspec-0.26.0.dist-info/RECORD +0 -157
- sqlspec-0.26.0.dist-info/licenses/NOTICE +0 -29
- {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/entry_points.txt +0 -0
- {sqlspec-0.26.0.dist-info → sqlspec-0.27.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import contextlib
|
|
4
4
|
import logging
|
|
5
|
-
|
|
5
|
+
import re
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Final
|
|
6
7
|
|
|
7
8
|
import oracledb
|
|
8
9
|
from oracledb import AsyncCursor, Cursor
|
|
@@ -19,7 +20,19 @@ from sqlspec.driver import (
|
|
|
19
20
|
SyncDataDictionaryBase,
|
|
20
21
|
SyncDriverAdapterBase,
|
|
21
22
|
)
|
|
22
|
-
from sqlspec.exceptions import
|
|
23
|
+
from sqlspec.exceptions import (
|
|
24
|
+
CheckViolationError,
|
|
25
|
+
DatabaseConnectionError,
|
|
26
|
+
DataError,
|
|
27
|
+
ForeignKeyViolationError,
|
|
28
|
+
IntegrityError,
|
|
29
|
+
NotNullViolationError,
|
|
30
|
+
OperationalError,
|
|
31
|
+
SQLParsingError,
|
|
32
|
+
SQLSpecError,
|
|
33
|
+
TransactionError,
|
|
34
|
+
UniqueViolationError,
|
|
35
|
+
)
|
|
23
36
|
from sqlspec.utils.serializers import to_json
|
|
24
37
|
|
|
25
38
|
if TYPE_CHECKING:
|
|
@@ -36,6 +49,9 @@ LARGE_STRING_THRESHOLD = 3000 # Threshold for large string parameters to avoid
|
|
|
36
49
|
|
|
37
50
|
_type_converter = OracleTypeConverter()
|
|
38
51
|
|
|
52
|
+
IMPLICIT_UPPER_COLUMN_PATTERN: Final[re.Pattern[str]] = re.compile(r"^(?!\d)(?:[A-Z0-9_]+)$")
|
|
53
|
+
|
|
54
|
+
|
|
39
55
|
__all__ = (
|
|
40
56
|
"OracleAsyncDriver",
|
|
41
57
|
"OracleAsyncExceptionHandler",
|
|
@@ -45,6 +61,83 @@ __all__ = (
|
|
|
45
61
|
)
|
|
46
62
|
|
|
47
63
|
|
|
64
|
+
def _normalize_column_names(column_names: "list[str]", driver_features: "dict[str, Any]") -> "list[str]":
|
|
65
|
+
should_lowercase = driver_features.get("enable_lowercase_column_names", False)
|
|
66
|
+
if not should_lowercase:
|
|
67
|
+
return column_names
|
|
68
|
+
normalized: list[str] = []
|
|
69
|
+
for name in column_names:
|
|
70
|
+
if name and IMPLICIT_UPPER_COLUMN_PATTERN.fullmatch(name):
|
|
71
|
+
normalized.append(name.lower())
|
|
72
|
+
else:
|
|
73
|
+
normalized.append(name)
|
|
74
|
+
return normalized
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _coerce_sync_row_values(row: "tuple[Any, ...]") -> "list[Any]":
|
|
78
|
+
"""Coerce LOB handles to concrete values for synchronous execution.
|
|
79
|
+
|
|
80
|
+
Processes each value in the row, reading LOB objects and applying
|
|
81
|
+
type detection for JSON values stored in CLOBs.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
row: Tuple of column values from database fetch.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
List of coerced values with LOBs read to strings/bytes.
|
|
88
|
+
"""
|
|
89
|
+
coerced_values: list[Any] = []
|
|
90
|
+
for value in row:
|
|
91
|
+
if hasattr(value, "read"):
|
|
92
|
+
try:
|
|
93
|
+
processed_value = value.read()
|
|
94
|
+
except Exception:
|
|
95
|
+
coerced_values.append(value)
|
|
96
|
+
continue
|
|
97
|
+
if isinstance(processed_value, str):
|
|
98
|
+
processed_value = _type_converter.convert_if_detected(processed_value)
|
|
99
|
+
coerced_values.append(processed_value)
|
|
100
|
+
else:
|
|
101
|
+
coerced_values.append(value)
|
|
102
|
+
return coerced_values
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
async def _coerce_async_row_values(row: "tuple[Any, ...]") -> "list[Any]":
|
|
106
|
+
"""Coerce LOB handles to concrete values for asynchronous execution.
|
|
107
|
+
|
|
108
|
+
Processes each value in the row, reading LOB objects asynchronously
|
|
109
|
+
and applying type detection for JSON values stored in CLOBs.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
row: Tuple of column values from database fetch.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
List of coerced values with LOBs read to strings/bytes.
|
|
116
|
+
"""
|
|
117
|
+
coerced_values: list[Any] = []
|
|
118
|
+
for value in row:
|
|
119
|
+
if hasattr(value, "read"):
|
|
120
|
+
try:
|
|
121
|
+
processed_value = await _type_converter.process_lob(value)
|
|
122
|
+
except Exception:
|
|
123
|
+
coerced_values.append(value)
|
|
124
|
+
continue
|
|
125
|
+
if isinstance(processed_value, str):
|
|
126
|
+
processed_value = _type_converter.convert_if_detected(processed_value)
|
|
127
|
+
coerced_values.append(processed_value)
|
|
128
|
+
else:
|
|
129
|
+
coerced_values.append(value)
|
|
130
|
+
return coerced_values
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
ORA_CHECK_CONSTRAINT = 2290
|
|
134
|
+
ORA_INTEGRITY_RANGE_START = 2200
|
|
135
|
+
ORA_INTEGRITY_RANGE_END = 2300
|
|
136
|
+
ORA_PARSING_RANGE_START = 900
|
|
137
|
+
ORA_PARSING_RANGE_END = 1000
|
|
138
|
+
ORA_TABLESPACE_FULL = 1652
|
|
139
|
+
|
|
140
|
+
|
|
48
141
|
oracledb_statement_config = StatementConfig(
|
|
49
142
|
dialect="oracle",
|
|
50
143
|
parameter_config=ParameterStyleConfig(
|
|
@@ -71,7 +164,7 @@ class OracleSyncCursor:
|
|
|
71
164
|
|
|
72
165
|
def __init__(self, connection: OracleSyncConnection) -> None:
|
|
73
166
|
self.connection = connection
|
|
74
|
-
self.cursor:
|
|
167
|
+
self.cursor: Cursor | None = None
|
|
75
168
|
|
|
76
169
|
def __enter__(self) -> Cursor:
|
|
77
170
|
self.cursor = self.connection.cursor()
|
|
@@ -89,7 +182,7 @@ class OracleAsyncCursor:
|
|
|
89
182
|
|
|
90
183
|
def __init__(self, connection: OracleAsyncConnection) -> None:
|
|
91
184
|
self.connection = connection
|
|
92
|
-
self.cursor:
|
|
185
|
+
self.cursor: AsyncCursor | None = None
|
|
93
186
|
|
|
94
187
|
async def __aenter__(self) -> AsyncCursor:
|
|
95
188
|
self.cursor = self.connection.cursor()
|
|
@@ -105,7 +198,11 @@ class OracleAsyncCursor:
|
|
|
105
198
|
|
|
106
199
|
|
|
107
200
|
class OracleSyncExceptionHandler:
|
|
108
|
-
"""Context manager for handling Oracle database exceptions
|
|
201
|
+
"""Context manager for handling Oracle database exceptions.
|
|
202
|
+
|
|
203
|
+
Maps Oracle ORA-XXXXX error codes to specific SQLSpec exceptions
|
|
204
|
+
for better error handling in application code.
|
|
205
|
+
"""
|
|
109
206
|
|
|
110
207
|
__slots__ = ()
|
|
111
208
|
|
|
@@ -113,46 +210,101 @@ class OracleSyncExceptionHandler:
|
|
|
113
210
|
return None
|
|
114
211
|
|
|
115
212
|
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
116
|
-
_ = exc_tb
|
|
213
|
+
_ = exc_tb
|
|
117
214
|
if exc_type is None:
|
|
118
215
|
return
|
|
119
|
-
|
|
120
|
-
if issubclass(exc_type, oracledb.IntegrityError):
|
|
121
|
-
e = exc_val
|
|
122
|
-
msg = f"Oracle integrity constraint violation: {e}"
|
|
123
|
-
raise SQLSpecError(msg) from e
|
|
124
|
-
if issubclass(exc_type, oracledb.ProgrammingError):
|
|
125
|
-
e = exc_val
|
|
126
|
-
error_msg = str(e).lower()
|
|
127
|
-
if "syntax" in error_msg or "parse" in error_msg:
|
|
128
|
-
msg = f"Oracle SQL syntax error: {e}"
|
|
129
|
-
raise SQLParsingError(msg) from e
|
|
130
|
-
msg = f"Oracle programming error: {e}"
|
|
131
|
-
raise SQLSpecError(msg) from e
|
|
132
|
-
if issubclass(exc_type, oracledb.OperationalError):
|
|
133
|
-
e = exc_val
|
|
134
|
-
msg = f"Oracle operational error: {e}"
|
|
135
|
-
raise SQLSpecError(msg) from e
|
|
136
216
|
if issubclass(exc_type, oracledb.DatabaseError):
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
217
|
+
self._map_oracle_exception(exc_val)
|
|
218
|
+
|
|
219
|
+
def _map_oracle_exception(self, e: Any) -> None:
|
|
220
|
+
"""Map Oracle exception to SQLSpec exception.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
e: oracledb.DatabaseError instance
|
|
224
|
+
"""
|
|
225
|
+
error_obj = e.args[0] if e.args else None
|
|
226
|
+
if not error_obj:
|
|
227
|
+
self._raise_generic_error(e, None)
|
|
228
|
+
|
|
229
|
+
error_code = getattr(error_obj, "code", None)
|
|
230
|
+
|
|
231
|
+
if not error_code:
|
|
232
|
+
self._raise_generic_error(e, None)
|
|
233
|
+
|
|
234
|
+
if error_code == 1:
|
|
235
|
+
self._raise_unique_violation(e, error_code)
|
|
236
|
+
elif error_code in {2291, 2292}:
|
|
237
|
+
self._raise_foreign_key_violation(e, error_code)
|
|
238
|
+
elif error_code == ORA_CHECK_CONSTRAINT:
|
|
239
|
+
self._raise_check_violation(e, error_code)
|
|
240
|
+
elif error_code in {1400, 1407}:
|
|
241
|
+
self._raise_not_null_violation(e, error_code)
|
|
242
|
+
elif error_code and ORA_INTEGRITY_RANGE_START <= error_code < ORA_INTEGRITY_RANGE_END:
|
|
243
|
+
self._raise_integrity_error(e, error_code)
|
|
244
|
+
elif error_code in {1017, 12154, 12541, 12545, 12514, 12505}:
|
|
245
|
+
self._raise_connection_error(e, error_code)
|
|
246
|
+
elif error_code in {60, 8176}:
|
|
247
|
+
self._raise_transaction_error(e, error_code)
|
|
248
|
+
elif error_code in {1722, 1858, 1840}:
|
|
249
|
+
self._raise_data_error(e, error_code)
|
|
250
|
+
elif error_code and ORA_PARSING_RANGE_START <= error_code < ORA_PARSING_RANGE_END:
|
|
251
|
+
self._raise_parsing_error(e, error_code)
|
|
252
|
+
elif error_code == ORA_TABLESPACE_FULL:
|
|
253
|
+
self._raise_operational_error(e, error_code)
|
|
254
|
+
else:
|
|
255
|
+
self._raise_generic_error(e, error_code)
|
|
256
|
+
|
|
257
|
+
def _raise_unique_violation(self, e: Any, code: int) -> None:
|
|
258
|
+
msg = f"Oracle unique constraint violation [ORA-{code:05d}]: {e}"
|
|
259
|
+
raise UniqueViolationError(msg) from e
|
|
260
|
+
|
|
261
|
+
def _raise_foreign_key_violation(self, e: Any, code: int) -> None:
|
|
262
|
+
msg = f"Oracle foreign key constraint violation [ORA-{code:05d}]: {e}"
|
|
263
|
+
raise ForeignKeyViolationError(msg) from e
|
|
264
|
+
|
|
265
|
+
def _raise_check_violation(self, e: Any, code: int) -> None:
|
|
266
|
+
msg = f"Oracle check constraint violation [ORA-{code:05d}]: {e}"
|
|
267
|
+
raise CheckViolationError(msg) from e
|
|
268
|
+
|
|
269
|
+
def _raise_not_null_violation(self, e: Any, code: int) -> None:
|
|
270
|
+
msg = f"Oracle not-null constraint violation [ORA-{code:05d}]: {e}"
|
|
271
|
+
raise NotNullViolationError(msg) from e
|
|
272
|
+
|
|
273
|
+
def _raise_integrity_error(self, e: Any, code: int) -> None:
|
|
274
|
+
msg = f"Oracle integrity constraint violation [ORA-{code:05d}]: {e}"
|
|
275
|
+
raise IntegrityError(msg) from e
|
|
276
|
+
|
|
277
|
+
def _raise_parsing_error(self, e: Any, code: int) -> None:
|
|
278
|
+
msg = f"Oracle SQL syntax error [ORA-{code:05d}]: {e}"
|
|
279
|
+
raise SQLParsingError(msg) from e
|
|
280
|
+
|
|
281
|
+
def _raise_connection_error(self, e: Any, code: int) -> None:
|
|
282
|
+
msg = f"Oracle connection error [ORA-{code:05d}]: {e}"
|
|
283
|
+
raise DatabaseConnectionError(msg) from e
|
|
284
|
+
|
|
285
|
+
def _raise_transaction_error(self, e: Any, code: int) -> None:
|
|
286
|
+
msg = f"Oracle transaction error [ORA-{code:05d}]: {e}"
|
|
287
|
+
raise TransactionError(msg) from e
|
|
288
|
+
|
|
289
|
+
def _raise_data_error(self, e: Any, code: int) -> None:
|
|
290
|
+
msg = f"Oracle data error [ORA-{code:05d}]: {e}"
|
|
291
|
+
raise DataError(msg) from e
|
|
292
|
+
|
|
293
|
+
def _raise_operational_error(self, e: Any, code: int) -> None:
|
|
294
|
+
msg = f"Oracle operational error [ORA-{code:05d}]: {e}"
|
|
295
|
+
raise OperationalError(msg) from e
|
|
296
|
+
|
|
297
|
+
def _raise_generic_error(self, e: Any, code: "int | None") -> None:
|
|
298
|
+
msg = f"Oracle database error [ORA-{code:05d}]: {e}" if code else f"Oracle database error: {e}"
|
|
299
|
+
raise SQLSpecError(msg) from e
|
|
152
300
|
|
|
153
301
|
|
|
154
302
|
class OracleAsyncExceptionHandler:
|
|
155
|
-
"""
|
|
303
|
+
"""Async context manager for handling Oracle database exceptions.
|
|
304
|
+
|
|
305
|
+
Maps Oracle ORA-XXXXX error codes to specific SQLSpec exceptions
|
|
306
|
+
for better error handling in application code.
|
|
307
|
+
"""
|
|
156
308
|
|
|
157
309
|
__slots__ = ()
|
|
158
310
|
|
|
@@ -160,42 +312,93 @@ class OracleAsyncExceptionHandler:
|
|
|
160
312
|
return None
|
|
161
313
|
|
|
162
314
|
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
163
|
-
_ = exc_tb
|
|
315
|
+
_ = exc_tb
|
|
164
316
|
if exc_type is None:
|
|
165
317
|
return
|
|
166
|
-
|
|
167
|
-
if issubclass(exc_type, oracledb.IntegrityError):
|
|
168
|
-
e = exc_val
|
|
169
|
-
msg = f"Oracle integrity constraint violation: {e}"
|
|
170
|
-
raise SQLSpecError(msg) from e
|
|
171
|
-
if issubclass(exc_type, oracledb.ProgrammingError):
|
|
172
|
-
e = exc_val
|
|
173
|
-
error_msg = str(e).lower()
|
|
174
|
-
if "syntax" in error_msg or "parse" in error_msg:
|
|
175
|
-
msg = f"Oracle SQL syntax error: {e}"
|
|
176
|
-
raise SQLParsingError(msg) from e
|
|
177
|
-
msg = f"Oracle programming error: {e}"
|
|
178
|
-
raise SQLSpecError(msg) from e
|
|
179
|
-
if issubclass(exc_type, oracledb.OperationalError):
|
|
180
|
-
e = exc_val
|
|
181
|
-
msg = f"Oracle operational error: {e}"
|
|
182
|
-
raise SQLSpecError(msg) from e
|
|
183
318
|
if issubclass(exc_type, oracledb.DatabaseError):
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
319
|
+
self._map_oracle_exception(exc_val)
|
|
320
|
+
|
|
321
|
+
def _map_oracle_exception(self, e: Any) -> None:
|
|
322
|
+
"""Map Oracle exception to SQLSpec exception.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
e: oracledb.DatabaseError instance
|
|
326
|
+
"""
|
|
327
|
+
error_obj = e.args[0] if e.args else None
|
|
328
|
+
if not error_obj:
|
|
329
|
+
self._raise_generic_error(e, None)
|
|
330
|
+
|
|
331
|
+
error_code = getattr(error_obj, "code", None)
|
|
332
|
+
|
|
333
|
+
if not error_code:
|
|
334
|
+
self._raise_generic_error(e, None)
|
|
335
|
+
|
|
336
|
+
if error_code == 1:
|
|
337
|
+
self._raise_unique_violation(e, error_code)
|
|
338
|
+
elif error_code in {2291, 2292}:
|
|
339
|
+
self._raise_foreign_key_violation(e, error_code)
|
|
340
|
+
elif error_code == ORA_CHECK_CONSTRAINT:
|
|
341
|
+
self._raise_check_violation(e, error_code)
|
|
342
|
+
elif error_code in {1400, 1407}:
|
|
343
|
+
self._raise_not_null_violation(e, error_code)
|
|
344
|
+
elif error_code and ORA_INTEGRITY_RANGE_START <= error_code < ORA_INTEGRITY_RANGE_END:
|
|
345
|
+
self._raise_integrity_error(e, error_code)
|
|
346
|
+
elif error_code in {1017, 12154, 12541, 12545, 12514, 12505}:
|
|
347
|
+
self._raise_connection_error(e, error_code)
|
|
348
|
+
elif error_code in {60, 8176}:
|
|
349
|
+
self._raise_transaction_error(e, error_code)
|
|
350
|
+
elif error_code in {1722, 1858, 1840}:
|
|
351
|
+
self._raise_data_error(e, error_code)
|
|
352
|
+
elif error_code and ORA_PARSING_RANGE_START <= error_code < ORA_PARSING_RANGE_END:
|
|
353
|
+
self._raise_parsing_error(e, error_code)
|
|
354
|
+
elif error_code == ORA_TABLESPACE_FULL:
|
|
355
|
+
self._raise_operational_error(e, error_code)
|
|
356
|
+
else:
|
|
357
|
+
self._raise_generic_error(e, error_code)
|
|
358
|
+
|
|
359
|
+
def _raise_unique_violation(self, e: Any, code: int) -> None:
|
|
360
|
+
msg = f"Oracle unique constraint violation [ORA-{code:05d}]: {e}"
|
|
361
|
+
raise UniqueViolationError(msg) from e
|
|
362
|
+
|
|
363
|
+
def _raise_foreign_key_violation(self, e: Any, code: int) -> None:
|
|
364
|
+
msg = f"Oracle foreign key constraint violation [ORA-{code:05d}]: {e}"
|
|
365
|
+
raise ForeignKeyViolationError(msg) from e
|
|
366
|
+
|
|
367
|
+
def _raise_check_violation(self, e: Any, code: int) -> None:
|
|
368
|
+
msg = f"Oracle check constraint violation [ORA-{code:05d}]: {e}"
|
|
369
|
+
raise CheckViolationError(msg) from e
|
|
370
|
+
|
|
371
|
+
def _raise_not_null_violation(self, e: Any, code: int) -> None:
|
|
372
|
+
msg = f"Oracle not-null constraint violation [ORA-{code:05d}]: {e}"
|
|
373
|
+
raise NotNullViolationError(msg) from e
|
|
374
|
+
|
|
375
|
+
def _raise_integrity_error(self, e: Any, code: int) -> None:
|
|
376
|
+
msg = f"Oracle integrity constraint violation [ORA-{code:05d}]: {e}"
|
|
377
|
+
raise IntegrityError(msg) from e
|
|
378
|
+
|
|
379
|
+
def _raise_parsing_error(self, e: Any, code: int) -> None:
|
|
380
|
+
msg = f"Oracle SQL syntax error [ORA-{code:05d}]: {e}"
|
|
381
|
+
raise SQLParsingError(msg) from e
|
|
382
|
+
|
|
383
|
+
def _raise_connection_error(self, e: Any, code: int) -> None:
|
|
384
|
+
msg = f"Oracle connection error [ORA-{code:05d}]: {e}"
|
|
385
|
+
raise DatabaseConnectionError(msg) from e
|
|
386
|
+
|
|
387
|
+
def _raise_transaction_error(self, e: Any, code: int) -> None:
|
|
388
|
+
msg = f"Oracle transaction error [ORA-{code:05d}]: {e}"
|
|
389
|
+
raise TransactionError(msg) from e
|
|
390
|
+
|
|
391
|
+
def _raise_data_error(self, e: Any, code: int) -> None:
|
|
392
|
+
msg = f"Oracle data error [ORA-{code:05d}]: {e}"
|
|
393
|
+
raise DataError(msg) from e
|
|
394
|
+
|
|
395
|
+
def _raise_operational_error(self, e: Any, code: int) -> None:
|
|
396
|
+
msg = f"Oracle operational error [ORA-{code:05d}]: {e}"
|
|
397
|
+
raise OperationalError(msg) from e
|
|
398
|
+
|
|
399
|
+
def _raise_generic_error(self, e: Any, code: "int | None") -> None:
|
|
400
|
+
msg = f"Oracle database error [ORA-{code:05d}]: {e}" if code else f"Oracle database error: {e}"
|
|
401
|
+
raise SQLSpecError(msg) from e
|
|
199
402
|
|
|
200
403
|
|
|
201
404
|
class OracleSyncDriver(SyncDriverAdapterBase):
|
|
@@ -211,8 +414,8 @@ class OracleSyncDriver(SyncDriverAdapterBase):
|
|
|
211
414
|
def __init__(
|
|
212
415
|
self,
|
|
213
416
|
connection: OracleSyncConnection,
|
|
214
|
-
statement_config: "
|
|
215
|
-
driver_features: "
|
|
417
|
+
statement_config: "StatementConfig | None" = None,
|
|
418
|
+
driver_features: "dict[str, Any] | None" = None,
|
|
216
419
|
) -> None:
|
|
217
420
|
if statement_config is None:
|
|
218
421
|
cache_config = get_cache_config()
|
|
@@ -224,7 +427,7 @@ class OracleSyncDriver(SyncDriverAdapterBase):
|
|
|
224
427
|
)
|
|
225
428
|
|
|
226
429
|
super().__init__(connection=connection, statement_config=statement_config, driver_features=driver_features)
|
|
227
|
-
self._data_dictionary:
|
|
430
|
+
self._data_dictionary: SyncDataDictionaryBase | None = None
|
|
228
431
|
|
|
229
432
|
def with_cursor(self, connection: OracleSyncConnection) -> OracleSyncCursor:
|
|
230
433
|
"""Create context manager for Oracle cursor.
|
|
@@ -241,7 +444,7 @@ class OracleSyncDriver(SyncDriverAdapterBase):
|
|
|
241
444
|
"""Handle database-specific exceptions and wrap them appropriately."""
|
|
242
445
|
return OracleSyncExceptionHandler()
|
|
243
446
|
|
|
244
|
-
def _try_special_handling(self, cursor: Any, statement: "SQL") -> "
|
|
447
|
+
def _try_special_handling(self, cursor: Any, statement: "SQL") -> "SQLResult | None":
|
|
245
448
|
"""Hook for Oracle-specific special operations.
|
|
246
449
|
|
|
247
450
|
Oracle doesn't have complex special operations like PostgreSQL COPY,
|
|
@@ -339,9 +542,10 @@ class OracleSyncDriver(SyncDriverAdapterBase):
|
|
|
339
542
|
if statement.returns_rows():
|
|
340
543
|
fetched_data = cursor.fetchall()
|
|
341
544
|
column_names = [col[0] for col in cursor.description or []]
|
|
545
|
+
column_names = _normalize_column_names(column_names, self.driver_features)
|
|
342
546
|
|
|
343
|
-
# Oracle returns tuples - convert to consistent dict format
|
|
344
|
-
data = [dict(zip(column_names, row)) for row in fetched_data]
|
|
547
|
+
# Oracle returns tuples - convert to consistent dict format after LOB hydration
|
|
548
|
+
data = [dict(zip(column_names, _coerce_sync_row_values(row), strict=False)) for row in fetched_data]
|
|
345
549
|
|
|
346
550
|
return self.create_execution_result(
|
|
347
551
|
cursor, selected_data=data, column_names=column_names, data_row_count=len(data), is_select_result=True
|
|
@@ -408,8 +612,8 @@ class OracleAsyncDriver(AsyncDriverAdapterBase):
|
|
|
408
612
|
def __init__(
|
|
409
613
|
self,
|
|
410
614
|
connection: OracleAsyncConnection,
|
|
411
|
-
statement_config: "
|
|
412
|
-
driver_features: "
|
|
615
|
+
statement_config: "StatementConfig | None" = None,
|
|
616
|
+
driver_features: "dict[str, Any] | None" = None,
|
|
413
617
|
) -> None:
|
|
414
618
|
if statement_config is None:
|
|
415
619
|
cache_config = get_cache_config()
|
|
@@ -421,7 +625,7 @@ class OracleAsyncDriver(AsyncDriverAdapterBase):
|
|
|
421
625
|
)
|
|
422
626
|
|
|
423
627
|
super().__init__(connection=connection, statement_config=statement_config, driver_features=driver_features)
|
|
424
|
-
self._data_dictionary:
|
|
628
|
+
self._data_dictionary: AsyncDataDictionaryBase | None = None
|
|
425
629
|
|
|
426
630
|
def with_cursor(self, connection: OracleAsyncConnection) -> OracleAsyncCursor:
|
|
427
631
|
"""Create context manager for Oracle cursor.
|
|
@@ -438,7 +642,7 @@ class OracleAsyncDriver(AsyncDriverAdapterBase):
|
|
|
438
642
|
"""Handle database-specific exceptions and wrap them appropriately."""
|
|
439
643
|
return OracleAsyncExceptionHandler()
|
|
440
644
|
|
|
441
|
-
async def _try_special_handling(self, cursor: Any, statement: "SQL") -> "
|
|
645
|
+
async def _try_special_handling(self, cursor: Any, statement: "SQL") -> "SQLResult | None":
|
|
442
646
|
"""Hook for Oracle-specific special operations.
|
|
443
647
|
|
|
444
648
|
Oracle doesn't have complex special operations like PostgreSQL COPY,
|
|
@@ -531,9 +735,13 @@ class OracleAsyncDriver(AsyncDriverAdapterBase):
|
|
|
531
735
|
if statement.returns_rows():
|
|
532
736
|
fetched_data = await cursor.fetchall()
|
|
533
737
|
column_names = [col[0] for col in cursor.description or []]
|
|
738
|
+
column_names = _normalize_column_names(column_names, self.driver_features)
|
|
534
739
|
|
|
535
|
-
# Oracle returns tuples - convert to consistent dict format
|
|
536
|
-
data = [
|
|
740
|
+
# Oracle returns tuples - convert to consistent dict format after LOB hydration
|
|
741
|
+
data = []
|
|
742
|
+
for row in fetched_data:
|
|
743
|
+
coerced_row = await _coerce_async_row_values(row)
|
|
744
|
+
data.append(dict(zip(column_names, coerced_row, strict=False)))
|
|
537
745
|
|
|
538
746
|
return self.create_execution_result(
|
|
539
747
|
cursor, selected_data=data, column_names=column_names, data_row_count=len(data), is_select_result=True
|