sqlspec 0.13.1__py3-none-any.whl → 0.16.2__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 +71 -8
- sqlspec/__main__.py +12 -0
- sqlspec/__metadata__.py +1 -3
- sqlspec/_serialization.py +1 -2
- sqlspec/_sql.py +930 -136
- sqlspec/_typing.py +278 -142
- sqlspec/adapters/adbc/__init__.py +4 -3
- sqlspec/adapters/adbc/_types.py +12 -0
- sqlspec/adapters/adbc/config.py +116 -285
- sqlspec/adapters/adbc/driver.py +462 -340
- sqlspec/adapters/aiosqlite/__init__.py +18 -3
- sqlspec/adapters/aiosqlite/_types.py +13 -0
- sqlspec/adapters/aiosqlite/config.py +202 -150
- sqlspec/adapters/aiosqlite/driver.py +226 -247
- sqlspec/adapters/asyncmy/__init__.py +18 -3
- sqlspec/adapters/asyncmy/_types.py +12 -0
- sqlspec/adapters/asyncmy/config.py +80 -199
- sqlspec/adapters/asyncmy/driver.py +257 -215
- sqlspec/adapters/asyncpg/__init__.py +19 -4
- sqlspec/adapters/asyncpg/_types.py +17 -0
- sqlspec/adapters/asyncpg/config.py +81 -214
- sqlspec/adapters/asyncpg/driver.py +284 -359
- sqlspec/adapters/bigquery/__init__.py +17 -3
- sqlspec/adapters/bigquery/_types.py +12 -0
- sqlspec/adapters/bigquery/config.py +191 -299
- sqlspec/adapters/bigquery/driver.py +474 -634
- sqlspec/adapters/duckdb/__init__.py +14 -3
- sqlspec/adapters/duckdb/_types.py +12 -0
- sqlspec/adapters/duckdb/config.py +414 -397
- sqlspec/adapters/duckdb/driver.py +342 -393
- sqlspec/adapters/oracledb/__init__.py +19 -5
- sqlspec/adapters/oracledb/_types.py +14 -0
- sqlspec/adapters/oracledb/config.py +123 -458
- sqlspec/adapters/oracledb/driver.py +505 -531
- sqlspec/adapters/psqlpy/__init__.py +13 -3
- sqlspec/adapters/psqlpy/_types.py +11 -0
- sqlspec/adapters/psqlpy/config.py +93 -307
- sqlspec/adapters/psqlpy/driver.py +504 -213
- sqlspec/adapters/psycopg/__init__.py +19 -5
- sqlspec/adapters/psycopg/_types.py +17 -0
- sqlspec/adapters/psycopg/config.py +143 -472
- sqlspec/adapters/psycopg/driver.py +704 -825
- sqlspec/adapters/sqlite/__init__.py +14 -3
- sqlspec/adapters/sqlite/_types.py +11 -0
- sqlspec/adapters/sqlite/config.py +208 -142
- sqlspec/adapters/sqlite/driver.py +263 -278
- sqlspec/base.py +105 -9
- sqlspec/{statement/builder → builder}/__init__.py +12 -14
- sqlspec/{statement/builder/base.py → builder/_base.py} +184 -86
- sqlspec/{statement/builder/column.py → builder/_column.py} +97 -60
- sqlspec/{statement/builder/ddl.py → builder/_ddl.py} +61 -131
- sqlspec/{statement/builder → builder}/_ddl_utils.py +4 -10
- sqlspec/{statement/builder/delete.py → builder/_delete.py} +10 -30
- sqlspec/builder/_insert.py +421 -0
- sqlspec/builder/_merge.py +71 -0
- sqlspec/{statement/builder → builder}/_parsing_utils.py +49 -26
- sqlspec/builder/_select.py +170 -0
- sqlspec/{statement/builder/update.py → builder/_update.py} +16 -20
- sqlspec/builder/mixins/__init__.py +55 -0
- sqlspec/builder/mixins/_cte_and_set_ops.py +222 -0
- sqlspec/{statement/builder/mixins/_delete_from.py → builder/mixins/_delete_operations.py} +8 -1
- sqlspec/builder/mixins/_insert_operations.py +244 -0
- sqlspec/{statement/builder/mixins/_join.py → builder/mixins/_join_operations.py} +45 -13
- sqlspec/{statement/builder/mixins/_merge_clauses.py → builder/mixins/_merge_operations.py} +188 -30
- sqlspec/builder/mixins/_order_limit_operations.py +135 -0
- sqlspec/builder/mixins/_pivot_operations.py +153 -0
- sqlspec/builder/mixins/_select_operations.py +604 -0
- sqlspec/builder/mixins/_update_operations.py +202 -0
- sqlspec/builder/mixins/_where_clause.py +644 -0
- sqlspec/cli.py +247 -0
- sqlspec/config.py +183 -138
- sqlspec/core/__init__.py +63 -0
- sqlspec/core/cache.py +871 -0
- sqlspec/core/compiler.py +417 -0
- sqlspec/core/filters.py +830 -0
- sqlspec/core/hashing.py +310 -0
- sqlspec/core/parameters.py +1237 -0
- sqlspec/core/result.py +677 -0
- sqlspec/{statement → core}/splitter.py +321 -191
- sqlspec/core/statement.py +676 -0
- sqlspec/driver/__init__.py +7 -10
- sqlspec/driver/_async.py +422 -163
- sqlspec/driver/_common.py +545 -287
- sqlspec/driver/_sync.py +426 -160
- sqlspec/driver/mixins/__init__.py +2 -13
- sqlspec/driver/mixins/_result_tools.py +193 -0
- sqlspec/driver/mixins/_sql_translator.py +65 -14
- sqlspec/exceptions.py +5 -252
- sqlspec/extensions/aiosql/adapter.py +93 -96
- sqlspec/extensions/litestar/__init__.py +2 -1
- sqlspec/extensions/litestar/cli.py +48 -0
- sqlspec/extensions/litestar/config.py +0 -1
- sqlspec/extensions/litestar/handlers.py +15 -26
- sqlspec/extensions/litestar/plugin.py +21 -16
- sqlspec/extensions/litestar/providers.py +17 -52
- sqlspec/loader.py +423 -104
- sqlspec/migrations/__init__.py +35 -0
- sqlspec/migrations/base.py +414 -0
- sqlspec/migrations/commands.py +443 -0
- sqlspec/migrations/loaders.py +402 -0
- sqlspec/migrations/runner.py +213 -0
- sqlspec/migrations/tracker.py +140 -0
- sqlspec/migrations/utils.py +129 -0
- sqlspec/protocols.py +51 -186
- sqlspec/storage/__init__.py +1 -1
- sqlspec/storage/backends/base.py +37 -40
- sqlspec/storage/backends/fsspec.py +136 -112
- sqlspec/storage/backends/obstore.py +138 -160
- sqlspec/storage/capabilities.py +5 -4
- sqlspec/storage/registry.py +57 -106
- sqlspec/typing.py +136 -115
- sqlspec/utils/__init__.py +2 -2
- sqlspec/utils/correlation.py +0 -3
- sqlspec/utils/deprecation.py +6 -6
- sqlspec/utils/fixtures.py +6 -6
- sqlspec/utils/logging.py +0 -2
- sqlspec/utils/module_loader.py +7 -12
- sqlspec/utils/singleton.py +0 -1
- sqlspec/utils/sync_tools.py +17 -38
- sqlspec/utils/text.py +12 -51
- sqlspec/utils/type_guards.py +482 -235
- {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/METADATA +7 -2
- sqlspec-0.16.2.dist-info/RECORD +134 -0
- sqlspec-0.16.2.dist-info/entry_points.txt +2 -0
- sqlspec/driver/connection.py +0 -207
- sqlspec/driver/mixins/_csv_writer.py +0 -91
- sqlspec/driver/mixins/_pipeline.py +0 -512
- sqlspec/driver/mixins/_result_utils.py +0 -140
- sqlspec/driver/mixins/_storage.py +0 -926
- sqlspec/driver/mixins/_type_coercion.py +0 -130
- sqlspec/driver/parameters.py +0 -138
- sqlspec/service/__init__.py +0 -4
- sqlspec/service/_util.py +0 -147
- sqlspec/service/base.py +0 -1131
- sqlspec/service/pagination.py +0 -26
- sqlspec/statement/__init__.py +0 -21
- sqlspec/statement/builder/insert.py +0 -288
- sqlspec/statement/builder/merge.py +0 -95
- sqlspec/statement/builder/mixins/__init__.py +0 -65
- sqlspec/statement/builder/mixins/_aggregate_functions.py +0 -250
- sqlspec/statement/builder/mixins/_case_builder.py +0 -91
- sqlspec/statement/builder/mixins/_common_table_expr.py +0 -90
- sqlspec/statement/builder/mixins/_from.py +0 -63
- sqlspec/statement/builder/mixins/_group_by.py +0 -118
- sqlspec/statement/builder/mixins/_having.py +0 -35
- sqlspec/statement/builder/mixins/_insert_from_select.py +0 -47
- sqlspec/statement/builder/mixins/_insert_into.py +0 -36
- sqlspec/statement/builder/mixins/_insert_values.py +0 -67
- sqlspec/statement/builder/mixins/_limit_offset.py +0 -53
- sqlspec/statement/builder/mixins/_order_by.py +0 -46
- sqlspec/statement/builder/mixins/_pivot.py +0 -79
- sqlspec/statement/builder/mixins/_returning.py +0 -37
- sqlspec/statement/builder/mixins/_select_columns.py +0 -61
- sqlspec/statement/builder/mixins/_set_ops.py +0 -122
- sqlspec/statement/builder/mixins/_unpivot.py +0 -77
- sqlspec/statement/builder/mixins/_update_from.py +0 -55
- sqlspec/statement/builder/mixins/_update_set.py +0 -94
- sqlspec/statement/builder/mixins/_update_table.py +0 -29
- sqlspec/statement/builder/mixins/_where.py +0 -401
- sqlspec/statement/builder/mixins/_window_functions.py +0 -86
- sqlspec/statement/builder/select.py +0 -221
- sqlspec/statement/filters.py +0 -596
- sqlspec/statement/parameter_manager.py +0 -220
- sqlspec/statement/parameters.py +0 -867
- sqlspec/statement/pipelines/__init__.py +0 -210
- sqlspec/statement/pipelines/analyzers/__init__.py +0 -9
- sqlspec/statement/pipelines/analyzers/_analyzer.py +0 -646
- sqlspec/statement/pipelines/context.py +0 -115
- sqlspec/statement/pipelines/transformers/__init__.py +0 -7
- sqlspec/statement/pipelines/transformers/_expression_simplifier.py +0 -88
- sqlspec/statement/pipelines/transformers/_literal_parameterizer.py +0 -1247
- sqlspec/statement/pipelines/transformers/_remove_comments_and_hints.py +0 -76
- sqlspec/statement/pipelines/validators/__init__.py +0 -23
- sqlspec/statement/pipelines/validators/_dml_safety.py +0 -290
- sqlspec/statement/pipelines/validators/_parameter_style.py +0 -370
- sqlspec/statement/pipelines/validators/_performance.py +0 -718
- sqlspec/statement/pipelines/validators/_security.py +0 -967
- sqlspec/statement/result.py +0 -435
- sqlspec/statement/sql.py +0 -1704
- sqlspec/statement/sql_compiler.py +0 -140
- sqlspec/utils/cached_property.py +0 -25
- sqlspec-0.13.1.dist-info/RECORD +0 -150
- {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/WHEEL +0 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.13.1.dist-info → sqlspec-0.16.2.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,512 +0,0 @@
|
|
|
1
|
-
"""Pipeline execution mixin for batch database operations.
|
|
2
|
-
|
|
3
|
-
This module provides mixins that enable pipelined execution of SQL statements,
|
|
4
|
-
allowing multiple operations to be sent to the database in a single network
|
|
5
|
-
round-trip for improved performance.
|
|
6
|
-
|
|
7
|
-
The implementation leverages native driver support where available (psycopg, asyncpg, oracledb)
|
|
8
|
-
and provides high-quality simulated behavior for others.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from dataclasses import dataclass
|
|
12
|
-
from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
|
13
|
-
|
|
14
|
-
from sqlspec.exceptions import PipelineExecutionError
|
|
15
|
-
from sqlspec.statement.filters import StatementFilter
|
|
16
|
-
from sqlspec.statement.result import SQLResult
|
|
17
|
-
from sqlspec.statement.sql import SQL
|
|
18
|
-
from sqlspec.utils.logging import get_logger
|
|
19
|
-
from sqlspec.utils.type_guards import (
|
|
20
|
-
is_async_pipeline_capable_driver,
|
|
21
|
-
is_async_transaction_state_capable,
|
|
22
|
-
is_sync_pipeline_capable_driver,
|
|
23
|
-
is_sync_transaction_state_capable,
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
if TYPE_CHECKING:
|
|
27
|
-
from typing import Literal
|
|
28
|
-
|
|
29
|
-
from sqlspec.config import DriverT
|
|
30
|
-
from sqlspec.driver import AsyncDriverAdapterProtocol, SyncDriverAdapterProtocol
|
|
31
|
-
from sqlspec.typing import StatementParameters
|
|
32
|
-
|
|
33
|
-
__all__ = (
|
|
34
|
-
"AsyncPipeline",
|
|
35
|
-
"AsyncPipelinedExecutionMixin",
|
|
36
|
-
"Pipeline",
|
|
37
|
-
"PipelineOperation",
|
|
38
|
-
"SyncPipelinedExecutionMixin",
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
logger = get_logger(__name__)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
@dataclass
|
|
45
|
-
class PipelineOperation:
|
|
46
|
-
"""Container for a queued pipeline operation."""
|
|
47
|
-
|
|
48
|
-
sql: SQL
|
|
49
|
-
operation_type: "Literal['execute', 'execute_many', 'execute_script', 'select']"
|
|
50
|
-
filters: "Optional[list[StatementFilter]]" = None
|
|
51
|
-
original_params: "Optional[Any]" = None
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
class SyncPipelinedExecutionMixin:
|
|
55
|
-
"""Mixin providing pipeline execution for sync drivers."""
|
|
56
|
-
|
|
57
|
-
__slots__ = ()
|
|
58
|
-
|
|
59
|
-
def pipeline(
|
|
60
|
-
self,
|
|
61
|
-
*,
|
|
62
|
-
isolation_level: "Optional[str]" = None,
|
|
63
|
-
continue_on_error: bool = False,
|
|
64
|
-
max_operations: int = 1000,
|
|
65
|
-
**options: Any,
|
|
66
|
-
) -> "Pipeline":
|
|
67
|
-
"""Create a new pipeline for batch operations.
|
|
68
|
-
|
|
69
|
-
Args:
|
|
70
|
-
isolation_level: Transaction isolation level
|
|
71
|
-
continue_on_error: Continue processing after errors
|
|
72
|
-
max_operations: Maximum operations before auto-flush
|
|
73
|
-
**options: Driver-specific pipeline options
|
|
74
|
-
|
|
75
|
-
Returns:
|
|
76
|
-
A new Pipeline instance for queuing operations
|
|
77
|
-
"""
|
|
78
|
-
return Pipeline(
|
|
79
|
-
driver=cast("SyncDriverAdapterProtocol[Any, Any]", self),
|
|
80
|
-
isolation_level=isolation_level,
|
|
81
|
-
continue_on_error=continue_on_error,
|
|
82
|
-
max_operations=max_operations,
|
|
83
|
-
options=options,
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
class AsyncPipelinedExecutionMixin:
|
|
88
|
-
"""Async version of pipeline execution mixin."""
|
|
89
|
-
|
|
90
|
-
__slots__ = ()
|
|
91
|
-
|
|
92
|
-
def pipeline(
|
|
93
|
-
self,
|
|
94
|
-
*,
|
|
95
|
-
isolation_level: "Optional[str]" = None,
|
|
96
|
-
continue_on_error: bool = False,
|
|
97
|
-
max_operations: int = 1000,
|
|
98
|
-
**options: Any,
|
|
99
|
-
) -> "AsyncPipeline":
|
|
100
|
-
"""Create a new async pipeline for batch operations."""
|
|
101
|
-
return AsyncPipeline(
|
|
102
|
-
driver=cast("AsyncDriverAdapterProtocol[Any, Any]", self),
|
|
103
|
-
isolation_level=isolation_level,
|
|
104
|
-
continue_on_error=continue_on_error,
|
|
105
|
-
max_operations=max_operations,
|
|
106
|
-
options=options,
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
class Pipeline:
|
|
111
|
-
"""Synchronous pipeline with enhanced parameter handling."""
|
|
112
|
-
|
|
113
|
-
def __init__(
|
|
114
|
-
self,
|
|
115
|
-
driver: "DriverT", # pyright: ignore
|
|
116
|
-
isolation_level: "Optional[str]" = None,
|
|
117
|
-
continue_on_error: bool = False,
|
|
118
|
-
max_operations: int = 1000,
|
|
119
|
-
options: "Optional[dict[str, Any]]" = None,
|
|
120
|
-
) -> None:
|
|
121
|
-
self.driver = driver
|
|
122
|
-
self.isolation_level = isolation_level
|
|
123
|
-
self.continue_on_error = continue_on_error
|
|
124
|
-
self.max_operations = max_operations
|
|
125
|
-
self.options = options or {}
|
|
126
|
-
self._operations: list[PipelineOperation] = []
|
|
127
|
-
self._results: Optional[list[SQLResult[Any]]] = None
|
|
128
|
-
self._simulation_logged = False
|
|
129
|
-
|
|
130
|
-
def add_execute(
|
|
131
|
-
self, statement: "Union[str, SQL]", /, *parameters: "Union[StatementParameters, StatementFilter]", **kwargs: Any
|
|
132
|
-
) -> "Pipeline":
|
|
133
|
-
"""Add an execute operation to the pipeline.
|
|
134
|
-
|
|
135
|
-
Args:
|
|
136
|
-
statement: SQL statement to execute
|
|
137
|
-
*parameters: Mixed positional args containing parameters and filters
|
|
138
|
-
**kwargs: Named parameters
|
|
139
|
-
|
|
140
|
-
Returns:
|
|
141
|
-
Self for fluent API
|
|
142
|
-
"""
|
|
143
|
-
self._operations.append(
|
|
144
|
-
PipelineOperation(
|
|
145
|
-
sql=SQL(statement, parameters=parameters or None, config=self.driver.config, **kwargs),
|
|
146
|
-
operation_type="execute",
|
|
147
|
-
)
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
if len(self._operations) >= self.max_operations:
|
|
151
|
-
logger.warning("Pipeline auto-flushing at %s operations", len(self._operations))
|
|
152
|
-
self.process()
|
|
153
|
-
|
|
154
|
-
return self
|
|
155
|
-
|
|
156
|
-
def add_select(
|
|
157
|
-
self, statement: "Union[str, SQL]", /, *parameters: "Union[StatementParameters, StatementFilter]", **kwargs: Any
|
|
158
|
-
) -> "Pipeline":
|
|
159
|
-
"""Add a select operation to the pipeline."""
|
|
160
|
-
self._operations.append(
|
|
161
|
-
PipelineOperation(
|
|
162
|
-
sql=SQL(statement, parameters=parameters or None, config=self.driver.config, **kwargs),
|
|
163
|
-
operation_type="select",
|
|
164
|
-
)
|
|
165
|
-
)
|
|
166
|
-
return self
|
|
167
|
-
|
|
168
|
-
def add_execute_many(
|
|
169
|
-
self, statement: "Union[str, SQL]", /, *parameters: "Union[StatementParameters, StatementFilter]", **kwargs: Any
|
|
170
|
-
) -> "Pipeline":
|
|
171
|
-
"""Add batch execution preserving parameter types.
|
|
172
|
-
|
|
173
|
-
Args:
|
|
174
|
-
statement: SQL statement to execute multiple times
|
|
175
|
-
*parameters: First arg should be batch data (list of param sets),
|
|
176
|
-
followed by optional StatementFilter instances
|
|
177
|
-
**kwargs: Not typically used for execute_many
|
|
178
|
-
"""
|
|
179
|
-
# First parameter should be the batch data
|
|
180
|
-
if not parameters or not isinstance(parameters[0], (list, tuple)):
|
|
181
|
-
msg = "execute_many requires a sequence of parameter sets as first parameter"
|
|
182
|
-
raise ValueError(msg)
|
|
183
|
-
|
|
184
|
-
batch_params = parameters[0]
|
|
185
|
-
if isinstance(batch_params, tuple):
|
|
186
|
-
batch_params = list(batch_params)
|
|
187
|
-
sql_obj = SQL(
|
|
188
|
-
statement, parameters=parameters[1:] if len(parameters) > 1 else None, config=self.driver.config, **kwargs
|
|
189
|
-
).as_many(batch_params)
|
|
190
|
-
|
|
191
|
-
self._operations.append(PipelineOperation(sql=sql_obj, operation_type="execute_many"))
|
|
192
|
-
return self
|
|
193
|
-
|
|
194
|
-
def add_execute_script(self, script: "Union[str, SQL]", *filters: StatementFilter, **kwargs: Any) -> "Pipeline":
|
|
195
|
-
"""Add a multi-statement script to the pipeline."""
|
|
196
|
-
if isinstance(script, SQL):
|
|
197
|
-
sql_obj = script.as_script()
|
|
198
|
-
else:
|
|
199
|
-
sql_obj = SQL(script, parameters=filters or None, config=self.driver.config, **kwargs).as_script()
|
|
200
|
-
|
|
201
|
-
self._operations.append(PipelineOperation(sql=sql_obj, operation_type="execute_script"))
|
|
202
|
-
return self
|
|
203
|
-
|
|
204
|
-
def process(self, filters: "Optional[list[StatementFilter]]" = None) -> "list[SQLResult]":
|
|
205
|
-
"""Execute all queued operations.
|
|
206
|
-
|
|
207
|
-
Args:
|
|
208
|
-
filters: Global filters to apply to all operations
|
|
209
|
-
|
|
210
|
-
Returns:
|
|
211
|
-
List of results from all operations
|
|
212
|
-
"""
|
|
213
|
-
if not self._operations:
|
|
214
|
-
return []
|
|
215
|
-
|
|
216
|
-
if filters:
|
|
217
|
-
self._apply_global_filters(filters)
|
|
218
|
-
|
|
219
|
-
if is_sync_pipeline_capable_driver(self.driver):
|
|
220
|
-
results = self.driver._execute_pipeline_native(self._operations, **self.options)
|
|
221
|
-
else:
|
|
222
|
-
results = self._execute_pipeline_simulated()
|
|
223
|
-
|
|
224
|
-
self._results = results
|
|
225
|
-
self._operations.clear()
|
|
226
|
-
return cast("list[SQLResult]", results)
|
|
227
|
-
|
|
228
|
-
def _execute_pipeline_simulated(self) -> "list[SQLResult]":
|
|
229
|
-
"""Enhanced simulation with transaction support and error handling."""
|
|
230
|
-
results: list[SQLResult[Any]] = []
|
|
231
|
-
connection = None
|
|
232
|
-
auto_transaction = False
|
|
233
|
-
|
|
234
|
-
if not self._simulation_logged:
|
|
235
|
-
logger.info(
|
|
236
|
-
"%s using simulated pipeline. Native support: %s",
|
|
237
|
-
self.driver.__class__.__name__,
|
|
238
|
-
self._has_native_support(),
|
|
239
|
-
)
|
|
240
|
-
self._simulation_logged = True
|
|
241
|
-
|
|
242
|
-
try:
|
|
243
|
-
connection = self.driver._connection()
|
|
244
|
-
|
|
245
|
-
if is_sync_transaction_state_capable(connection) and not connection.in_transaction():
|
|
246
|
-
connection.begin()
|
|
247
|
-
auto_transaction = True
|
|
248
|
-
|
|
249
|
-
for i, op in enumerate(self._operations):
|
|
250
|
-
self._execute_single_operation(i, op, results, connection, auto_transaction)
|
|
251
|
-
|
|
252
|
-
# Commit if we started the transaction
|
|
253
|
-
if auto_transaction and is_sync_transaction_state_capable(connection):
|
|
254
|
-
connection.commit()
|
|
255
|
-
|
|
256
|
-
except Exception as e:
|
|
257
|
-
if connection and auto_transaction and is_sync_transaction_state_capable(connection):
|
|
258
|
-
connection.rollback()
|
|
259
|
-
if not isinstance(e, PipelineExecutionError):
|
|
260
|
-
msg = f"Pipeline execution failed: {e}"
|
|
261
|
-
raise PipelineExecutionError(msg) from e
|
|
262
|
-
raise
|
|
263
|
-
|
|
264
|
-
return results
|
|
265
|
-
|
|
266
|
-
def _execute_single_operation(
|
|
267
|
-
self, i: int, op: PipelineOperation, results: "list[SQLResult[Any]]", connection: Any, auto_transaction: bool
|
|
268
|
-
) -> None:
|
|
269
|
-
"""Execute a single pipeline operation with error handling."""
|
|
270
|
-
try:
|
|
271
|
-
# Execute based on operation type
|
|
272
|
-
result: SQLResult[Any]
|
|
273
|
-
if op.operation_type == "execute_script":
|
|
274
|
-
result = cast("SQLResult[Any]", self.driver.execute_script(op.sql, _connection=connection))
|
|
275
|
-
elif op.operation_type == "execute_many":
|
|
276
|
-
result = cast("SQLResult[Any]", self.driver.execute_many(op.sql, _connection=connection))
|
|
277
|
-
else:
|
|
278
|
-
result = cast("SQLResult[Any]", self.driver.execute(op.sql, _connection=connection))
|
|
279
|
-
|
|
280
|
-
result.operation_index = i
|
|
281
|
-
result.pipeline_sql = op.sql
|
|
282
|
-
results.append(result)
|
|
283
|
-
|
|
284
|
-
except Exception as e:
|
|
285
|
-
if self.continue_on_error:
|
|
286
|
-
error_result = SQLResult(
|
|
287
|
-
statement=op.sql, data=[], error=e, operation_index=i, parameters=op.sql.parameters
|
|
288
|
-
)
|
|
289
|
-
results.append(error_result)
|
|
290
|
-
else:
|
|
291
|
-
if auto_transaction and is_sync_transaction_state_capable(connection):
|
|
292
|
-
connection.rollback()
|
|
293
|
-
msg = f"Pipeline failed at operation {i}: {e}"
|
|
294
|
-
raise PipelineExecutionError(
|
|
295
|
-
msg, operation_index=i, partial_results=results, failed_operation=op
|
|
296
|
-
) from e
|
|
297
|
-
|
|
298
|
-
def _apply_global_filters(self, filters: "list[StatementFilter]") -> None:
|
|
299
|
-
"""Apply global filters to all operations."""
|
|
300
|
-
for operation in self._operations:
|
|
301
|
-
if operation.filters is None:
|
|
302
|
-
operation.filters = []
|
|
303
|
-
operation.filters.extend(filters)
|
|
304
|
-
|
|
305
|
-
def _apply_operation_filters(self, sql: SQL, filters: "list[StatementFilter]") -> SQL:
|
|
306
|
-
"""Apply filters to a SQL object."""
|
|
307
|
-
result = sql
|
|
308
|
-
for filter_obj in filters:
|
|
309
|
-
result = filter_obj.append_to_statement(result)
|
|
310
|
-
return result
|
|
311
|
-
|
|
312
|
-
def _has_native_support(self) -> bool:
|
|
313
|
-
"""Check if driver has native pipeline support."""
|
|
314
|
-
return is_sync_pipeline_capable_driver(self.driver)
|
|
315
|
-
|
|
316
|
-
def _process_parameters(self, params: tuple[Any, ...]) -> tuple["list[StatementFilter]", "Optional[Any]"]:
|
|
317
|
-
"""Extract filters and parameters from mixed args.
|
|
318
|
-
|
|
319
|
-
Returns:
|
|
320
|
-
Tuple of (filters, parameters)
|
|
321
|
-
"""
|
|
322
|
-
filters: list[StatementFilter] = []
|
|
323
|
-
parameters: list[Any] = []
|
|
324
|
-
|
|
325
|
-
for param in params:
|
|
326
|
-
if isinstance(param, StatementFilter):
|
|
327
|
-
filters.append(param)
|
|
328
|
-
else:
|
|
329
|
-
parameters.append(param)
|
|
330
|
-
|
|
331
|
-
if not parameters:
|
|
332
|
-
return filters, None
|
|
333
|
-
if len(parameters) == 1:
|
|
334
|
-
return filters, parameters[0]
|
|
335
|
-
return filters, parameters
|
|
336
|
-
|
|
337
|
-
@property
|
|
338
|
-
def operations(self) -> "list[PipelineOperation]":
|
|
339
|
-
"""Get the current list of queued operations."""
|
|
340
|
-
return self._operations.copy()
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
class AsyncPipeline:
|
|
344
|
-
"""Asynchronous pipeline with identical structure to Pipeline."""
|
|
345
|
-
|
|
346
|
-
def __init__(
|
|
347
|
-
self,
|
|
348
|
-
driver: "AsyncDriverAdapterProtocol[Any, Any]",
|
|
349
|
-
isolation_level: "Optional[str]" = None,
|
|
350
|
-
continue_on_error: bool = False,
|
|
351
|
-
max_operations: int = 1000,
|
|
352
|
-
options: "Optional[dict[str, Any]]" = None,
|
|
353
|
-
) -> None:
|
|
354
|
-
self.driver = driver
|
|
355
|
-
self.isolation_level = isolation_level
|
|
356
|
-
self.continue_on_error = continue_on_error
|
|
357
|
-
self.max_operations = max_operations
|
|
358
|
-
self.options = options or {}
|
|
359
|
-
self._operations: list[PipelineOperation] = []
|
|
360
|
-
self._results: Optional[list[SQLResult[Any]]] = None
|
|
361
|
-
self._simulation_logged = False
|
|
362
|
-
|
|
363
|
-
async def add_execute(
|
|
364
|
-
self, statement: "Union[str, SQL]", /, *parameters: "Union[StatementParameters, StatementFilter]", **kwargs: Any
|
|
365
|
-
) -> "AsyncPipeline":
|
|
366
|
-
"""Add an execute operation to the async pipeline."""
|
|
367
|
-
self._operations.append(
|
|
368
|
-
PipelineOperation(
|
|
369
|
-
sql=SQL(statement, parameters=parameters or None, config=self.driver.config, **kwargs),
|
|
370
|
-
operation_type="execute",
|
|
371
|
-
)
|
|
372
|
-
)
|
|
373
|
-
|
|
374
|
-
if len(self._operations) >= self.max_operations:
|
|
375
|
-
logger.warning("Async pipeline auto-flushing at %s operations", len(self._operations))
|
|
376
|
-
await self.process()
|
|
377
|
-
|
|
378
|
-
return self
|
|
379
|
-
|
|
380
|
-
async def add_select(
|
|
381
|
-
self, statement: "Union[str, SQL]", /, *parameters: "Union[StatementParameters, StatementFilter]", **kwargs: Any
|
|
382
|
-
) -> "AsyncPipeline":
|
|
383
|
-
"""Add a select operation to the async pipeline."""
|
|
384
|
-
self._operations.append(
|
|
385
|
-
PipelineOperation(
|
|
386
|
-
sql=SQL(statement, parameters=parameters or None, config=self.driver.config, **kwargs),
|
|
387
|
-
operation_type="select",
|
|
388
|
-
)
|
|
389
|
-
)
|
|
390
|
-
return self
|
|
391
|
-
|
|
392
|
-
async def add_execute_many(
|
|
393
|
-
self, statement: "Union[str, SQL]", /, *parameters: "Union[StatementParameters, StatementFilter]", **kwargs: Any
|
|
394
|
-
) -> "AsyncPipeline":
|
|
395
|
-
"""Add batch execution to the async pipeline."""
|
|
396
|
-
# First parameter should be the batch data
|
|
397
|
-
if not parameters or not isinstance(parameters[0], (list, tuple)):
|
|
398
|
-
msg = "execute_many requires a sequence of parameter sets as first parameter"
|
|
399
|
-
raise ValueError(msg)
|
|
400
|
-
|
|
401
|
-
batch_params = parameters[0]
|
|
402
|
-
if isinstance(batch_params, tuple):
|
|
403
|
-
batch_params = list(batch_params)
|
|
404
|
-
sql_obj = SQL(
|
|
405
|
-
statement, parameters=parameters[1:] if len(parameters) > 1 else None, config=self.driver.config, **kwargs
|
|
406
|
-
).as_many(batch_params)
|
|
407
|
-
|
|
408
|
-
self._operations.append(PipelineOperation(sql=sql_obj, operation_type="execute_many"))
|
|
409
|
-
return self
|
|
410
|
-
|
|
411
|
-
async def add_execute_script(
|
|
412
|
-
self, script: "Union[str, SQL]", *filters: StatementFilter, **kwargs: Any
|
|
413
|
-
) -> "AsyncPipeline":
|
|
414
|
-
"""Add a script to the async pipeline."""
|
|
415
|
-
if isinstance(script, SQL):
|
|
416
|
-
sql_obj = script.as_script()
|
|
417
|
-
else:
|
|
418
|
-
sql_obj = SQL(script, parameters=filters or None, config=self.driver.config, **kwargs).as_script()
|
|
419
|
-
|
|
420
|
-
self._operations.append(PipelineOperation(sql=sql_obj, operation_type="execute_script"))
|
|
421
|
-
return self
|
|
422
|
-
|
|
423
|
-
async def process(self, filters: "Optional[list[StatementFilter]]" = None) -> "list[SQLResult]":
|
|
424
|
-
"""Execute all queued operations asynchronously."""
|
|
425
|
-
if not self._operations:
|
|
426
|
-
return []
|
|
427
|
-
|
|
428
|
-
if is_async_pipeline_capable_driver(self.driver):
|
|
429
|
-
results = await cast("Any", self.driver)._execute_pipeline_native(self._operations, **self.options)
|
|
430
|
-
else:
|
|
431
|
-
results = await self._execute_pipeline_simulated()
|
|
432
|
-
|
|
433
|
-
self._results = results
|
|
434
|
-
self._operations.clear()
|
|
435
|
-
return cast("list[SQLResult]", results)
|
|
436
|
-
|
|
437
|
-
async def _execute_pipeline_simulated(self) -> "list[SQLResult]":
|
|
438
|
-
"""Async version of simulated pipeline execution."""
|
|
439
|
-
results: list[SQLResult[Any]] = []
|
|
440
|
-
connection = None
|
|
441
|
-
auto_transaction = False
|
|
442
|
-
|
|
443
|
-
if not self._simulation_logged:
|
|
444
|
-
logger.info(
|
|
445
|
-
"%s using simulated async pipeline. Native support: %s",
|
|
446
|
-
self.driver.__class__.__name__,
|
|
447
|
-
self._has_native_support(),
|
|
448
|
-
)
|
|
449
|
-
self._simulation_logged = True
|
|
450
|
-
|
|
451
|
-
try:
|
|
452
|
-
connection = self.driver._connection()
|
|
453
|
-
|
|
454
|
-
if is_async_transaction_state_capable(connection) and not connection.in_transaction():
|
|
455
|
-
await connection.begin()
|
|
456
|
-
auto_transaction = True
|
|
457
|
-
|
|
458
|
-
for i, op in enumerate(self._operations):
|
|
459
|
-
await self._execute_single_operation_async(i, op, results, connection, auto_transaction)
|
|
460
|
-
|
|
461
|
-
if auto_transaction and is_async_transaction_state_capable(connection):
|
|
462
|
-
await connection.commit()
|
|
463
|
-
|
|
464
|
-
except Exception as e:
|
|
465
|
-
if connection and auto_transaction and is_async_transaction_state_capable(connection):
|
|
466
|
-
await connection.rollback()
|
|
467
|
-
if not isinstance(e, PipelineExecutionError):
|
|
468
|
-
msg = f"Async pipeline execution failed: {e}"
|
|
469
|
-
raise PipelineExecutionError(msg) from e
|
|
470
|
-
raise
|
|
471
|
-
|
|
472
|
-
return results
|
|
473
|
-
|
|
474
|
-
async def _execute_single_operation_async(
|
|
475
|
-
self, i: int, op: PipelineOperation, results: "list[SQLResult[Any]]", connection: Any, auto_transaction: bool
|
|
476
|
-
) -> None:
|
|
477
|
-
"""Execute a single async pipeline operation with error handling."""
|
|
478
|
-
try:
|
|
479
|
-
result: SQLResult[Any]
|
|
480
|
-
if op.operation_type == "execute_script":
|
|
481
|
-
result = await self.driver.execute_script(op.sql, _connection=connection)
|
|
482
|
-
elif op.operation_type == "execute_many":
|
|
483
|
-
result = await self.driver.execute_many(op.sql, _connection=connection)
|
|
484
|
-
else:
|
|
485
|
-
result = await self.driver.execute(op.sql, _connection=connection)
|
|
486
|
-
|
|
487
|
-
result.operation_index = i
|
|
488
|
-
result.pipeline_sql = op.sql
|
|
489
|
-
results.append(result)
|
|
490
|
-
|
|
491
|
-
except Exception as e:
|
|
492
|
-
if self.continue_on_error:
|
|
493
|
-
error_result = SQLResult(
|
|
494
|
-
statement=op.sql, data=[], error=e, operation_index=i, parameters=op.sql.parameters
|
|
495
|
-
)
|
|
496
|
-
results.append(error_result)
|
|
497
|
-
else:
|
|
498
|
-
if auto_transaction and is_async_transaction_state_capable(connection):
|
|
499
|
-
await connection.rollback()
|
|
500
|
-
msg = f"Async pipeline failed at operation {i}: {e}"
|
|
501
|
-
raise PipelineExecutionError(
|
|
502
|
-
msg, operation_index=i, partial_results=results, failed_operation=op
|
|
503
|
-
) from e
|
|
504
|
-
|
|
505
|
-
def _has_native_support(self) -> bool:
|
|
506
|
-
"""Check if driver has native pipeline support."""
|
|
507
|
-
return is_async_pipeline_capable_driver(self.driver)
|
|
508
|
-
|
|
509
|
-
@property
|
|
510
|
-
def operations(self) -> "list[PipelineOperation]":
|
|
511
|
-
"""Get the current list of queued operations."""
|
|
512
|
-
return self._operations.copy()
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
"""Result conversion utilities for unified storage architecture.
|
|
2
|
-
|
|
3
|
-
This module contains the result conversion functionality integrated with the unified
|
|
4
|
-
storage architecture.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import datetime
|
|
8
|
-
from collections.abc import Sequence
|
|
9
|
-
from enum import Enum
|
|
10
|
-
from functools import partial
|
|
11
|
-
from pathlib import Path, PurePath
|
|
12
|
-
from typing import Any, Callable, Optional, Union, cast, overload
|
|
13
|
-
from uuid import UUID
|
|
14
|
-
|
|
15
|
-
from sqlspec.exceptions import SQLSpecError, wrap_exceptions
|
|
16
|
-
from sqlspec.statement.result import OperationType
|
|
17
|
-
from sqlspec.typing import ModelDTOT, ModelT, convert, get_type_adapter
|
|
18
|
-
from sqlspec.utils.type_guards import is_dataclass, is_msgspec_struct, is_pydantic_model
|
|
19
|
-
|
|
20
|
-
__all__ = ("_DEFAULT_TYPE_DECODERS", "ToSchemaMixin", "_default_msgspec_deserializer")
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
_DEFAULT_TYPE_DECODERS: list[tuple[Callable[[Any], bool], Callable[[Any, Any], Any]]] = [
|
|
24
|
-
(lambda x: x is UUID, lambda t, v: t(v.hex)),
|
|
25
|
-
(lambda x: x is datetime.datetime, lambda t, v: t(v.isoformat())),
|
|
26
|
-
(lambda x: x is datetime.date, lambda t, v: t(v.isoformat())),
|
|
27
|
-
(lambda x: x is datetime.time, lambda t, v: t(v.isoformat())),
|
|
28
|
-
(lambda x: x is Enum, lambda t, v: t(v.value)),
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def _default_msgspec_deserializer(
|
|
33
|
-
target_type: Any, value: Any, type_decoders: "Optional[Sequence[tuple[Any, Any]]]" = None
|
|
34
|
-
) -> Any:
|
|
35
|
-
"""Default msgspec deserializer with type conversion support."""
|
|
36
|
-
if type_decoders:
|
|
37
|
-
for predicate, decoder in type_decoders:
|
|
38
|
-
if predicate(target_type):
|
|
39
|
-
return decoder(target_type, value)
|
|
40
|
-
if target_type is UUID and isinstance(value, UUID):
|
|
41
|
-
return value.hex
|
|
42
|
-
if target_type in {datetime.datetime, datetime.date, datetime.time}:
|
|
43
|
-
with wrap_exceptions(suppress=AttributeError):
|
|
44
|
-
return value.isoformat()
|
|
45
|
-
if isinstance(target_type, type) and issubclass(target_type, Enum) and isinstance(value, Enum):
|
|
46
|
-
return value.value
|
|
47
|
-
if isinstance(value, target_type):
|
|
48
|
-
return value
|
|
49
|
-
if issubclass(target_type, (Path, PurePath, UUID)):
|
|
50
|
-
return target_type(value)
|
|
51
|
-
return value
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
class ToSchemaMixin:
|
|
55
|
-
__slots__ = ()
|
|
56
|
-
|
|
57
|
-
@staticmethod
|
|
58
|
-
def _determine_operation_type(statement: "Any") -> OperationType:
|
|
59
|
-
"""Determine operation type from SQL statement expression.
|
|
60
|
-
|
|
61
|
-
Args:
|
|
62
|
-
statement: SQL statement object with expression attribute
|
|
63
|
-
|
|
64
|
-
Returns:
|
|
65
|
-
OperationType literal value
|
|
66
|
-
"""
|
|
67
|
-
if not hasattr(statement, "expression") or not statement.expression:
|
|
68
|
-
return "EXECUTE"
|
|
69
|
-
|
|
70
|
-
expr_type = type(statement.expression).__name__.upper()
|
|
71
|
-
if "INSERT" in expr_type:
|
|
72
|
-
return "INSERT"
|
|
73
|
-
if "UPDATE" in expr_type:
|
|
74
|
-
return "UPDATE"
|
|
75
|
-
if "DELETE" in expr_type:
|
|
76
|
-
return "DELETE"
|
|
77
|
-
if "SELECT" in expr_type:
|
|
78
|
-
return "SELECT"
|
|
79
|
-
return "EXECUTE"
|
|
80
|
-
|
|
81
|
-
@overload
|
|
82
|
-
@staticmethod
|
|
83
|
-
def to_schema(data: "ModelT", *, schema_type: None = None) -> "ModelT": ...
|
|
84
|
-
@overload
|
|
85
|
-
@staticmethod
|
|
86
|
-
def to_schema(data: "dict[str, Any]", *, schema_type: "type[ModelDTOT]") -> "ModelDTOT": ...
|
|
87
|
-
@overload
|
|
88
|
-
@staticmethod
|
|
89
|
-
def to_schema(data: "Sequence[ModelT]", *, schema_type: None = None) -> "Sequence[ModelT]": ...
|
|
90
|
-
@overload
|
|
91
|
-
@staticmethod
|
|
92
|
-
def to_schema(data: "Sequence[dict[str, Any]]", *, schema_type: "type[ModelDTOT]") -> "Sequence[ModelDTOT]": ...
|
|
93
|
-
|
|
94
|
-
@staticmethod
|
|
95
|
-
def to_schema(
|
|
96
|
-
data: "Union[ModelT, dict[str, Any], Sequence[ModelT], Sequence[dict[str, Any]]]",
|
|
97
|
-
*,
|
|
98
|
-
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
99
|
-
) -> "Union[ModelT, ModelDTOT, Sequence[ModelT], Sequence[ModelDTOT]]":
|
|
100
|
-
"""Convert data to a specified schema type."""
|
|
101
|
-
if schema_type is None:
|
|
102
|
-
if not isinstance(data, Sequence):
|
|
103
|
-
return cast("ModelT", data)
|
|
104
|
-
return cast("Sequence[ModelT]", data)
|
|
105
|
-
if is_dataclass(schema_type):
|
|
106
|
-
if not isinstance(data, Sequence):
|
|
107
|
-
return cast("ModelDTOT", schema_type(**data)) # type: ignore[operator]
|
|
108
|
-
return cast("Sequence[ModelDTOT]", [schema_type(**item) for item in data]) # type: ignore[operator]
|
|
109
|
-
if is_msgspec_struct(schema_type):
|
|
110
|
-
if not isinstance(data, Sequence):
|
|
111
|
-
return cast(
|
|
112
|
-
"ModelDTOT",
|
|
113
|
-
convert(
|
|
114
|
-
obj=data,
|
|
115
|
-
type=schema_type,
|
|
116
|
-
from_attributes=True,
|
|
117
|
-
dec_hook=partial(_default_msgspec_deserializer, type_decoders=_DEFAULT_TYPE_DECODERS),
|
|
118
|
-
),
|
|
119
|
-
)
|
|
120
|
-
return cast(
|
|
121
|
-
"Sequence[ModelDTOT]",
|
|
122
|
-
convert(
|
|
123
|
-
obj=data,
|
|
124
|
-
type=list[schema_type], # type: ignore[valid-type] # pyright: ignore
|
|
125
|
-
from_attributes=True,
|
|
126
|
-
dec_hook=partial(_default_msgspec_deserializer, type_decoders=_DEFAULT_TYPE_DECODERS),
|
|
127
|
-
),
|
|
128
|
-
)
|
|
129
|
-
if schema_type is not None and is_pydantic_model(schema_type):
|
|
130
|
-
if not isinstance(data, Sequence):
|
|
131
|
-
return cast(
|
|
132
|
-
"ModelDTOT",
|
|
133
|
-
get_type_adapter(schema_type).validate_python(data, from_attributes=True), # pyright: ignore
|
|
134
|
-
)
|
|
135
|
-
return cast(
|
|
136
|
-
"Sequence[ModelDTOT]",
|
|
137
|
-
get_type_adapter(list[schema_type]).validate_python(data, from_attributes=True), # type: ignore[valid-type] # pyright: ignore
|
|
138
|
-
)
|
|
139
|
-
msg = "`schema_type` should be a valid Dataclass, Pydantic model or Msgspec struct"
|
|
140
|
-
raise SQLSpecError(msg)
|