sqlspec 0.11.1__py3-none-any.whl → 0.12.1__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 +16 -3
- sqlspec/_serialization.py +3 -10
- sqlspec/_sql.py +1147 -0
- sqlspec/_typing.py +343 -41
- sqlspec/adapters/adbc/__init__.py +2 -6
- sqlspec/adapters/adbc/config.py +474 -149
- sqlspec/adapters/adbc/driver.py +330 -621
- sqlspec/adapters/aiosqlite/__init__.py +2 -6
- sqlspec/adapters/aiosqlite/config.py +143 -57
- sqlspec/adapters/aiosqlite/driver.py +269 -431
- sqlspec/adapters/asyncmy/__init__.py +3 -8
- sqlspec/adapters/asyncmy/config.py +247 -202
- sqlspec/adapters/asyncmy/driver.py +218 -436
- sqlspec/adapters/asyncpg/__init__.py +4 -7
- sqlspec/adapters/asyncpg/config.py +329 -176
- sqlspec/adapters/asyncpg/driver.py +417 -487
- sqlspec/adapters/bigquery/__init__.py +2 -2
- sqlspec/adapters/bigquery/config.py +407 -0
- sqlspec/adapters/bigquery/driver.py +600 -553
- sqlspec/adapters/duckdb/__init__.py +4 -1
- sqlspec/adapters/duckdb/config.py +432 -321
- sqlspec/adapters/duckdb/driver.py +392 -406
- sqlspec/adapters/oracledb/__init__.py +3 -8
- sqlspec/adapters/oracledb/config.py +625 -0
- sqlspec/adapters/oracledb/driver.py +548 -921
- sqlspec/adapters/psqlpy/__init__.py +4 -7
- sqlspec/adapters/psqlpy/config.py +372 -203
- sqlspec/adapters/psqlpy/driver.py +197 -533
- sqlspec/adapters/psycopg/__init__.py +3 -8
- sqlspec/adapters/psycopg/config.py +725 -0
- sqlspec/adapters/psycopg/driver.py +734 -694
- sqlspec/adapters/sqlite/__init__.py +2 -6
- sqlspec/adapters/sqlite/config.py +146 -81
- sqlspec/adapters/sqlite/driver.py +242 -405
- sqlspec/base.py +220 -784
- sqlspec/config.py +354 -0
- sqlspec/driver/__init__.py +22 -0
- sqlspec/driver/_async.py +252 -0
- sqlspec/driver/_common.py +338 -0
- sqlspec/driver/_sync.py +261 -0
- sqlspec/driver/mixins/__init__.py +17 -0
- sqlspec/driver/mixins/_pipeline.py +523 -0
- sqlspec/driver/mixins/_result_utils.py +122 -0
- sqlspec/driver/mixins/_sql_translator.py +35 -0
- sqlspec/driver/mixins/_storage.py +993 -0
- sqlspec/driver/mixins/_type_coercion.py +131 -0
- sqlspec/exceptions.py +299 -7
- sqlspec/extensions/aiosql/__init__.py +10 -0
- sqlspec/extensions/aiosql/adapter.py +474 -0
- sqlspec/extensions/litestar/__init__.py +1 -6
- sqlspec/extensions/litestar/_utils.py +1 -5
- sqlspec/extensions/litestar/config.py +5 -6
- sqlspec/extensions/litestar/handlers.py +13 -12
- sqlspec/extensions/litestar/plugin.py +22 -24
- sqlspec/extensions/litestar/providers.py +37 -55
- sqlspec/loader.py +528 -0
- sqlspec/service/__init__.py +3 -0
- sqlspec/service/base.py +24 -0
- sqlspec/service/pagination.py +26 -0
- sqlspec/statement/__init__.py +21 -0
- sqlspec/statement/builder/__init__.py +54 -0
- sqlspec/statement/builder/_ddl_utils.py +119 -0
- sqlspec/statement/builder/_parsing_utils.py +135 -0
- sqlspec/statement/builder/base.py +328 -0
- sqlspec/statement/builder/ddl.py +1379 -0
- sqlspec/statement/builder/delete.py +80 -0
- sqlspec/statement/builder/insert.py +274 -0
- sqlspec/statement/builder/merge.py +95 -0
- sqlspec/statement/builder/mixins/__init__.py +65 -0
- sqlspec/statement/builder/mixins/_aggregate_functions.py +151 -0
- sqlspec/statement/builder/mixins/_case_builder.py +91 -0
- sqlspec/statement/builder/mixins/_common_table_expr.py +91 -0
- sqlspec/statement/builder/mixins/_delete_from.py +34 -0
- sqlspec/statement/builder/mixins/_from.py +61 -0
- sqlspec/statement/builder/mixins/_group_by.py +119 -0
- sqlspec/statement/builder/mixins/_having.py +35 -0
- sqlspec/statement/builder/mixins/_insert_from_select.py +48 -0
- sqlspec/statement/builder/mixins/_insert_into.py +36 -0
- sqlspec/statement/builder/mixins/_insert_values.py +69 -0
- sqlspec/statement/builder/mixins/_join.py +110 -0
- sqlspec/statement/builder/mixins/_limit_offset.py +53 -0
- sqlspec/statement/builder/mixins/_merge_clauses.py +405 -0
- sqlspec/statement/builder/mixins/_order_by.py +46 -0
- sqlspec/statement/builder/mixins/_pivot.py +82 -0
- sqlspec/statement/builder/mixins/_returning.py +37 -0
- sqlspec/statement/builder/mixins/_select_columns.py +60 -0
- sqlspec/statement/builder/mixins/_set_ops.py +122 -0
- sqlspec/statement/builder/mixins/_unpivot.py +80 -0
- sqlspec/statement/builder/mixins/_update_from.py +54 -0
- sqlspec/statement/builder/mixins/_update_set.py +91 -0
- sqlspec/statement/builder/mixins/_update_table.py +29 -0
- sqlspec/statement/builder/mixins/_where.py +374 -0
- sqlspec/statement/builder/mixins/_window_functions.py +86 -0
- sqlspec/statement/builder/protocols.py +20 -0
- sqlspec/statement/builder/select.py +206 -0
- sqlspec/statement/builder/update.py +178 -0
- sqlspec/statement/filters.py +571 -0
- sqlspec/statement/parameters.py +736 -0
- sqlspec/statement/pipelines/__init__.py +67 -0
- sqlspec/statement/pipelines/analyzers/__init__.py +9 -0
- sqlspec/statement/pipelines/analyzers/_analyzer.py +649 -0
- sqlspec/statement/pipelines/base.py +315 -0
- sqlspec/statement/pipelines/context.py +119 -0
- sqlspec/statement/pipelines/result_types.py +41 -0
- sqlspec/statement/pipelines/transformers/__init__.py +8 -0
- sqlspec/statement/pipelines/transformers/_expression_simplifier.py +256 -0
- sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +623 -0
- sqlspec/statement/pipelines/transformers/_remove_comments.py +66 -0
- sqlspec/statement/pipelines/transformers/_remove_hints.py +81 -0
- sqlspec/statement/pipelines/validators/__init__.py +23 -0
- sqlspec/statement/pipelines/validators/_dml_safety.py +275 -0
- sqlspec/statement/pipelines/validators/_parameter_style.py +297 -0
- sqlspec/statement/pipelines/validators/_performance.py +703 -0
- sqlspec/statement/pipelines/validators/_security.py +990 -0
- sqlspec/statement/pipelines/validators/base.py +67 -0
- sqlspec/statement/result.py +527 -0
- sqlspec/statement/splitter.py +701 -0
- sqlspec/statement/sql.py +1198 -0
- sqlspec/storage/__init__.py +15 -0
- sqlspec/storage/backends/__init__.py +0 -0
- sqlspec/storage/backends/base.py +166 -0
- sqlspec/storage/backends/fsspec.py +315 -0
- sqlspec/storage/backends/obstore.py +464 -0
- sqlspec/storage/protocol.py +170 -0
- sqlspec/storage/registry.py +315 -0
- sqlspec/typing.py +157 -36
- sqlspec/utils/correlation.py +155 -0
- sqlspec/utils/deprecation.py +3 -6
- sqlspec/utils/fixtures.py +6 -11
- sqlspec/utils/logging.py +135 -0
- sqlspec/utils/module_loader.py +45 -43
- sqlspec/utils/serializers.py +4 -0
- sqlspec/utils/singleton.py +6 -8
- sqlspec/utils/sync_tools.py +15 -27
- sqlspec/utils/text.py +58 -26
- {sqlspec-0.11.1.dist-info → sqlspec-0.12.1.dist-info}/METADATA +97 -26
- sqlspec-0.12.1.dist-info/RECORD +145 -0
- sqlspec/adapters/bigquery/config/__init__.py +0 -3
- sqlspec/adapters/bigquery/config/_common.py +0 -40
- sqlspec/adapters/bigquery/config/_sync.py +0 -87
- sqlspec/adapters/oracledb/config/__init__.py +0 -9
- sqlspec/adapters/oracledb/config/_asyncio.py +0 -186
- sqlspec/adapters/oracledb/config/_common.py +0 -131
- sqlspec/adapters/oracledb/config/_sync.py +0 -186
- sqlspec/adapters/psycopg/config/__init__.py +0 -19
- sqlspec/adapters/psycopg/config/_async.py +0 -169
- sqlspec/adapters/psycopg/config/_common.py +0 -56
- sqlspec/adapters/psycopg/config/_sync.py +0 -168
- sqlspec/filters.py +0 -331
- sqlspec/mixins.py +0 -305
- sqlspec/statement.py +0 -378
- sqlspec-0.11.1.dist-info/RECORD +0 -69
- {sqlspec-0.11.1.dist-info → sqlspec-0.12.1.dist-info}/WHEEL +0 -0
- {sqlspec-0.11.1.dist-info → sqlspec-0.12.1.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.11.1.dist-info → sqlspec-0.12.1.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,954 +1,581 @@
|
|
|
1
|
-
import
|
|
1
|
+
from collections.abc import AsyncGenerator, Generator
|
|
2
2
|
from contextlib import asynccontextmanager, contextmanager
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Any, ClassVar, Optional, Union, cast
|
|
4
4
|
|
|
5
5
|
from oracledb import AsyncConnection, AsyncCursor, Connection, Cursor
|
|
6
|
+
from sqlglot.dialects.dialect import DialectType
|
|
6
7
|
|
|
7
|
-
from sqlspec.
|
|
8
|
-
from sqlspec.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
ResultConverter,
|
|
8
|
+
from sqlspec.driver import AsyncDriverAdapterProtocol, SyncDriverAdapterProtocol
|
|
9
|
+
from sqlspec.driver.mixins import (
|
|
10
|
+
AsyncPipelinedExecutionMixin,
|
|
11
|
+
AsyncStorageMixin,
|
|
12
12
|
SQLTranslatorMixin,
|
|
13
|
-
|
|
13
|
+
SyncPipelinedExecutionMixin,
|
|
14
|
+
SyncStorageMixin,
|
|
15
|
+
ToSchemaMixin,
|
|
16
|
+
TypeCoercionMixin,
|
|
14
17
|
)
|
|
15
|
-
from sqlspec.statement import
|
|
16
|
-
from sqlspec.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
from sqlspec.typing import ModelDTOT
|
|
18
|
+
from sqlspec.statement.parameters import ParameterStyle
|
|
19
|
+
from sqlspec.statement.result import ArrowResult, DMLResultDict, ScriptResultDict, SelectResultDict, SQLResult
|
|
20
|
+
from sqlspec.statement.sql import SQL, SQLConfig
|
|
21
|
+
from sqlspec.typing import DictRow, ModelDTOT, RowT, SQLParameterType
|
|
22
|
+
from sqlspec.utils.logging import get_logger
|
|
23
|
+
from sqlspec.utils.sync_tools import ensure_async_
|
|
22
24
|
|
|
23
25
|
__all__ = ("OracleAsyncConnection", "OracleAsyncDriver", "OracleSyncConnection", "OracleSyncDriver")
|
|
24
26
|
|
|
25
27
|
OracleSyncConnection = Connection
|
|
26
28
|
OracleAsyncConnection = AsyncConnection
|
|
27
29
|
|
|
28
|
-
logger =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
combined_filters_list.insert(0, parameters)
|
|
30
|
+
logger = get_logger("adapters.oracledb")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _process_oracle_parameters(params: Any) -> Any:
|
|
34
|
+
"""Process parameters to handle Oracle-specific requirements.
|
|
35
|
+
|
|
36
|
+
- Extract values from TypedParameter objects
|
|
37
|
+
- Convert tuples to lists (Oracle doesn't support tuples)
|
|
38
|
+
"""
|
|
39
|
+
from sqlspec.statement.parameters import TypedParameter
|
|
40
|
+
|
|
41
|
+
if params is None:
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
# Handle TypedParameter objects
|
|
45
|
+
if isinstance(params, TypedParameter):
|
|
46
|
+
return _process_oracle_parameters(params.value)
|
|
47
|
+
|
|
48
|
+
if isinstance(params, tuple):
|
|
49
|
+
# Convert single tuple to list and process each element
|
|
50
|
+
return [_process_oracle_parameters(item) for item in params]
|
|
51
|
+
if isinstance(params, list):
|
|
52
|
+
# Process list of parameter sets
|
|
53
|
+
processed = []
|
|
54
|
+
for param_set in params:
|
|
55
|
+
if isinstance(param_set, tuple):
|
|
56
|
+
# Convert tuple to list and process each element
|
|
57
|
+
processed.append([_process_oracle_parameters(item) for item in param_set])
|
|
58
|
+
elif isinstance(param_set, list):
|
|
59
|
+
# Process each element in the list
|
|
60
|
+
processed.append([_process_oracle_parameters(item) for item in param_set])
|
|
60
61
|
else:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
statement = SQLStatement(sql, data_params_for_statement, kwargs=kwargs, dialect=self.dialect)
|
|
69
|
-
for filter_obj in combined_filters_list:
|
|
70
|
-
statement = statement.apply_filter(filter_obj)
|
|
71
|
-
|
|
72
|
-
processed_sql, processed_params, _ = statement.process()
|
|
73
|
-
if processed_params is None:
|
|
74
|
-
return processed_sql, None
|
|
75
|
-
if isinstance(processed_params, dict):
|
|
76
|
-
return processed_sql, processed_params
|
|
77
|
-
if isinstance(processed_params, (list, tuple)):
|
|
78
|
-
return processed_sql, tuple(processed_params)
|
|
79
|
-
return processed_sql, (processed_params,) # type: ignore[unreachable]
|
|
62
|
+
processed.append(_process_oracle_parameters(param_set))
|
|
63
|
+
return processed
|
|
64
|
+
if isinstance(params, dict):
|
|
65
|
+
# Process dict values
|
|
66
|
+
return {key: _process_oracle_parameters(value) for key, value in params.items()}
|
|
67
|
+
# Return as-is for other types
|
|
68
|
+
return params
|
|
80
69
|
|
|
81
70
|
|
|
82
71
|
class OracleSyncDriver(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
72
|
+
SyncDriverAdapterProtocol[OracleSyncConnection, RowT],
|
|
73
|
+
SQLTranslatorMixin,
|
|
74
|
+
TypeCoercionMixin,
|
|
75
|
+
SyncStorageMixin,
|
|
76
|
+
SyncPipelinedExecutionMixin,
|
|
77
|
+
ToSchemaMixin,
|
|
88
78
|
):
|
|
89
|
-
"""Oracle Sync Driver Adapter."""
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
79
|
+
"""Oracle Sync Driver Adapter. Refactored for new protocol."""
|
|
80
|
+
|
|
81
|
+
dialect: "DialectType" = "oracle"
|
|
82
|
+
supported_parameter_styles: "tuple[ParameterStyle, ...]" = (
|
|
83
|
+
ParameterStyle.NAMED_COLON,
|
|
84
|
+
ParameterStyle.POSITIONAL_COLON,
|
|
85
|
+
)
|
|
86
|
+
default_parameter_style: ParameterStyle = ParameterStyle.NAMED_COLON
|
|
87
|
+
support_native_arrow_export = True
|
|
88
|
+
__slots__ = ()
|
|
89
|
+
|
|
90
|
+
def __init__(
|
|
91
|
+
self,
|
|
92
|
+
connection: OracleSyncConnection,
|
|
93
|
+
config: Optional[SQLConfig] = None,
|
|
94
|
+
default_row_type: type[DictRow] = DictRow,
|
|
95
|
+
) -> None:
|
|
96
|
+
super().__init__(connection=connection, config=config, default_row_type=default_row_type)
|
|
97
|
+
|
|
98
|
+
def _process_parameters(self, parameters: "SQLParameterType") -> "SQLParameterType":
|
|
99
|
+
"""Process parameters to handle Oracle-specific requirements.
|
|
100
|
+
|
|
101
|
+
- Extract values from TypedParameter objects
|
|
102
|
+
- Convert tuples to lists (Oracle doesn't support tuples)
|
|
103
|
+
"""
|
|
104
|
+
return _process_oracle_parameters(parameters)
|
|
95
105
|
|
|
96
|
-
@staticmethod
|
|
97
106
|
@contextmanager
|
|
98
|
-
def
|
|
99
|
-
|
|
107
|
+
def _get_cursor(self, connection: Optional[OracleSyncConnection] = None) -> Generator[Cursor, None, None]:
|
|
108
|
+
conn_to_use = connection or self.connection
|
|
109
|
+
cursor: Cursor = conn_to_use.cursor()
|
|
100
110
|
try:
|
|
101
111
|
yield cursor
|
|
102
112
|
finally:
|
|
103
113
|
cursor.close()
|
|
104
114
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
def
|
|
181
|
-
self,
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
""
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
parameters: The parameters for the query (dict, tuple, list, or None).
|
|
305
|
-
*filters: Statement filters to apply.
|
|
306
|
-
connection: Optional connection override.
|
|
307
|
-
schema_type: Optional type to convert the result to.
|
|
308
|
-
**kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
|
|
309
|
-
|
|
310
|
-
Returns:
|
|
311
|
-
The first value of the first row of the query results.
|
|
312
|
-
"""
|
|
313
|
-
connection = self._connection(connection)
|
|
314
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
315
|
-
|
|
316
|
-
with self._with_cursor(connection) as cursor:
|
|
317
|
-
cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
|
|
318
|
-
result = cursor.fetchone() # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
319
|
-
result = self.check_not_found(result) # pyright: ignore[reportUnknownArgumentType]
|
|
320
|
-
|
|
321
|
-
if schema_type is None:
|
|
322
|
-
return result[0] # pyright: ignore[reportUnknownArgumentType]
|
|
323
|
-
return schema_type(result[0]) # type: ignore[call-arg]
|
|
324
|
-
|
|
325
|
-
@overload
|
|
326
|
-
def select_value_or_none(
|
|
327
|
-
self,
|
|
328
|
-
sql: str,
|
|
329
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
330
|
-
*filters: "StatementFilter",
|
|
331
|
-
connection: "Optional[OracleSyncConnection]" = None,
|
|
332
|
-
schema_type: None = None,
|
|
333
|
-
**kwargs: Any,
|
|
334
|
-
) -> "Optional[Any]": ...
|
|
335
|
-
@overload
|
|
336
|
-
def select_value_or_none(
|
|
337
|
-
self,
|
|
338
|
-
sql: str,
|
|
339
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
340
|
-
*filters: "StatementFilter",
|
|
341
|
-
connection: "Optional[OracleSyncConnection]" = None,
|
|
342
|
-
schema_type: "type[T]",
|
|
343
|
-
**kwargs: Any,
|
|
344
|
-
) -> "Optional[T]": ...
|
|
345
|
-
def select_value_or_none(
|
|
346
|
-
self,
|
|
347
|
-
sql: str,
|
|
348
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
349
|
-
*filters: "StatementFilter",
|
|
350
|
-
connection: "Optional[OracleSyncConnection]" = None,
|
|
351
|
-
schema_type: "Optional[type[T]]" = None,
|
|
352
|
-
**kwargs: Any,
|
|
353
|
-
) -> "Optional[Union[T, Any]]":
|
|
354
|
-
"""Fetch a single value or None if not found.
|
|
355
|
-
|
|
356
|
-
Args:
|
|
357
|
-
sql: The SQL query string.
|
|
358
|
-
parameters: The parameters for the query (dict, tuple, list, or None).
|
|
359
|
-
*filters: Statement filters to apply.
|
|
360
|
-
connection: Optional connection override.
|
|
361
|
-
schema_type: Optional type to convert the result to.
|
|
362
|
-
**kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
|
|
363
|
-
|
|
364
|
-
Returns:
|
|
365
|
-
The first value of the first row of the query results, or None if no results found.
|
|
366
|
-
"""
|
|
367
|
-
connection = self._connection(connection)
|
|
368
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
369
|
-
|
|
370
|
-
with self._with_cursor(connection) as cursor:
|
|
371
|
-
cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
|
|
372
|
-
result = cursor.fetchone() # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
373
|
-
if result is None:
|
|
374
|
-
return None
|
|
375
|
-
|
|
376
|
-
if schema_type is None:
|
|
377
|
-
return result[0] # pyright: ignore[reportUnknownArgumentType]
|
|
378
|
-
return schema_type(result[0]) # type: ignore[call-arg]
|
|
379
|
-
|
|
380
|
-
def insert_update_delete(
|
|
381
|
-
self,
|
|
382
|
-
sql: str,
|
|
383
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
384
|
-
*filters: "StatementFilter",
|
|
385
|
-
connection: "Optional[OracleSyncConnection]" = None,
|
|
386
|
-
**kwargs: Any,
|
|
387
|
-
) -> int:
|
|
388
|
-
"""Execute an insert, update, or delete statement.
|
|
389
|
-
|
|
390
|
-
Args:
|
|
391
|
-
sql: The SQL statement to execute.
|
|
392
|
-
parameters: The parameters for the statement (dict, tuple, list, or None).
|
|
393
|
-
*filters: Statement filters to apply.
|
|
394
|
-
connection: Optional connection override.
|
|
395
|
-
**kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
|
|
396
|
-
|
|
397
|
-
Returns:
|
|
398
|
-
The number of rows affected by the statement.
|
|
399
|
-
"""
|
|
400
|
-
connection = self._connection(connection)
|
|
401
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
402
|
-
|
|
403
|
-
with self._with_cursor(connection) as cursor:
|
|
404
|
-
cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
|
|
405
|
-
return cursor.rowcount # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
406
|
-
|
|
407
|
-
@overload
|
|
408
|
-
def insert_update_delete_returning(
|
|
409
|
-
self,
|
|
410
|
-
sql: str,
|
|
411
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
412
|
-
*filters: "StatementFilter",
|
|
413
|
-
connection: "Optional[OracleSyncConnection]" = None,
|
|
414
|
-
schema_type: None = None,
|
|
415
|
-
**kwargs: Any,
|
|
416
|
-
) -> "dict[str, Any]": ...
|
|
417
|
-
@overload
|
|
418
|
-
def insert_update_delete_returning(
|
|
419
|
-
self,
|
|
420
|
-
sql: str,
|
|
421
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
422
|
-
*filters: "StatementFilter",
|
|
423
|
-
connection: "Optional[OracleSyncConnection]" = None,
|
|
424
|
-
schema_type: "type[ModelDTOT]",
|
|
425
|
-
**kwargs: Any,
|
|
426
|
-
) -> "ModelDTOT": ...
|
|
427
|
-
def insert_update_delete_returning(
|
|
428
|
-
self,
|
|
429
|
-
sql: str,
|
|
430
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
431
|
-
*filters: "StatementFilter",
|
|
432
|
-
connection: "Optional[OracleSyncConnection]" = None,
|
|
433
|
-
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
434
|
-
**kwargs: Any,
|
|
435
|
-
) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
|
|
436
|
-
"""Insert, update, or delete data from the database and return result.
|
|
437
|
-
|
|
438
|
-
Returns:
|
|
439
|
-
The first row of results.
|
|
440
|
-
"""
|
|
441
|
-
connection = self._connection(connection)
|
|
442
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
443
|
-
|
|
444
|
-
with self._with_cursor(connection) as cursor:
|
|
445
|
-
cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
|
|
446
|
-
result = cursor.fetchone() # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
447
|
-
|
|
448
|
-
if result is None:
|
|
449
|
-
return None
|
|
450
|
-
|
|
451
|
-
# Get column names
|
|
452
|
-
column_names = [col[0] for col in cursor.description or []] # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
|
|
453
|
-
|
|
454
|
-
if schema_type is not None:
|
|
455
|
-
return cast("ModelDTOT", schema_type(**dict(zip(column_names, result)))) # pyright: ignore[reportUnknownArgumentType]
|
|
456
|
-
# Always return dictionaries
|
|
457
|
-
return dict(zip(column_names, result)) # pyright: ignore[reportUnknownArgumentType,reportUnknownVariableType]
|
|
458
|
-
|
|
459
|
-
def execute_script(
|
|
460
|
-
self,
|
|
461
|
-
sql: str,
|
|
462
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
463
|
-
connection: "Optional[OracleSyncConnection]" = None,
|
|
464
|
-
**kwargs: Any,
|
|
465
|
-
) -> str:
|
|
466
|
-
"""Execute a SQL script.
|
|
467
|
-
|
|
468
|
-
Args:
|
|
469
|
-
sql: The SQL script to execute.
|
|
470
|
-
parameters: The parameters for the script (dict, tuple, list, or None).
|
|
471
|
-
connection: Optional connection override.
|
|
472
|
-
**kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
|
|
473
|
-
|
|
474
|
-
Returns:
|
|
475
|
-
A success message.
|
|
476
|
-
"""
|
|
477
|
-
connection = self._connection(connection)
|
|
478
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
479
|
-
|
|
480
|
-
with self._with_cursor(connection) as cursor:
|
|
481
|
-
cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
|
|
482
|
-
return str(cursor.rowcount) # pyright: ignore[reportUnknownMemberType]
|
|
483
|
-
|
|
484
|
-
def select_arrow( # pyright: ignore[reportUnknownParameterType]
|
|
485
|
-
self,
|
|
486
|
-
sql: str,
|
|
487
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
488
|
-
*filters: "StatementFilter",
|
|
489
|
-
connection: "Optional[OracleSyncConnection]" = None,
|
|
490
|
-
**kwargs: Any,
|
|
491
|
-
) -> "ArrowTable": # pyright: ignore[reportUnknownVariableType]
|
|
492
|
-
"""Execute a SQL query and return results as an Apache Arrow Table.
|
|
493
|
-
|
|
494
|
-
Returns:
|
|
495
|
-
An Apache Arrow Table containing the query results.
|
|
496
|
-
"""
|
|
497
|
-
|
|
498
|
-
connection = self._connection(connection)
|
|
499
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
500
|
-
results = connection.fetch_df_all(sql, parameters)
|
|
501
|
-
return cast("ArrowTable", ArrowTable.from_arrays(arrays=results.column_arrays(), names=results.column_names())) # pyright: ignore
|
|
502
|
-
|
|
503
|
-
def _connection(self, connection: "Optional[OracleSyncConnection]" = None) -> "OracleSyncConnection":
|
|
504
|
-
"""Get the connection to use for the operation.
|
|
505
|
-
|
|
506
|
-
Args:
|
|
507
|
-
connection: Optional connection to use.
|
|
508
|
-
|
|
509
|
-
Returns:
|
|
510
|
-
The connection to use.
|
|
511
|
-
"""
|
|
512
|
-
return connection or self.connection
|
|
115
|
+
def _execute_statement(
|
|
116
|
+
self, statement: SQL, connection: Optional[OracleSyncConnection] = None, **kwargs: Any
|
|
117
|
+
) -> Union[SelectResultDict, DMLResultDict, ScriptResultDict]:
|
|
118
|
+
if statement.is_script:
|
|
119
|
+
sql, _ = statement.compile(placeholder_style=ParameterStyle.STATIC)
|
|
120
|
+
return self._execute_script(sql, connection=connection, **kwargs)
|
|
121
|
+
|
|
122
|
+
# Determine if we need to convert parameter style
|
|
123
|
+
detected_styles = {p.style for p in statement.parameter_info}
|
|
124
|
+
target_style = self.default_parameter_style
|
|
125
|
+
|
|
126
|
+
# Check if any detected style is not supported
|
|
127
|
+
unsupported_styles = detected_styles - set(self.supported_parameter_styles)
|
|
128
|
+
if unsupported_styles:
|
|
129
|
+
# Convert to default style if we have unsupported styles
|
|
130
|
+
target_style = self.default_parameter_style
|
|
131
|
+
elif detected_styles:
|
|
132
|
+
# Use the first detected style if all are supported
|
|
133
|
+
# Prefer the first supported style found
|
|
134
|
+
for style in detected_styles:
|
|
135
|
+
if style in self.supported_parameter_styles:
|
|
136
|
+
target_style = style
|
|
137
|
+
break
|
|
138
|
+
|
|
139
|
+
if statement.is_many:
|
|
140
|
+
sql, params = statement.compile(placeholder_style=target_style)
|
|
141
|
+
# Process parameters to convert tuples to lists for Oracle
|
|
142
|
+
params = self._process_parameters(params)
|
|
143
|
+
# Oracle doesn't like underscores in bind parameter names
|
|
144
|
+
if isinstance(params, list) and params and isinstance(params[0], dict):
|
|
145
|
+
# Fix the SQL and parameters
|
|
146
|
+
for key in list(params[0].keys()):
|
|
147
|
+
if key.startswith("_arg_"):
|
|
148
|
+
# Remove leading underscore: _arg_0 -> arg0
|
|
149
|
+
new_key = key[1:].replace("_", "")
|
|
150
|
+
sql = sql.replace(f":{key}", f":{new_key}")
|
|
151
|
+
# Update all parameter sets
|
|
152
|
+
for param_set in params:
|
|
153
|
+
if isinstance(param_set, dict) and key in param_set:
|
|
154
|
+
param_set[new_key] = param_set.pop(key)
|
|
155
|
+
return self._execute_many(sql, params, connection=connection, **kwargs)
|
|
156
|
+
|
|
157
|
+
sql, params = statement.compile(placeholder_style=target_style)
|
|
158
|
+
# Oracle doesn't like underscores in bind parameter names
|
|
159
|
+
if isinstance(params, dict):
|
|
160
|
+
# Fix the SQL and parameters
|
|
161
|
+
for key in list(params.keys()):
|
|
162
|
+
if key.startswith("_arg_"):
|
|
163
|
+
# Remove leading underscore: _arg_0 -> arg0
|
|
164
|
+
new_key = key[1:].replace("_", "")
|
|
165
|
+
sql = sql.replace(f":{key}", f":{new_key}")
|
|
166
|
+
params[new_key] = params.pop(key)
|
|
167
|
+
return self._execute(sql, params, statement, connection=connection, **kwargs)
|
|
168
|
+
|
|
169
|
+
def _execute(
|
|
170
|
+
self,
|
|
171
|
+
sql: str,
|
|
172
|
+
parameters: Any,
|
|
173
|
+
statement: SQL,
|
|
174
|
+
connection: Optional[OracleSyncConnection] = None,
|
|
175
|
+
**kwargs: Any,
|
|
176
|
+
) -> Union[SelectResultDict, DMLResultDict]:
|
|
177
|
+
conn = self._connection(connection)
|
|
178
|
+
with self._get_cursor(conn) as cursor:
|
|
179
|
+
# Process parameters to extract values from TypedParameter objects
|
|
180
|
+
processed_params = self._process_parameters(parameters) if parameters else []
|
|
181
|
+
cursor.execute(sql, processed_params)
|
|
182
|
+
|
|
183
|
+
if self.returns_rows(statement.expression):
|
|
184
|
+
fetched_data = cursor.fetchall()
|
|
185
|
+
column_names = [col[0] for col in cursor.description or []]
|
|
186
|
+
return {"data": fetched_data, "column_names": column_names, "rows_affected": cursor.rowcount}
|
|
187
|
+
|
|
188
|
+
return {"rows_affected": cursor.rowcount, "status_message": "OK"}
|
|
189
|
+
|
|
190
|
+
def _execute_many(
|
|
191
|
+
self, sql: str, param_list: Any, connection: Optional[OracleSyncConnection] = None, **kwargs: Any
|
|
192
|
+
) -> DMLResultDict:
|
|
193
|
+
conn = self._connection(connection)
|
|
194
|
+
with self._get_cursor(conn) as cursor:
|
|
195
|
+
# Handle None or empty param_list
|
|
196
|
+
if param_list is None:
|
|
197
|
+
param_list = []
|
|
198
|
+
# Ensure param_list is a list of parameter sets
|
|
199
|
+
elif param_list and not isinstance(param_list, list):
|
|
200
|
+
# Single parameter set, wrap it
|
|
201
|
+
param_list = [param_list]
|
|
202
|
+
elif param_list and not isinstance(param_list[0], (list, tuple, dict)):
|
|
203
|
+
# Already a flat list, likely from incorrect usage
|
|
204
|
+
param_list = [param_list]
|
|
205
|
+
# Parameters have already been processed in _execute_statement
|
|
206
|
+
cursor.executemany(sql, param_list)
|
|
207
|
+
return {"rows_affected": cursor.rowcount, "status_message": "OK"}
|
|
208
|
+
|
|
209
|
+
def _execute_script(
|
|
210
|
+
self, script: str, connection: Optional[OracleSyncConnection] = None, **kwargs: Any
|
|
211
|
+
) -> ScriptResultDict:
|
|
212
|
+
conn = self._connection(connection)
|
|
213
|
+
statements = self._split_script_statements(script, strip_trailing_semicolon=True)
|
|
214
|
+
with self._get_cursor(conn) as cursor:
|
|
215
|
+
for statement in statements:
|
|
216
|
+
if statement and statement.strip():
|
|
217
|
+
cursor.execute(statement.strip())
|
|
218
|
+
|
|
219
|
+
return {"statements_executed": len(statements), "status_message": "SCRIPT EXECUTED"}
|
|
220
|
+
|
|
221
|
+
def _fetch_arrow_table(self, sql: SQL, connection: "Optional[Any]" = None, **kwargs: Any) -> "ArrowResult":
|
|
222
|
+
self._ensure_pyarrow_installed()
|
|
223
|
+
conn = self._connection(connection)
|
|
224
|
+
|
|
225
|
+
# Get SQL and parameters using compile to ensure they match
|
|
226
|
+
# For fetch_arrow_table, we need to use POSITIONAL_COLON style since the SQL has :1 placeholders
|
|
227
|
+
sql_str, params = sql.compile(placeholder_style=ParameterStyle.POSITIONAL_COLON)
|
|
228
|
+
if params is None:
|
|
229
|
+
params = []
|
|
230
|
+
|
|
231
|
+
# Process parameters to extract values from TypedParameter objects
|
|
232
|
+
processed_params = self._process_parameters(params) if params else []
|
|
233
|
+
|
|
234
|
+
oracle_df = conn.fetch_df_all(sql_str, processed_params)
|
|
235
|
+
from pyarrow.interchange.from_dataframe import from_dataframe
|
|
236
|
+
|
|
237
|
+
arrow_table = from_dataframe(oracle_df)
|
|
238
|
+
|
|
239
|
+
return ArrowResult(statement=sql, data=arrow_table)
|
|
240
|
+
|
|
241
|
+
def _ingest_arrow_table(self, table: "Any", table_name: str, mode: str = "append", **options: Any) -> int:
|
|
242
|
+
self._ensure_pyarrow_installed()
|
|
243
|
+
conn = self._connection(None)
|
|
244
|
+
|
|
245
|
+
with self._get_cursor(conn) as cursor:
|
|
246
|
+
if mode == "replace":
|
|
247
|
+
cursor.execute(f"TRUNCATE TABLE {table_name}")
|
|
248
|
+
elif mode == "create":
|
|
249
|
+
msg = "'create' mode is not supported for oracledb ingestion."
|
|
250
|
+
raise NotImplementedError(msg)
|
|
251
|
+
|
|
252
|
+
data_for_ingest = table.to_pylist()
|
|
253
|
+
if not data_for_ingest:
|
|
254
|
+
return 0
|
|
255
|
+
|
|
256
|
+
# Generate column placeholders: :1, :2, etc.
|
|
257
|
+
num_columns = len(data_for_ingest[0])
|
|
258
|
+
placeholders = ", ".join(f":{i + 1}" for i in range(num_columns))
|
|
259
|
+
sql = f"INSERT INTO {table_name} VALUES ({placeholders})"
|
|
260
|
+
cursor.executemany(sql, data_for_ingest)
|
|
261
|
+
return cursor.rowcount
|
|
262
|
+
|
|
263
|
+
def _wrap_select_result(
|
|
264
|
+
self, statement: SQL, result: SelectResultDict, schema_type: Optional[type[ModelDTOT]] = None, **kwargs: Any
|
|
265
|
+
) -> Union[SQLResult[ModelDTOT], SQLResult[RowT]]:
|
|
266
|
+
fetched_tuples = result.get("data", [])
|
|
267
|
+
column_names = result.get("column_names", [])
|
|
268
|
+
|
|
269
|
+
if not fetched_tuples:
|
|
270
|
+
return SQLResult[RowT](statement=statement, data=[], column_names=column_names, operation_type="SELECT")
|
|
271
|
+
|
|
272
|
+
rows_as_dicts: list[dict[str, Any]] = [dict(zip(column_names, row_tuple)) for row_tuple in fetched_tuples]
|
|
273
|
+
|
|
274
|
+
if schema_type:
|
|
275
|
+
converted_data = self.to_schema(rows_as_dicts, schema_type=schema_type)
|
|
276
|
+
return SQLResult[ModelDTOT](
|
|
277
|
+
statement=statement, data=list(converted_data), column_names=column_names, operation_type="SELECT"
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
return SQLResult[RowT](
|
|
281
|
+
statement=statement, data=rows_as_dicts, column_names=column_names, operation_type="SELECT"
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
def _wrap_execute_result(
|
|
285
|
+
self, statement: SQL, result: Union[DMLResultDict, ScriptResultDict], **kwargs: Any
|
|
286
|
+
) -> SQLResult[RowT]:
|
|
287
|
+
operation_type = "UNKNOWN"
|
|
288
|
+
if statement.expression:
|
|
289
|
+
operation_type = str(statement.expression.key).upper()
|
|
290
|
+
|
|
291
|
+
if "statements_executed" in result:
|
|
292
|
+
script_result = cast("ScriptResultDict", result)
|
|
293
|
+
return SQLResult[RowT](
|
|
294
|
+
statement=statement,
|
|
295
|
+
data=[],
|
|
296
|
+
rows_affected=0,
|
|
297
|
+
operation_type="SCRIPT",
|
|
298
|
+
metadata={
|
|
299
|
+
"status_message": script_result.get("status_message", ""),
|
|
300
|
+
"statements_executed": script_result.get("statements_executed", -1),
|
|
301
|
+
},
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
dml_result = cast("DMLResultDict", result)
|
|
305
|
+
rows_affected = dml_result.get("rows_affected", -1)
|
|
306
|
+
status_message = dml_result.get("status_message", "")
|
|
307
|
+
return SQLResult[RowT](
|
|
308
|
+
statement=statement,
|
|
309
|
+
data=[],
|
|
310
|
+
rows_affected=rows_affected,
|
|
311
|
+
operation_type=operation_type,
|
|
312
|
+
metadata={"status_message": status_message},
|
|
313
|
+
)
|
|
513
314
|
|
|
514
315
|
|
|
515
316
|
class OracleAsyncDriver(
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
317
|
+
AsyncDriverAdapterProtocol[OracleAsyncConnection, RowT],
|
|
318
|
+
SQLTranslatorMixin,
|
|
319
|
+
TypeCoercionMixin,
|
|
320
|
+
AsyncStorageMixin,
|
|
321
|
+
AsyncPipelinedExecutionMixin,
|
|
322
|
+
ToSchemaMixin,
|
|
521
323
|
):
|
|
522
|
-
"""Oracle Async Driver Adapter."""
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
324
|
+
"""Oracle Async Driver Adapter. Refactored for new protocol."""
|
|
325
|
+
|
|
326
|
+
dialect: DialectType = "oracle"
|
|
327
|
+
supported_parameter_styles: "tuple[ParameterStyle, ...]" = (
|
|
328
|
+
ParameterStyle.NAMED_COLON,
|
|
329
|
+
ParameterStyle.POSITIONAL_COLON,
|
|
330
|
+
)
|
|
331
|
+
default_parameter_style: ParameterStyle = ParameterStyle.NAMED_COLON
|
|
332
|
+
__supports_arrow__: ClassVar[bool] = True
|
|
333
|
+
__supports_parquet__: ClassVar[bool] = False
|
|
334
|
+
__slots__ = ()
|
|
335
|
+
|
|
336
|
+
def __init__(
|
|
337
|
+
self,
|
|
338
|
+
connection: OracleAsyncConnection,
|
|
339
|
+
config: "Optional[SQLConfig]" = None,
|
|
340
|
+
default_row_type: "type[DictRow]" = DictRow,
|
|
341
|
+
) -> None:
|
|
342
|
+
super().__init__(connection=connection, config=config, default_row_type=default_row_type)
|
|
343
|
+
|
|
344
|
+
def _process_parameters(self, parameters: "SQLParameterType") -> "SQLParameterType":
|
|
345
|
+
"""Process parameters to handle Oracle-specific requirements.
|
|
346
|
+
|
|
347
|
+
- Extract values from TypedParameter objects
|
|
348
|
+
- Convert tuples to lists (Oracle doesn't support tuples)
|
|
349
|
+
"""
|
|
350
|
+
return _process_oracle_parameters(parameters)
|
|
528
351
|
|
|
529
|
-
@staticmethod
|
|
530
352
|
@asynccontextmanager
|
|
531
|
-
async def
|
|
532
|
-
|
|
353
|
+
async def _get_cursor(
|
|
354
|
+
self, connection: Optional[OracleAsyncConnection] = None
|
|
355
|
+
) -> AsyncGenerator[AsyncCursor, None]:
|
|
356
|
+
conn_to_use = connection or self.connection
|
|
357
|
+
cursor: AsyncCursor = conn_to_use.cursor()
|
|
533
358
|
try:
|
|
534
359
|
yield cursor
|
|
535
360
|
finally:
|
|
536
|
-
cursor.close()
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
async def
|
|
659
|
-
self
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
return
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
sql: str,
|
|
758
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
759
|
-
*filters: "StatementFilter",
|
|
760
|
-
connection: "Optional[OracleAsyncConnection]" = None,
|
|
761
|
-
schema_type: None = None,
|
|
762
|
-
**kwargs: Any,
|
|
763
|
-
) -> "Optional[Any]": ...
|
|
764
|
-
@overload
|
|
765
|
-
async def select_value_or_none(
|
|
766
|
-
self,
|
|
767
|
-
sql: str,
|
|
768
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
769
|
-
*filters: "StatementFilter",
|
|
770
|
-
connection: "Optional[OracleAsyncConnection]" = None,
|
|
771
|
-
schema_type: "type[T]",
|
|
772
|
-
**kwargs: Any,
|
|
773
|
-
) -> "Optional[T]": ...
|
|
774
|
-
async def select_value_or_none(
|
|
775
|
-
self,
|
|
776
|
-
sql: str,
|
|
777
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
778
|
-
*filters: "StatementFilter",
|
|
779
|
-
connection: "Optional[OracleAsyncConnection]" = None,
|
|
780
|
-
schema_type: "Optional[type[T]]" = None,
|
|
781
|
-
**kwargs: Any,
|
|
782
|
-
) -> "Optional[Union[T, Any]]":
|
|
783
|
-
"""Fetch a single value or None if not found.
|
|
784
|
-
|
|
785
|
-
Args:
|
|
786
|
-
sql: The SQL query string.
|
|
787
|
-
parameters: The parameters for the query (dict, tuple, list, or None).
|
|
788
|
-
*filters: Statement filters to apply.
|
|
789
|
-
connection: Optional connection override.
|
|
790
|
-
schema_type: Optional type to convert the result to.
|
|
791
|
-
**kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
|
|
792
|
-
|
|
793
|
-
Returns:
|
|
794
|
-
The first value of the first row of the query results, or None if no results found.
|
|
795
|
-
"""
|
|
796
|
-
connection = self._connection(connection)
|
|
797
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
798
|
-
|
|
799
|
-
async with self._with_cursor(connection) as cursor:
|
|
800
|
-
await cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
|
|
801
|
-
result = await cursor.fetchone() # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
802
|
-
if result is None:
|
|
803
|
-
return None
|
|
804
|
-
|
|
805
|
-
if schema_type is None:
|
|
806
|
-
return result[0] # pyright: ignore[reportUnknownArgumentType]
|
|
807
|
-
return schema_type(result[0]) # type: ignore[call-arg]
|
|
808
|
-
|
|
809
|
-
async def insert_update_delete(
|
|
810
|
-
self,
|
|
811
|
-
sql: str,
|
|
812
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
813
|
-
*filters: "StatementFilter",
|
|
814
|
-
connection: "Optional[OracleAsyncConnection]" = None,
|
|
815
|
-
**kwargs: Any,
|
|
816
|
-
) -> int:
|
|
817
|
-
"""Insert, update, or delete data from the database.
|
|
818
|
-
|
|
819
|
-
Args:
|
|
820
|
-
sql: The SQL statement to execute.
|
|
821
|
-
parameters: The parameters for the statement (dict, tuple, list, or None).
|
|
822
|
-
*filters: Statement filters to apply.
|
|
823
|
-
connection: Optional connection override.
|
|
824
|
-
**kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
|
|
825
|
-
|
|
826
|
-
Returns:
|
|
827
|
-
Row count affected by the operation.
|
|
828
|
-
"""
|
|
829
|
-
connection = self._connection(connection)
|
|
830
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
831
|
-
|
|
832
|
-
async with self._with_cursor(connection) as cursor:
|
|
833
|
-
await cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
|
|
834
|
-
return cursor.rowcount # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
|
|
835
|
-
|
|
836
|
-
@overload
|
|
837
|
-
async def insert_update_delete_returning(
|
|
838
|
-
self,
|
|
839
|
-
sql: str,
|
|
840
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
841
|
-
*filters: "StatementFilter",
|
|
842
|
-
connection: "Optional[OracleAsyncConnection]" = None,
|
|
843
|
-
schema_type: None = None,
|
|
844
|
-
**kwargs: Any,
|
|
845
|
-
) -> "dict[str, Any]": ...
|
|
846
|
-
@overload
|
|
847
|
-
async def insert_update_delete_returning(
|
|
848
|
-
self,
|
|
849
|
-
sql: str,
|
|
850
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
851
|
-
*filters: "StatementFilter",
|
|
852
|
-
connection: "Optional[OracleAsyncConnection]" = None,
|
|
853
|
-
schema_type: "type[ModelDTOT]",
|
|
854
|
-
**kwargs: Any,
|
|
855
|
-
) -> "ModelDTOT": ...
|
|
856
|
-
async def insert_update_delete_returning(
|
|
857
|
-
self,
|
|
858
|
-
sql: str,
|
|
859
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
860
|
-
*filters: "StatementFilter",
|
|
861
|
-
connection: "Optional[OracleAsyncConnection]" = None,
|
|
862
|
-
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
863
|
-
**kwargs: Any,
|
|
864
|
-
) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
|
|
865
|
-
"""Insert, update, or delete data from the database and return result.
|
|
866
|
-
|
|
867
|
-
Args:
|
|
868
|
-
sql: The SQL statement with RETURNING clause.
|
|
869
|
-
parameters: The parameters for the statement (dict, tuple, list, or None).
|
|
870
|
-
*filters: Statement filters to apply.
|
|
871
|
-
connection: Optional connection override.
|
|
872
|
-
schema_type: Optional schema class for the result.
|
|
873
|
-
**kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
|
|
874
|
-
|
|
875
|
-
Returns:
|
|
876
|
-
The returned row data, as either a model instance or dictionary.
|
|
877
|
-
"""
|
|
878
|
-
connection = self._connection(connection)
|
|
879
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
880
|
-
|
|
881
|
-
async with self._with_cursor(connection) as cursor:
|
|
882
|
-
await cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
|
|
883
|
-
result = await cursor.fetchone() # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
|
|
884
|
-
if result is None:
|
|
885
|
-
return None
|
|
886
|
-
|
|
887
|
-
# Get column names
|
|
888
|
-
column_names = [col[0] for col in cursor.description or []] # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
|
|
889
|
-
|
|
890
|
-
if schema_type is not None:
|
|
891
|
-
return cast("ModelDTOT", schema_type(**dict(zip(column_names, result)))) # pyright: ignore[reportUnknownArgumentType]
|
|
892
|
-
return dict(zip(column_names, result)) # pyright: ignore[reportUnknownArgumentType,reportUnknownVariableType]
|
|
893
|
-
|
|
894
|
-
async def execute_script(
|
|
895
|
-
self,
|
|
896
|
-
sql: str,
|
|
897
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
898
|
-
connection: "Optional[OracleAsyncConnection]" = None,
|
|
899
|
-
**kwargs: Any,
|
|
900
|
-
) -> str:
|
|
901
|
-
"""Execute a SQL script.
|
|
902
|
-
|
|
903
|
-
Args:
|
|
904
|
-
sql: The SQL script to execute.
|
|
905
|
-
parameters: The parameters for the script (dict, tuple, list, or None).
|
|
906
|
-
connection: Optional connection override.
|
|
907
|
-
**kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
|
|
908
|
-
|
|
909
|
-
Returns:
|
|
910
|
-
A success message.
|
|
911
|
-
"""
|
|
912
|
-
connection = self._connection(connection)
|
|
913
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
914
|
-
|
|
915
|
-
async with self._with_cursor(connection) as cursor:
|
|
916
|
-
await cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
|
|
917
|
-
return str(cursor.rowcount) # pyright: ignore[reportUnknownMemberType]
|
|
918
|
-
|
|
919
|
-
async def select_arrow(
|
|
920
|
-
self,
|
|
921
|
-
sql: str,
|
|
922
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
923
|
-
*filters: "StatementFilter",
|
|
924
|
-
connection: "Optional[OracleAsyncConnection]" = None,
|
|
925
|
-
**kwargs: Any,
|
|
926
|
-
) -> "ArrowTable": # pyright: ignore[reportUnknownVariableType]
|
|
927
|
-
"""Execute a SQL query asynchronously and return results as an Apache Arrow Table.
|
|
928
|
-
|
|
929
|
-
Args:
|
|
930
|
-
sql: The SQL query string.
|
|
931
|
-
parameters: Parameters for the query.
|
|
932
|
-
filters: Statement filters to apply.
|
|
933
|
-
connection: Optional connection override.
|
|
934
|
-
**kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
|
|
935
|
-
|
|
936
|
-
Returns:
|
|
937
|
-
An Apache Arrow Table containing the query results.
|
|
938
|
-
"""
|
|
939
|
-
|
|
940
|
-
connection = self._connection(connection)
|
|
941
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
942
|
-
results = await connection.fetch_df_all(sql, parameters)
|
|
943
|
-
return ArrowTable.from_arrays(arrays=results.column_arrays(), names=results.column_names()) # pyright: ignore
|
|
944
|
-
|
|
945
|
-
def _connection(self, connection: "Optional[OracleAsyncConnection]" = None) -> "OracleAsyncConnection":
|
|
946
|
-
"""Get the connection to use for the operation.
|
|
947
|
-
|
|
948
|
-
Args:
|
|
949
|
-
connection: Optional connection to use.
|
|
950
|
-
|
|
951
|
-
Returns:
|
|
952
|
-
The connection to use.
|
|
953
|
-
"""
|
|
954
|
-
return connection or self.connection
|
|
361
|
+
await ensure_async_(cursor.close)()
|
|
362
|
+
|
|
363
|
+
async def _execute_statement(
|
|
364
|
+
self, statement: SQL, connection: Optional[OracleAsyncConnection] = None, **kwargs: Any
|
|
365
|
+
) -> Union[SelectResultDict, DMLResultDict, ScriptResultDict]:
|
|
366
|
+
if statement.is_script:
|
|
367
|
+
sql, _ = statement.compile(placeholder_style=ParameterStyle.STATIC)
|
|
368
|
+
return await self._execute_script(sql, connection=connection, **kwargs)
|
|
369
|
+
|
|
370
|
+
# Determine if we need to convert parameter style
|
|
371
|
+
detected_styles = {p.style for p in statement.parameter_info}
|
|
372
|
+
target_style = self.default_parameter_style
|
|
373
|
+
|
|
374
|
+
# Check if any detected style is not supported
|
|
375
|
+
unsupported_styles = detected_styles - set(self.supported_parameter_styles)
|
|
376
|
+
if unsupported_styles:
|
|
377
|
+
# Convert to default style if we have unsupported styles
|
|
378
|
+
target_style = self.default_parameter_style
|
|
379
|
+
elif detected_styles:
|
|
380
|
+
# Use the first detected style if all are supported
|
|
381
|
+
# Prefer the first supported style found
|
|
382
|
+
for style in detected_styles:
|
|
383
|
+
if style in self.supported_parameter_styles:
|
|
384
|
+
target_style = style
|
|
385
|
+
break
|
|
386
|
+
|
|
387
|
+
if statement.is_many:
|
|
388
|
+
sql, params = statement.compile(placeholder_style=target_style)
|
|
389
|
+
# Process parameters to convert tuples to lists for Oracle
|
|
390
|
+
params = self._process_parameters(params)
|
|
391
|
+
# Oracle doesn't like underscores in bind parameter names
|
|
392
|
+
if isinstance(params, list) and params and isinstance(params[0], dict):
|
|
393
|
+
# Fix the SQL and parameters
|
|
394
|
+
for key in list(params[0].keys()):
|
|
395
|
+
if key.startswith("_arg_"):
|
|
396
|
+
# Remove leading underscore: _arg_0 -> arg0
|
|
397
|
+
new_key = key[1:].replace("_", "")
|
|
398
|
+
sql = sql.replace(f":{key}", f":{new_key}")
|
|
399
|
+
# Update all parameter sets
|
|
400
|
+
for param_set in params:
|
|
401
|
+
if isinstance(param_set, dict) and key in param_set:
|
|
402
|
+
param_set[new_key] = param_set.pop(key)
|
|
403
|
+
return await self._execute_many(sql, params, connection=connection, **kwargs)
|
|
404
|
+
|
|
405
|
+
sql, params = statement.compile(placeholder_style=target_style)
|
|
406
|
+
# Oracle doesn't like underscores in bind parameter names
|
|
407
|
+
if isinstance(params, dict):
|
|
408
|
+
# Fix the SQL and parameters
|
|
409
|
+
for key in list(params.keys()):
|
|
410
|
+
if key.startswith("_arg_"):
|
|
411
|
+
# Remove leading underscore: _arg_0 -> arg0
|
|
412
|
+
new_key = key[1:].replace("_", "")
|
|
413
|
+
sql = sql.replace(f":{key}", f":{new_key}")
|
|
414
|
+
params[new_key] = params.pop(key)
|
|
415
|
+
return await self._execute(sql, params, statement, connection=connection, **kwargs)
|
|
416
|
+
|
|
417
|
+
async def _execute(
|
|
418
|
+
self,
|
|
419
|
+
sql: str,
|
|
420
|
+
parameters: Any,
|
|
421
|
+
statement: SQL,
|
|
422
|
+
connection: Optional[OracleAsyncConnection] = None,
|
|
423
|
+
**kwargs: Any,
|
|
424
|
+
) -> Union[SelectResultDict, DMLResultDict]:
|
|
425
|
+
conn = self._connection(connection)
|
|
426
|
+
async with self._get_cursor(conn) as cursor:
|
|
427
|
+
if parameters is None:
|
|
428
|
+
await cursor.execute(sql)
|
|
429
|
+
else:
|
|
430
|
+
# Process parameters to extract values from TypedParameter objects
|
|
431
|
+
processed_params = self._process_parameters(parameters)
|
|
432
|
+
await cursor.execute(sql, processed_params)
|
|
433
|
+
|
|
434
|
+
# For SELECT statements, extract data while cursor is open
|
|
435
|
+
if self.returns_rows(statement.expression):
|
|
436
|
+
fetched_data = await cursor.fetchall()
|
|
437
|
+
column_names = [col[0] for col in cursor.description or []]
|
|
438
|
+
result: SelectResultDict = {
|
|
439
|
+
"data": fetched_data,
|
|
440
|
+
"column_names": column_names,
|
|
441
|
+
"rows_affected": cursor.rowcount,
|
|
442
|
+
}
|
|
443
|
+
return result
|
|
444
|
+
dml_result: DMLResultDict = {"rows_affected": cursor.rowcount, "status_message": "OK"}
|
|
445
|
+
return dml_result
|
|
446
|
+
|
|
447
|
+
async def _execute_many(
|
|
448
|
+
self, sql: str, param_list: Any, connection: Optional[OracleAsyncConnection] = None, **kwargs: Any
|
|
449
|
+
) -> DMLResultDict:
|
|
450
|
+
conn = self._connection(connection)
|
|
451
|
+
async with self._get_cursor(conn) as cursor:
|
|
452
|
+
# Handle None or empty param_list
|
|
453
|
+
if param_list is None:
|
|
454
|
+
param_list = []
|
|
455
|
+
# Ensure param_list is a list of parameter sets
|
|
456
|
+
elif param_list and not isinstance(param_list, list):
|
|
457
|
+
# Single parameter set, wrap it
|
|
458
|
+
param_list = [param_list]
|
|
459
|
+
elif param_list and not isinstance(param_list[0], (list, tuple, dict)):
|
|
460
|
+
# Already a flat list, likely from incorrect usage
|
|
461
|
+
param_list = [param_list]
|
|
462
|
+
# Parameters have already been processed in _execute_statement
|
|
463
|
+
await cursor.executemany(sql, param_list)
|
|
464
|
+
result: DMLResultDict = {"rows_affected": cursor.rowcount, "status_message": "OK"}
|
|
465
|
+
return result
|
|
466
|
+
|
|
467
|
+
async def _execute_script(
|
|
468
|
+
self, script: str, connection: Optional[OracleAsyncConnection] = None, **kwargs: Any
|
|
469
|
+
) -> ScriptResultDict:
|
|
470
|
+
conn = self._connection(connection)
|
|
471
|
+
# Oracle doesn't support multi-statement scripts in a single execute
|
|
472
|
+
# The splitter now handles PL/SQL blocks correctly when strip_trailing_semicolon=True
|
|
473
|
+
statements = self._split_script_statements(script, strip_trailing_semicolon=True)
|
|
474
|
+
|
|
475
|
+
async with self._get_cursor(conn) as cursor:
|
|
476
|
+
for statement in statements:
|
|
477
|
+
if statement and statement.strip():
|
|
478
|
+
await cursor.execute(statement.strip())
|
|
479
|
+
|
|
480
|
+
result: ScriptResultDict = {"statements_executed": len(statements), "status_message": "SCRIPT EXECUTED"}
|
|
481
|
+
return result
|
|
482
|
+
|
|
483
|
+
async def _fetch_arrow_table(self, sql: SQL, connection: "Optional[Any]" = None, **kwargs: Any) -> "ArrowResult":
|
|
484
|
+
self._ensure_pyarrow_installed()
|
|
485
|
+
conn = self._connection(connection)
|
|
486
|
+
|
|
487
|
+
# Get SQL and parameters using compile to ensure they match
|
|
488
|
+
# For fetch_arrow_table, we need to use POSITIONAL_COLON style since the SQL has :1 placeholders
|
|
489
|
+
sql_str, params = sql.compile(placeholder_style=ParameterStyle.POSITIONAL_COLON)
|
|
490
|
+
if params is None:
|
|
491
|
+
params = []
|
|
492
|
+
|
|
493
|
+
# Process parameters to extract values from TypedParameter objects
|
|
494
|
+
processed_params = self._process_parameters(params) if params else []
|
|
495
|
+
|
|
496
|
+
oracle_df = await conn.fetch_df_all(sql_str, processed_params)
|
|
497
|
+
from pyarrow.interchange.from_dataframe import from_dataframe
|
|
498
|
+
|
|
499
|
+
arrow_table = from_dataframe(oracle_df)
|
|
500
|
+
|
|
501
|
+
return ArrowResult(statement=sql, data=arrow_table)
|
|
502
|
+
|
|
503
|
+
async def _ingest_arrow_table(self, table: "Any", table_name: str, mode: str = "append", **options: Any) -> int:
|
|
504
|
+
self._ensure_pyarrow_installed()
|
|
505
|
+
conn = self._connection(None)
|
|
506
|
+
|
|
507
|
+
async with self._get_cursor(conn) as cursor:
|
|
508
|
+
if mode == "replace":
|
|
509
|
+
await cursor.execute(f"TRUNCATE TABLE {table_name}")
|
|
510
|
+
elif mode == "create":
|
|
511
|
+
msg = "'create' mode is not supported for oracledb ingestion."
|
|
512
|
+
raise NotImplementedError(msg)
|
|
513
|
+
|
|
514
|
+
data_for_ingest = table.to_pylist()
|
|
515
|
+
if not data_for_ingest:
|
|
516
|
+
return 0
|
|
517
|
+
|
|
518
|
+
# Generate column placeholders: :1, :2, etc.
|
|
519
|
+
num_columns = len(data_for_ingest[0])
|
|
520
|
+
placeholders = ", ".join(f":{i + 1}" for i in range(num_columns))
|
|
521
|
+
sql = f"INSERT INTO {table_name} VALUES ({placeholders})"
|
|
522
|
+
await cursor.executemany(sql, data_for_ingest)
|
|
523
|
+
return cursor.rowcount
|
|
524
|
+
|
|
525
|
+
async def _wrap_select_result(
|
|
526
|
+
self,
|
|
527
|
+
statement: SQL,
|
|
528
|
+
result: SelectResultDict,
|
|
529
|
+
schema_type: Optional[type[ModelDTOT]] = None,
|
|
530
|
+
**kwargs: Any, # pyright: ignore[reportUnusedParameter]
|
|
531
|
+
) -> Union[SQLResult[ModelDTOT], SQLResult[RowT]]:
|
|
532
|
+
fetched_tuples = result["data"]
|
|
533
|
+
column_names = result["column_names"]
|
|
534
|
+
|
|
535
|
+
if not fetched_tuples:
|
|
536
|
+
return SQLResult[RowT](statement=statement, data=[], column_names=column_names, operation_type="SELECT")
|
|
537
|
+
|
|
538
|
+
rows_as_dicts: list[dict[str, Any]] = [dict(zip(column_names, row_tuple)) for row_tuple in fetched_tuples]
|
|
539
|
+
|
|
540
|
+
if schema_type:
|
|
541
|
+
converted_data = self.to_schema(rows_as_dicts, schema_type=schema_type)
|
|
542
|
+
return SQLResult[ModelDTOT](
|
|
543
|
+
statement=statement, data=list(converted_data), column_names=column_names, operation_type="SELECT"
|
|
544
|
+
)
|
|
545
|
+
return SQLResult[RowT](
|
|
546
|
+
statement=statement, data=rows_as_dicts, column_names=column_names, operation_type="SELECT"
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
async def _wrap_execute_result(
|
|
550
|
+
self,
|
|
551
|
+
statement: SQL,
|
|
552
|
+
result: Union[DMLResultDict, ScriptResultDict],
|
|
553
|
+
**kwargs: Any, # pyright: ignore[reportUnusedParameter]
|
|
554
|
+
) -> SQLResult[RowT]:
|
|
555
|
+
operation_type = "UNKNOWN"
|
|
556
|
+
if statement.expression:
|
|
557
|
+
operation_type = str(statement.expression.key).upper()
|
|
558
|
+
|
|
559
|
+
if "statements_executed" in result:
|
|
560
|
+
script_result = cast("ScriptResultDict", result)
|
|
561
|
+
return SQLResult[RowT](
|
|
562
|
+
statement=statement,
|
|
563
|
+
data=[],
|
|
564
|
+
rows_affected=0,
|
|
565
|
+
operation_type="SCRIPT",
|
|
566
|
+
metadata={
|
|
567
|
+
"status_message": script_result.get("status_message", ""),
|
|
568
|
+
"statements_executed": script_result.get("statements_executed", -1),
|
|
569
|
+
},
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
dml_result = cast("DMLResultDict", result)
|
|
573
|
+
rows_affected = dml_result.get("rows_affected", -1)
|
|
574
|
+
status_message = dml_result.get("status_message", "")
|
|
575
|
+
return SQLResult[RowT](
|
|
576
|
+
statement=statement,
|
|
577
|
+
data=[],
|
|
578
|
+
rows_affected=rows_affected,
|
|
579
|
+
operation_type=operation_type,
|
|
580
|
+
metadata={"status_message": status_message},
|
|
581
|
+
)
|