sqlspec 0.11.1__py3-none-any.whl → 0.12.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sqlspec might be problematic. Click here for more details.
- sqlspec/__init__.py +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 +741 -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.0.dist-info}/METADATA +97 -26
- sqlspec-0.12.0.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.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.11.1.dist-info → sqlspec-0.12.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.11.1.dist-info → sqlspec-0.12.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,426 +1,263 @@
|
|
|
1
|
-
import
|
|
1
|
+
import contextlib
|
|
2
|
+
import csv
|
|
2
3
|
import sqlite3
|
|
4
|
+
from collections.abc import Iterator
|
|
3
5
|
from contextlib import contextmanager
|
|
4
|
-
from
|
|
5
|
-
from typing import TYPE_CHECKING, Any, Optional, Union,
|
|
6
|
-
|
|
7
|
-
from
|
|
8
|
-
|
|
9
|
-
from sqlspec.
|
|
10
|
-
from sqlspec.
|
|
11
|
-
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Optional, Union, cast
|
|
8
|
+
|
|
9
|
+
from typing_extensions import TypeAlias
|
|
10
|
+
|
|
11
|
+
from sqlspec.driver import SyncDriverAdapterProtocol
|
|
12
|
+
from sqlspec.driver.mixins import (
|
|
13
|
+
SQLTranslatorMixin,
|
|
14
|
+
SyncPipelinedExecutionMixin,
|
|
15
|
+
SyncStorageMixin,
|
|
16
|
+
ToSchemaMixin,
|
|
17
|
+
TypeCoercionMixin,
|
|
18
|
+
)
|
|
19
|
+
from sqlspec.statement.parameters import ParameterStyle
|
|
20
|
+
from sqlspec.statement.result import DMLResultDict, ScriptResultDict, SelectResultDict, SQLResult
|
|
21
|
+
from sqlspec.statement.sql import SQL, SQLConfig
|
|
22
|
+
from sqlspec.typing import DictRow, ModelDTOT, RowT, is_dict_with_field
|
|
23
|
+
from sqlspec.utils.logging import get_logger
|
|
24
|
+
from sqlspec.utils.serializers import to_json
|
|
12
25
|
|
|
13
26
|
if TYPE_CHECKING:
|
|
14
|
-
from
|
|
15
|
-
|
|
16
|
-
from sqlspec.typing import ModelDTOT, StatementParameterType, T
|
|
27
|
+
from sqlglot.dialects.dialect import DialectType
|
|
17
28
|
|
|
18
29
|
__all__ = ("SqliteConnection", "SqliteDriver")
|
|
19
30
|
|
|
20
|
-
logger =
|
|
31
|
+
logger = get_logger("adapters.sqlite")
|
|
21
32
|
|
|
22
|
-
SqliteConnection = sqlite3.Connection
|
|
33
|
+
SqliteConnection: TypeAlias = sqlite3.Connection
|
|
23
34
|
|
|
24
35
|
|
|
25
36
|
class SqliteDriver(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
SyncDriverAdapterProtocol[SqliteConnection, RowT],
|
|
38
|
+
SQLTranslatorMixin,
|
|
39
|
+
TypeCoercionMixin,
|
|
40
|
+
SyncStorageMixin,
|
|
41
|
+
SyncPipelinedExecutionMixin,
|
|
42
|
+
ToSchemaMixin,
|
|
29
43
|
):
|
|
30
|
-
"""SQLite Sync Driver Adapter.
|
|
44
|
+
"""SQLite Sync Driver Adapter with Arrow/Parquet export support.
|
|
31
45
|
|
|
32
|
-
|
|
33
|
-
|
|
46
|
+
Refactored to align with the new enhanced driver architecture and
|
|
47
|
+
instrumentation standards following the psycopg pattern.
|
|
48
|
+
"""
|
|
34
49
|
|
|
35
|
-
|
|
36
|
-
self.connection = connection
|
|
50
|
+
__slots__ = ()
|
|
37
51
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
52
|
+
dialect: "DialectType" = "sqlite"
|
|
53
|
+
supported_parameter_styles: "tuple[ParameterStyle, ...]" = (ParameterStyle.QMARK, ParameterStyle.NAMED_COLON)
|
|
54
|
+
default_parameter_style: ParameterStyle = ParameterStyle.QMARK
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
connection: "SqliteConnection",
|
|
59
|
+
config: "Optional[SQLConfig]" = None,
|
|
60
|
+
default_row_type: "type[DictRow]" = dict[str, Any],
|
|
61
|
+
) -> None:
|
|
62
|
+
super().__init__(connection=connection, config=config, default_row_type=default_row_type)
|
|
63
|
+
|
|
64
|
+
# SQLite-specific type coercion overrides
|
|
65
|
+
def _coerce_boolean(self, value: Any) -> Any:
|
|
66
|
+
"""SQLite stores booleans as integers (0/1)."""
|
|
67
|
+
if isinstance(value, bool):
|
|
68
|
+
return 1 if value else 0
|
|
69
|
+
return value
|
|
70
|
+
|
|
71
|
+
def _coerce_decimal(self, value: Any) -> Any:
|
|
72
|
+
"""SQLite stores decimals as strings to preserve precision."""
|
|
73
|
+
if isinstance(value, str):
|
|
74
|
+
return value # Already a string
|
|
75
|
+
from decimal import Decimal
|
|
76
|
+
|
|
77
|
+
if isinstance(value, Decimal):
|
|
78
|
+
return str(value)
|
|
79
|
+
return value
|
|
80
|
+
|
|
81
|
+
def _coerce_json(self, value: Any) -> Any:
|
|
82
|
+
"""SQLite stores JSON as strings (requires JSON1 extension)."""
|
|
83
|
+
if isinstance(value, (dict, list)):
|
|
84
|
+
return to_json(value)
|
|
85
|
+
return value
|
|
86
|
+
|
|
87
|
+
def _coerce_array(self, value: Any) -> Any:
|
|
88
|
+
"""SQLite doesn't have native arrays - store as JSON strings."""
|
|
89
|
+
if isinstance(value, (list, tuple)):
|
|
90
|
+
return to_json(list(value))
|
|
91
|
+
return value
|
|
41
92
|
|
|
93
|
+
@staticmethod
|
|
42
94
|
@contextmanager
|
|
43
|
-
def
|
|
44
|
-
cursor =
|
|
95
|
+
def _get_cursor(connection: SqliteConnection) -> Iterator[sqlite3.Cursor]:
|
|
96
|
+
cursor = connection.cursor()
|
|
45
97
|
try:
|
|
46
98
|
yield cursor
|
|
47
99
|
finally:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if parameters is not None:
|
|
77
|
-
if isinstance(parameters, StatementFilter):
|
|
78
|
-
combined_filters_list.insert(0, parameters)
|
|
100
|
+
with contextlib.suppress(Exception):
|
|
101
|
+
cursor.close()
|
|
102
|
+
|
|
103
|
+
def _execute_statement(
|
|
104
|
+
self, statement: SQL, connection: Optional[SqliteConnection] = None, **kwargs: Any
|
|
105
|
+
) -> Union[SelectResultDict, DMLResultDict, ScriptResultDict]:
|
|
106
|
+
if statement.is_script:
|
|
107
|
+
sql, _ = statement.compile(placeholder_style=ParameterStyle.STATIC)
|
|
108
|
+
return self._execute_script(sql, connection=connection, **kwargs)
|
|
109
|
+
|
|
110
|
+
# Determine if we need to convert parameter style
|
|
111
|
+
detected_styles = {p.style for p in statement.parameter_info}
|
|
112
|
+
target_style = self.default_parameter_style
|
|
113
|
+
|
|
114
|
+
# Check if any detected style is not supported
|
|
115
|
+
unsupported_styles = detected_styles - set(self.supported_parameter_styles)
|
|
116
|
+
if unsupported_styles:
|
|
117
|
+
# Convert to default style if we have unsupported styles
|
|
118
|
+
target_style = self.default_parameter_style
|
|
119
|
+
elif len(detected_styles) > 1:
|
|
120
|
+
# Mixed styles detected - use default style for consistency
|
|
121
|
+
target_style = self.default_parameter_style
|
|
122
|
+
elif detected_styles:
|
|
123
|
+
# Single style detected - use it if supported
|
|
124
|
+
single_style = next(iter(detected_styles))
|
|
125
|
+
if single_style in self.supported_parameter_styles:
|
|
126
|
+
target_style = single_style
|
|
79
127
|
else:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
connection:
|
|
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
|
-
|
|
181
|
-
|
|
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
|
-
self,
|
|
217
|
-
sql: str,
|
|
218
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
219
|
-
*filters: "StatementFilter",
|
|
220
|
-
connection: "Optional[SqliteConnection]" = None,
|
|
221
|
-
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
222
|
-
**kwargs: Any,
|
|
223
|
-
) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
|
|
224
|
-
"""Fetch one row from the database.
|
|
225
|
-
|
|
226
|
-
Returns:
|
|
227
|
-
The first row of the query results, or None if no results.
|
|
228
|
-
"""
|
|
229
|
-
connection = self._connection(connection)
|
|
230
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
231
|
-
|
|
232
|
-
with self._with_cursor(connection) as cursor:
|
|
233
|
-
cursor.execute(sql, parameters or [])
|
|
234
|
-
result = cursor.fetchone()
|
|
235
|
-
if result is None:
|
|
236
|
-
return None
|
|
237
|
-
|
|
238
|
-
column_names = [column[0] for column in cursor.description]
|
|
239
|
-
return self.to_schema(dict(zip(column_names, result)), schema_type=schema_type)
|
|
240
|
-
|
|
241
|
-
@overload
|
|
242
|
-
def select_value(
|
|
243
|
-
self,
|
|
244
|
-
sql: str,
|
|
245
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
246
|
-
*filters: "StatementFilter",
|
|
247
|
-
connection: "Optional[SqliteConnection]" = None,
|
|
248
|
-
schema_type: None = None,
|
|
249
|
-
**kwargs: Any,
|
|
250
|
-
) -> "Any": ...
|
|
251
|
-
@overload
|
|
252
|
-
def select_value(
|
|
253
|
-
self,
|
|
254
|
-
sql: str,
|
|
255
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
256
|
-
*filters: "StatementFilter",
|
|
257
|
-
connection: "Optional[SqliteConnection]" = None,
|
|
258
|
-
schema_type: "type[T]",
|
|
259
|
-
**kwargs: Any,
|
|
260
|
-
) -> "T": ...
|
|
261
|
-
def select_value(
|
|
262
|
-
self,
|
|
263
|
-
sql: str,
|
|
264
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
265
|
-
*filters: "StatementFilter",
|
|
266
|
-
connection: "Optional[SqliteConnection]" = None,
|
|
267
|
-
schema_type: "Optional[type[T]]" = None,
|
|
268
|
-
**kwargs: Any,
|
|
269
|
-
) -> "Union[T, Any]":
|
|
270
|
-
"""Fetch a single value from the database.
|
|
271
|
-
|
|
272
|
-
Returns:
|
|
273
|
-
The first value from the first row of results.
|
|
274
|
-
"""
|
|
275
|
-
connection = self._connection(connection)
|
|
276
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
277
|
-
|
|
278
|
-
with self._with_cursor(connection) as cursor:
|
|
279
|
-
cursor.execute(sql, parameters or [])
|
|
280
|
-
result = cursor.fetchone()
|
|
281
|
-
result = self.check_not_found(result)
|
|
282
|
-
result_value = result[0]
|
|
283
|
-
if schema_type is None:
|
|
284
|
-
return result_value
|
|
285
|
-
return schema_type(result_value) # type: ignore[call-arg]
|
|
286
|
-
|
|
287
|
-
@overload
|
|
288
|
-
def select_value_or_none(
|
|
289
|
-
self,
|
|
290
|
-
sql: str,
|
|
291
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
292
|
-
*filters: "StatementFilter",
|
|
293
|
-
connection: "Optional[SqliteConnection]" = None,
|
|
294
|
-
schema_type: None = None,
|
|
295
|
-
**kwargs: Any,
|
|
296
|
-
) -> "Optional[Any]": ...
|
|
297
|
-
@overload
|
|
298
|
-
def select_value_or_none(
|
|
299
|
-
self,
|
|
300
|
-
sql: str,
|
|
301
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
302
|
-
*filters: "StatementFilter",
|
|
303
|
-
connection: "Optional[SqliteConnection]" = None,
|
|
304
|
-
schema_type: "type[T]",
|
|
305
|
-
**kwargs: Any,
|
|
306
|
-
) -> "Optional[T]": ...
|
|
307
|
-
def select_value_or_none(
|
|
308
|
-
self,
|
|
309
|
-
sql: str,
|
|
310
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
311
|
-
*filters: "StatementFilter",
|
|
312
|
-
connection: "Optional[SqliteConnection]" = None,
|
|
313
|
-
schema_type: "Optional[type[T]]" = None,
|
|
314
|
-
**kwargs: Any,
|
|
315
|
-
) -> "Optional[Union[T, Any]]":
|
|
316
|
-
"""Fetch a single value from the database.
|
|
317
|
-
|
|
318
|
-
Returns:
|
|
319
|
-
The first value from the first row of results, or None if no results.
|
|
320
|
-
"""
|
|
321
|
-
connection = self._connection(connection)
|
|
322
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
323
|
-
|
|
324
|
-
with self._with_cursor(connection) as cursor:
|
|
325
|
-
cursor.execute(sql, parameters or [])
|
|
326
|
-
result = cursor.fetchone()
|
|
327
|
-
if result is None:
|
|
328
|
-
return None
|
|
329
|
-
result_value = result[0]
|
|
330
|
-
if schema_type is None:
|
|
331
|
-
return result_value
|
|
332
|
-
return schema_type(result_value) # type: ignore[call-arg]
|
|
333
|
-
|
|
334
|
-
def insert_update_delete(
|
|
335
|
-
self,
|
|
336
|
-
sql: str,
|
|
337
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
338
|
-
*filters: "StatementFilter",
|
|
339
|
-
connection: "Optional[SqliteConnection]" = None,
|
|
340
|
-
**kwargs: Any,
|
|
341
|
-
) -> int:
|
|
342
|
-
"""Insert, update, or delete data from the database.
|
|
343
|
-
|
|
344
|
-
Returns:
|
|
345
|
-
Row count affected by the operation.
|
|
346
|
-
"""
|
|
347
|
-
connection = self._connection(connection)
|
|
348
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
349
|
-
|
|
350
|
-
with self._with_cursor(connection) as cursor:
|
|
351
|
-
cursor.execute(sql, parameters or [])
|
|
352
|
-
return cursor.rowcount
|
|
353
|
-
|
|
354
|
-
@overload
|
|
355
|
-
def insert_update_delete_returning(
|
|
356
|
-
self,
|
|
357
|
-
sql: str,
|
|
358
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
359
|
-
*filters: "StatementFilter",
|
|
360
|
-
connection: "Optional[SqliteConnection]" = None,
|
|
361
|
-
schema_type: None = None,
|
|
362
|
-
**kwargs: Any,
|
|
363
|
-
) -> "dict[str, Any]": ...
|
|
364
|
-
@overload
|
|
365
|
-
def insert_update_delete_returning(
|
|
366
|
-
self,
|
|
367
|
-
sql: str,
|
|
368
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
369
|
-
*filters: "StatementFilter",
|
|
370
|
-
connection: "Optional[SqliteConnection]" = None,
|
|
371
|
-
schema_type: "type[ModelDTOT]",
|
|
372
|
-
**kwargs: Any,
|
|
373
|
-
) -> "ModelDTOT": ...
|
|
374
|
-
def insert_update_delete_returning(
|
|
375
|
-
self,
|
|
376
|
-
sql: str,
|
|
377
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
378
|
-
*filters: "StatementFilter",
|
|
379
|
-
connection: "Optional[SqliteConnection]" = None,
|
|
380
|
-
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
381
|
-
**kwargs: Any,
|
|
382
|
-
) -> "Union[dict[str, Any], ModelDTOT]":
|
|
383
|
-
"""Insert, update, or delete data from the database and return result.
|
|
384
|
-
|
|
385
|
-
Returns:
|
|
386
|
-
The first row of results.
|
|
387
|
-
"""
|
|
388
|
-
connection = self._connection(connection)
|
|
389
|
-
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
390
|
-
|
|
391
|
-
with self._with_cursor(connection) as cursor:
|
|
392
|
-
cursor.execute(sql, parameters or [])
|
|
393
|
-
result = cursor.fetchone()
|
|
394
|
-
result = self.check_not_found(result)
|
|
395
|
-
column_names = [column[0] for column in cursor.description]
|
|
396
|
-
return self.to_schema(dict(zip(column_names, result)), schema_type=schema_type)
|
|
397
|
-
|
|
398
|
-
def execute_script(
|
|
399
|
-
self,
|
|
400
|
-
sql: str,
|
|
401
|
-
parameters: "Optional[StatementParameterType]" = None,
|
|
402
|
-
connection: "Optional[SqliteConnection]" = None,
|
|
403
|
-
**kwargs: Any,
|
|
404
|
-
) -> str:
|
|
405
|
-
"""Execute a script.
|
|
406
|
-
|
|
407
|
-
Returns:
|
|
408
|
-
Status message for the operation.
|
|
409
|
-
"""
|
|
410
|
-
connection = self._connection(connection)
|
|
411
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
412
|
-
|
|
413
|
-
with self._with_cursor(connection) as cursor:
|
|
414
|
-
cursor.executescript(sql)
|
|
415
|
-
return "DONE"
|
|
416
|
-
|
|
417
|
-
def _connection(self, connection: "Optional[SqliteConnection]" = None) -> "SqliteConnection":
|
|
418
|
-
"""Get the connection to use for the operation.
|
|
419
|
-
|
|
420
|
-
Args:
|
|
421
|
-
connection: Optional connection to use.
|
|
422
|
-
|
|
423
|
-
Returns:
|
|
424
|
-
The connection to use.
|
|
425
|
-
"""
|
|
426
|
-
return connection or self.connection
|
|
128
|
+
target_style = self.default_parameter_style
|
|
129
|
+
|
|
130
|
+
if statement.is_many:
|
|
131
|
+
sql, params = statement.compile(placeholder_style=target_style)
|
|
132
|
+
return self._execute_many(sql, params, connection=connection, **kwargs)
|
|
133
|
+
|
|
134
|
+
sql, params = statement.compile(placeholder_style=target_style)
|
|
135
|
+
|
|
136
|
+
# Process parameters through type coercion
|
|
137
|
+
params = self._process_parameters(params)
|
|
138
|
+
|
|
139
|
+
# SQLite expects tuples for positional parameters
|
|
140
|
+
if isinstance(params, list):
|
|
141
|
+
params = tuple(params)
|
|
142
|
+
|
|
143
|
+
return self._execute(sql, params, statement, connection=connection, **kwargs)
|
|
144
|
+
|
|
145
|
+
def _execute(
|
|
146
|
+
self, sql: str, parameters: Any, statement: SQL, connection: Optional[SqliteConnection] = None, **kwargs: Any
|
|
147
|
+
) -> Union[SelectResultDict, DMLResultDict]:
|
|
148
|
+
"""Execute a single statement with parameters."""
|
|
149
|
+
conn = self._connection(connection)
|
|
150
|
+
with self._get_cursor(conn) as cursor:
|
|
151
|
+
# SQLite expects tuple or dict parameters
|
|
152
|
+
if parameters is not None and not isinstance(parameters, (tuple, list, dict)):
|
|
153
|
+
# Convert scalar to tuple
|
|
154
|
+
parameters = (parameters,)
|
|
155
|
+
cursor.execute(sql, parameters or ())
|
|
156
|
+
if self.returns_rows(statement.expression):
|
|
157
|
+
fetched_data: list[sqlite3.Row] = cursor.fetchall()
|
|
158
|
+
return {
|
|
159
|
+
"data": fetched_data,
|
|
160
|
+
"column_names": [col[0] for col in cursor.description or []],
|
|
161
|
+
"rows_affected": len(fetched_data),
|
|
162
|
+
}
|
|
163
|
+
return {"rows_affected": cursor.rowcount, "status_message": "OK"}
|
|
164
|
+
|
|
165
|
+
def _execute_many(
|
|
166
|
+
self, sql: str, param_list: Any, connection: Optional[SqliteConnection] = None, **kwargs: Any
|
|
167
|
+
) -> DMLResultDict:
|
|
168
|
+
"""Execute a statement many times with a list of parameter tuples."""
|
|
169
|
+
conn = self._connection(connection)
|
|
170
|
+
if param_list:
|
|
171
|
+
param_list = self._process_parameters(param_list)
|
|
172
|
+
|
|
173
|
+
# Convert parameter list to proper format for executemany
|
|
174
|
+
formatted_params: list[tuple[Any, ...]] = []
|
|
175
|
+
if param_list and isinstance(param_list, list):
|
|
176
|
+
for param_set in cast("list[Union[list, tuple]]", param_list):
|
|
177
|
+
if isinstance(param_set, (list, tuple)):
|
|
178
|
+
formatted_params.append(tuple(param_set))
|
|
179
|
+
elif param_set is None:
|
|
180
|
+
formatted_params.append(())
|
|
181
|
+
else:
|
|
182
|
+
formatted_params.append((param_set,))
|
|
183
|
+
|
|
184
|
+
with self._get_cursor(conn) as cursor:
|
|
185
|
+
cursor.executemany(sql, formatted_params)
|
|
186
|
+
return {"rows_affected": cursor.rowcount, "status_message": "OK"}
|
|
187
|
+
|
|
188
|
+
def _execute_script(
|
|
189
|
+
self, script: str, connection: Optional[SqliteConnection] = None, **kwargs: Any
|
|
190
|
+
) -> ScriptResultDict:
|
|
191
|
+
"""Execute a script on the SQLite connection."""
|
|
192
|
+
conn = self._connection(connection)
|
|
193
|
+
with self._get_cursor(conn) as cursor:
|
|
194
|
+
cursor.executescript(script)
|
|
195
|
+
# executescript doesn't auto-commit in some cases
|
|
196
|
+
conn.commit()
|
|
197
|
+
result: ScriptResultDict = {"statements_executed": -1, "status_message": "SCRIPT EXECUTED"}
|
|
198
|
+
return result
|
|
199
|
+
|
|
200
|
+
def _bulk_load_file(self, file_path: Path, table_name: str, format: str, mode: str, **options: Any) -> int:
|
|
201
|
+
"""Database-specific bulk load implementation."""
|
|
202
|
+
if format != "csv":
|
|
203
|
+
msg = f"SQLite driver only supports CSV for bulk loading, not {format}."
|
|
204
|
+
raise NotImplementedError(msg)
|
|
205
|
+
|
|
206
|
+
conn = self._connection(None)
|
|
207
|
+
with self._get_cursor(conn) as cursor:
|
|
208
|
+
if mode == "replace":
|
|
209
|
+
cursor.execute(f"DELETE FROM {table_name}")
|
|
210
|
+
|
|
211
|
+
with Path(file_path).open(encoding="utf-8") as f:
|
|
212
|
+
reader = csv.reader(f, **options)
|
|
213
|
+
header = next(reader) # Skip header
|
|
214
|
+
placeholders = ", ".join("?" for _ in header)
|
|
215
|
+
sql = f"INSERT INTO {table_name} VALUES ({placeholders})"
|
|
216
|
+
|
|
217
|
+
# executemany is efficient for bulk inserts
|
|
218
|
+
data_iter = list(reader) # Read all data into memory
|
|
219
|
+
cursor.executemany(sql, data_iter)
|
|
220
|
+
return cursor.rowcount
|
|
221
|
+
|
|
222
|
+
def _wrap_select_result(
|
|
223
|
+
self, statement: SQL, result: SelectResultDict, schema_type: Optional[type[ModelDTOT]] = None, **kwargs: Any
|
|
224
|
+
) -> Union[SQLResult[ModelDTOT], SQLResult[RowT]]:
|
|
225
|
+
rows_as_dicts = [dict(row) for row in result["data"]]
|
|
226
|
+
if schema_type:
|
|
227
|
+
return SQLResult[ModelDTOT](
|
|
228
|
+
statement=statement,
|
|
229
|
+
data=list(self.to_schema(data=rows_as_dicts, schema_type=schema_type)),
|
|
230
|
+
column_names=result["column_names"],
|
|
231
|
+
rows_affected=result["rows_affected"],
|
|
232
|
+
operation_type="SELECT",
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
return SQLResult[RowT](
|
|
236
|
+
statement=statement,
|
|
237
|
+
data=rows_as_dicts,
|
|
238
|
+
column_names=result["column_names"],
|
|
239
|
+
rows_affected=result["rows_affected"],
|
|
240
|
+
operation_type="SELECT",
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
def _wrap_execute_result(
|
|
244
|
+
self, statement: SQL, result: Union[DMLResultDict, ScriptResultDict], **kwargs: Any
|
|
245
|
+
) -> SQLResult[RowT]:
|
|
246
|
+
if is_dict_with_field(result, "statements_executed"):
|
|
247
|
+
return SQLResult[RowT](
|
|
248
|
+
statement=statement,
|
|
249
|
+
data=[],
|
|
250
|
+
rows_affected=0,
|
|
251
|
+
operation_type="SCRIPT",
|
|
252
|
+
metadata={
|
|
253
|
+
"status_message": result.get("status_message", ""),
|
|
254
|
+
"statements_executed": result.get("statements_executed", -1),
|
|
255
|
+
},
|
|
256
|
+
)
|
|
257
|
+
return SQLResult[RowT](
|
|
258
|
+
statement=statement,
|
|
259
|
+
data=[],
|
|
260
|
+
rows_affected=cast("int", result.get("rows_affected", -1)),
|
|
261
|
+
operation_type=statement.expression.key.upper() if statement.expression else "UNKNOWN",
|
|
262
|
+
metadata={"status_message": result.get("status_message", "")},
|
|
263
|
+
)
|