sqlspec 0.10.1__py3-none-any.whl → 0.11.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/adapters/adbc/config.py +1 -1
- sqlspec/adapters/adbc/driver.py +336 -165
- sqlspec/adapters/aiosqlite/driver.py +211 -126
- sqlspec/adapters/asyncmy/driver.py +164 -68
- sqlspec/adapters/asyncpg/config.py +3 -1
- sqlspec/adapters/asyncpg/driver.py +190 -231
- sqlspec/adapters/bigquery/driver.py +178 -169
- sqlspec/adapters/duckdb/driver.py +175 -84
- sqlspec/adapters/oracledb/driver.py +224 -90
- sqlspec/adapters/psqlpy/driver.py +267 -187
- sqlspec/adapters/psycopg/driver.py +138 -184
- sqlspec/adapters/sqlite/driver.py +153 -121
- sqlspec/base.py +57 -45
- sqlspec/extensions/litestar/__init__.py +3 -12
- sqlspec/extensions/litestar/config.py +22 -7
- sqlspec/extensions/litestar/handlers.py +142 -85
- sqlspec/extensions/litestar/plugin.py +9 -8
- sqlspec/extensions/litestar/providers.py +521 -0
- sqlspec/filters.py +214 -11
- sqlspec/mixins.py +152 -2
- sqlspec/statement.py +276 -271
- sqlspec/typing.py +18 -1
- sqlspec/utils/__init__.py +2 -2
- sqlspec/utils/singleton.py +35 -0
- sqlspec/utils/sync_tools.py +90 -151
- sqlspec/utils/text.py +68 -5
- {sqlspec-0.10.1.dist-info → sqlspec-0.11.0.dist-info}/METADATA +5 -1
- {sqlspec-0.10.1.dist-info → sqlspec-0.11.0.dist-info}/RECORD +31 -29
- {sqlspec-0.10.1.dist-info → sqlspec-0.11.0.dist-info}/WHEEL +0 -0
- {sqlspec-0.10.1.dist-info → sqlspec-0.11.0.dist-info}/licenses/LICENSE +0 -0
- {sqlspec-0.10.1.dist-info → sqlspec-0.11.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,19 +1,24 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
from contextlib import contextmanager
|
|
2
3
|
from typing import TYPE_CHECKING, Any, Optional, Union, cast, overload
|
|
3
4
|
|
|
4
5
|
from duckdb import DuckDBPyConnection
|
|
5
6
|
|
|
6
7
|
from sqlspec.base import SyncDriverAdapterProtocol
|
|
7
|
-
from sqlspec.mixins import SQLTranslatorMixin, SyncArrowBulkOperationsMixin
|
|
8
|
+
from sqlspec.mixins import ResultConverter, SQLTranslatorMixin, SyncArrowBulkOperationsMixin
|
|
9
|
+
from sqlspec.statement import SQLStatement
|
|
8
10
|
from sqlspec.typing import ArrowTable, StatementParameterType
|
|
9
11
|
|
|
10
12
|
if TYPE_CHECKING:
|
|
11
13
|
from collections.abc import Generator, Sequence
|
|
12
14
|
|
|
15
|
+
from sqlspec.filters import StatementFilter
|
|
13
16
|
from sqlspec.typing import ArrowTable, ModelDTOT, StatementParameterType, T
|
|
14
17
|
|
|
15
18
|
__all__ = ("DuckDBConnection", "DuckDBDriver")
|
|
16
19
|
|
|
20
|
+
logger = logging.getLogger("sqlspec")
|
|
21
|
+
|
|
17
22
|
DuckDBConnection = DuckDBPyConnection
|
|
18
23
|
|
|
19
24
|
|
|
@@ -21,6 +26,7 @@ class DuckDBDriver(
|
|
|
21
26
|
SyncArrowBulkOperationsMixin["DuckDBConnection"],
|
|
22
27
|
SQLTranslatorMixin["DuckDBConnection"],
|
|
23
28
|
SyncDriverAdapterProtocol["DuckDBConnection"],
|
|
29
|
+
ResultConverter,
|
|
24
30
|
):
|
|
25
31
|
"""DuckDB Sync Driver Adapter."""
|
|
26
32
|
|
|
@@ -32,7 +38,6 @@ class DuckDBDriver(
|
|
|
32
38
|
self.connection = connection
|
|
33
39
|
self.use_cursor = use_cursor
|
|
34
40
|
|
|
35
|
-
# --- Helper Methods --- #
|
|
36
41
|
def _cursor(self, connection: "DuckDBConnection") -> "DuckDBConnection":
|
|
37
42
|
if self.use_cursor:
|
|
38
43
|
return connection.cursor()
|
|
@@ -49,6 +54,44 @@ class DuckDBDriver(
|
|
|
49
54
|
else:
|
|
50
55
|
yield connection
|
|
51
56
|
|
|
57
|
+
def _process_sql_params(
|
|
58
|
+
self,
|
|
59
|
+
sql: str,
|
|
60
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
61
|
+
/,
|
|
62
|
+
*filters: "StatementFilter",
|
|
63
|
+
**kwargs: Any,
|
|
64
|
+
) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
|
|
65
|
+
"""Process SQL and parameters for DuckDB using SQLStatement.
|
|
66
|
+
|
|
67
|
+
DuckDB supports both named (:name, $name) and positional (?) parameters.
|
|
68
|
+
This method processes the SQL with dialect-aware parsing and handles
|
|
69
|
+
parameters appropriately for DuckDB.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
sql: SQL statement.
|
|
73
|
+
parameters: Query parameters.
|
|
74
|
+
*filters: Statement filters to apply.
|
|
75
|
+
**kwargs: Additional keyword arguments.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Tuple of processed SQL and parameters.
|
|
79
|
+
"""
|
|
80
|
+
statement = SQLStatement(sql, parameters, kwargs=kwargs, dialect=self.dialect)
|
|
81
|
+
|
|
82
|
+
# Apply any filters
|
|
83
|
+
for filter_obj in filters:
|
|
84
|
+
statement = statement.apply_filter(filter_obj)
|
|
85
|
+
|
|
86
|
+
processed_sql, processed_params, _ = statement.process()
|
|
87
|
+
if processed_params is None:
|
|
88
|
+
return processed_sql, None
|
|
89
|
+
if isinstance(processed_params, dict):
|
|
90
|
+
return processed_sql, processed_params
|
|
91
|
+
if isinstance(processed_params, (list, tuple)):
|
|
92
|
+
return processed_sql, tuple(processed_params)
|
|
93
|
+
return processed_sql, (processed_params,) # type: ignore[unreachable]
|
|
94
|
+
|
|
52
95
|
# --- Public API Methods --- #
|
|
53
96
|
@overload
|
|
54
97
|
def select(
|
|
@@ -56,7 +99,7 @@ class DuckDBDriver(
|
|
|
56
99
|
sql: str,
|
|
57
100
|
parameters: "Optional[StatementParameterType]" = None,
|
|
58
101
|
/,
|
|
59
|
-
|
|
102
|
+
*filters: "StatementFilter",
|
|
60
103
|
connection: "Optional[DuckDBConnection]" = None,
|
|
61
104
|
schema_type: None = None,
|
|
62
105
|
**kwargs: Any,
|
|
@@ -67,7 +110,7 @@ class DuckDBDriver(
|
|
|
67
110
|
sql: str,
|
|
68
111
|
parameters: "Optional[StatementParameterType]" = None,
|
|
69
112
|
/,
|
|
70
|
-
|
|
113
|
+
*filters: "StatementFilter",
|
|
71
114
|
connection: "Optional[DuckDBConnection]" = None,
|
|
72
115
|
schema_type: "type[ModelDTOT]",
|
|
73
116
|
**kwargs: Any,
|
|
@@ -77,24 +120,28 @@ class DuckDBDriver(
|
|
|
77
120
|
sql: str,
|
|
78
121
|
parameters: "Optional[StatementParameterType]" = None,
|
|
79
122
|
/,
|
|
80
|
-
|
|
123
|
+
*filters: "StatementFilter",
|
|
81
124
|
connection: "Optional[DuckDBConnection]" = None,
|
|
82
125
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
83
126
|
**kwargs: Any,
|
|
84
|
-
) -> "Sequence[Union[
|
|
127
|
+
) -> "Sequence[Union[dict[str, Any], ModelDTOT]]":
|
|
128
|
+
"""Fetch data from the database.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
List of row data as either model instances or dictionaries.
|
|
132
|
+
"""
|
|
85
133
|
connection = self._connection(connection)
|
|
86
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
134
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
87
135
|
with self._with_cursor(connection) as cursor:
|
|
88
|
-
cursor.execute(sql, parameters
|
|
89
|
-
results = cursor.fetchall()
|
|
136
|
+
cursor.execute(sql, [] if parameters is None else parameters)
|
|
137
|
+
results = cursor.fetchall()
|
|
90
138
|
if not results:
|
|
91
139
|
return []
|
|
140
|
+
column_names = [column[0] for column in cursor.description or []]
|
|
92
141
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return [cast("ModelDTOT", schema_type(**dict(zip(column_names, row)))) for row in results] # pyright: ignore[reportUnknownArgumentType]
|
|
97
|
-
return [dict(zip(column_names, row)) for row in results] # pyright: ignore[reportUnknownArgumentType]
|
|
142
|
+
# Convert to dicts first
|
|
143
|
+
dict_results = [dict(zip(column_names, row)) for row in results]
|
|
144
|
+
return self.to_schema(dict_results, schema_type=schema_type)
|
|
98
145
|
|
|
99
146
|
@overload
|
|
100
147
|
def select_one(
|
|
@@ -102,7 +149,7 @@ class DuckDBDriver(
|
|
|
102
149
|
sql: str,
|
|
103
150
|
parameters: "Optional[StatementParameterType]" = None,
|
|
104
151
|
/,
|
|
105
|
-
|
|
152
|
+
*filters: "StatementFilter",
|
|
106
153
|
connection: "Optional[DuckDBConnection]" = None,
|
|
107
154
|
schema_type: None = None,
|
|
108
155
|
**kwargs: Any,
|
|
@@ -113,7 +160,7 @@ class DuckDBDriver(
|
|
|
113
160
|
sql: str,
|
|
114
161
|
parameters: "Optional[StatementParameterType]" = None,
|
|
115
162
|
/,
|
|
116
|
-
|
|
163
|
+
*filters: "StatementFilter",
|
|
117
164
|
connection: "Optional[DuckDBConnection]" = None,
|
|
118
165
|
schema_type: "type[ModelDTOT]",
|
|
119
166
|
**kwargs: Any,
|
|
@@ -121,25 +168,29 @@ class DuckDBDriver(
|
|
|
121
168
|
def select_one(
|
|
122
169
|
self,
|
|
123
170
|
sql: str,
|
|
124
|
-
parameters: Optional[
|
|
171
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
125
172
|
/,
|
|
126
|
-
|
|
127
|
-
connection: Optional[
|
|
173
|
+
*filters: "StatementFilter",
|
|
174
|
+
connection: "Optional[DuckDBConnection]" = None,
|
|
128
175
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
129
176
|
**kwargs: Any,
|
|
130
|
-
) -> "Union[
|
|
177
|
+
) -> "Union[dict[str, Any], ModelDTOT]":
|
|
178
|
+
"""Fetch one row from the database.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
The first row of the query results.
|
|
182
|
+
"""
|
|
131
183
|
connection = self._connection(connection)
|
|
132
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
184
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
133
185
|
with self._with_cursor(connection) as cursor:
|
|
134
|
-
cursor.execute(sql, parameters
|
|
135
|
-
result = cursor.fetchone()
|
|
136
|
-
result = self.check_not_found(result)
|
|
186
|
+
cursor.execute(sql, [] if parameters is None else parameters)
|
|
187
|
+
result = cursor.fetchone()
|
|
188
|
+
result = self.check_not_found(result)
|
|
189
|
+
column_names = [column[0] for column in cursor.description or []]
|
|
137
190
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
# Always return dictionaries
|
|
142
|
-
return dict(zip(column_names, result)) # pyright: ignore[reportUnknownArgumentType,reportUnknownVariableType]
|
|
191
|
+
# Convert to dict and use ResultConverter
|
|
192
|
+
dict_result = dict(zip(column_names, result))
|
|
193
|
+
return self.to_schema(dict_result, schema_type=schema_type)
|
|
143
194
|
|
|
144
195
|
@overload
|
|
145
196
|
def select_one_or_none(
|
|
@@ -147,7 +198,7 @@ class DuckDBDriver(
|
|
|
147
198
|
sql: str,
|
|
148
199
|
parameters: "Optional[StatementParameterType]" = None,
|
|
149
200
|
/,
|
|
150
|
-
|
|
201
|
+
*filters: "StatementFilter",
|
|
151
202
|
connection: "Optional[DuckDBConnection]" = None,
|
|
152
203
|
schema_type: None = None,
|
|
153
204
|
**kwargs: Any,
|
|
@@ -158,7 +209,7 @@ class DuckDBDriver(
|
|
|
158
209
|
sql: str,
|
|
159
210
|
parameters: "Optional[StatementParameterType]" = None,
|
|
160
211
|
/,
|
|
161
|
-
|
|
212
|
+
*filters: "StatementFilter",
|
|
162
213
|
connection: "Optional[DuckDBConnection]" = None,
|
|
163
214
|
schema_type: "type[ModelDTOT]",
|
|
164
215
|
**kwargs: Any,
|
|
@@ -166,25 +217,30 @@ class DuckDBDriver(
|
|
|
166
217
|
def select_one_or_none(
|
|
167
218
|
self,
|
|
168
219
|
sql: str,
|
|
169
|
-
parameters: Optional[
|
|
220
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
170
221
|
/,
|
|
171
|
-
|
|
172
|
-
connection: Optional[
|
|
222
|
+
*filters: "StatementFilter",
|
|
223
|
+
connection: "Optional[DuckDBConnection]" = None,
|
|
173
224
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
174
225
|
**kwargs: Any,
|
|
175
|
-
) -> "Optional[Union[
|
|
226
|
+
) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
|
|
227
|
+
"""Fetch one row from the database.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
The first row of the query results, or None if no results.
|
|
231
|
+
"""
|
|
176
232
|
connection = self._connection(connection)
|
|
177
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
233
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
178
234
|
with self._with_cursor(connection) as cursor:
|
|
179
|
-
cursor.execute(sql, parameters
|
|
180
|
-
result = cursor.fetchone()
|
|
235
|
+
cursor.execute(sql, [] if parameters is None else parameters)
|
|
236
|
+
result = cursor.fetchone()
|
|
181
237
|
if result is None:
|
|
182
238
|
return None
|
|
239
|
+
column_names = [column[0] for column in cursor.description or []]
|
|
183
240
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
return dict(zip(column_names, result)) # pyright: ignore[reportUnknownArgumentType,reportUnknownVariableType]
|
|
241
|
+
# Convert to dict and use ResultConverter
|
|
242
|
+
dict_result = dict(zip(column_names, result))
|
|
243
|
+
return self.to_schema(dict_result, schema_type=schema_type)
|
|
188
244
|
|
|
189
245
|
@overload
|
|
190
246
|
def select_value(
|
|
@@ -192,7 +248,7 @@ class DuckDBDriver(
|
|
|
192
248
|
sql: str,
|
|
193
249
|
parameters: "Optional[StatementParameterType]" = None,
|
|
194
250
|
/,
|
|
195
|
-
|
|
251
|
+
*filters: "StatementFilter",
|
|
196
252
|
connection: "Optional[DuckDBConnection]" = None,
|
|
197
253
|
schema_type: None = None,
|
|
198
254
|
**kwargs: Any,
|
|
@@ -203,7 +259,7 @@ class DuckDBDriver(
|
|
|
203
259
|
sql: str,
|
|
204
260
|
parameters: "Optional[StatementParameterType]" = None,
|
|
205
261
|
/,
|
|
206
|
-
|
|
262
|
+
*filters: "StatementFilter",
|
|
207
263
|
connection: "Optional[DuckDBConnection]" = None,
|
|
208
264
|
schema_type: "type[T]",
|
|
209
265
|
**kwargs: Any,
|
|
@@ -213,20 +269,26 @@ class DuckDBDriver(
|
|
|
213
269
|
sql: str,
|
|
214
270
|
parameters: "Optional[StatementParameterType]" = None,
|
|
215
271
|
/,
|
|
216
|
-
|
|
272
|
+
*filters: "StatementFilter",
|
|
217
273
|
connection: "Optional[DuckDBConnection]" = None,
|
|
218
274
|
schema_type: "Optional[type[T]]" = None,
|
|
219
275
|
**kwargs: Any,
|
|
220
276
|
) -> "Union[T, Any]":
|
|
277
|
+
"""Fetch a single value from the database.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
The first value from the first row of results.
|
|
281
|
+
"""
|
|
221
282
|
connection = self._connection(connection)
|
|
222
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
283
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
223
284
|
with self._with_cursor(connection) as cursor:
|
|
224
|
-
cursor.execute(sql, parameters
|
|
225
|
-
result = cursor.fetchone()
|
|
226
|
-
result = self.check_not_found(result)
|
|
285
|
+
cursor.execute(sql, [] if parameters is None else parameters)
|
|
286
|
+
result = cursor.fetchone()
|
|
287
|
+
result = self.check_not_found(result)
|
|
288
|
+
result_value = result[0]
|
|
227
289
|
if schema_type is None:
|
|
228
|
-
return
|
|
229
|
-
return schema_type(
|
|
290
|
+
return result_value
|
|
291
|
+
return schema_type(result_value) # type: ignore[call-arg]
|
|
230
292
|
|
|
231
293
|
@overload
|
|
232
294
|
def select_value_or_none(
|
|
@@ -234,7 +296,7 @@ class DuckDBDriver(
|
|
|
234
296
|
sql: str,
|
|
235
297
|
parameters: "Optional[StatementParameterType]" = None,
|
|
236
298
|
/,
|
|
237
|
-
|
|
299
|
+
*filters: "StatementFilter",
|
|
238
300
|
connection: "Optional[DuckDBConnection]" = None,
|
|
239
301
|
schema_type: None = None,
|
|
240
302
|
**kwargs: Any,
|
|
@@ -245,7 +307,7 @@ class DuckDBDriver(
|
|
|
245
307
|
sql: str,
|
|
246
308
|
parameters: "Optional[StatementParameterType]" = None,
|
|
247
309
|
/,
|
|
248
|
-
|
|
310
|
+
*filters: "StatementFilter",
|
|
249
311
|
connection: "Optional[DuckDBConnection]" = None,
|
|
250
312
|
schema_type: "type[T]",
|
|
251
313
|
**kwargs: Any,
|
|
@@ -255,36 +317,37 @@ class DuckDBDriver(
|
|
|
255
317
|
sql: str,
|
|
256
318
|
parameters: "Optional[StatementParameterType]" = None,
|
|
257
319
|
/,
|
|
258
|
-
|
|
320
|
+
*filters: "StatementFilter",
|
|
259
321
|
connection: "Optional[DuckDBConnection]" = None,
|
|
260
322
|
schema_type: "Optional[type[T]]" = None,
|
|
261
323
|
**kwargs: Any,
|
|
262
324
|
) -> "Optional[Union[T, Any]]":
|
|
263
325
|
connection = self._connection(connection)
|
|
264
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
326
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
265
327
|
with self._with_cursor(connection) as cursor:
|
|
266
|
-
cursor.execute(sql, parameters
|
|
267
|
-
result = cursor.fetchone()
|
|
328
|
+
cursor.execute(sql, [] if parameters is None else parameters)
|
|
329
|
+
result = cursor.fetchone()
|
|
268
330
|
if result is None:
|
|
269
331
|
return None
|
|
270
332
|
if schema_type is None:
|
|
271
|
-
return result[0]
|
|
333
|
+
return result[0]
|
|
272
334
|
return schema_type(result[0]) # type: ignore[call-arg]
|
|
273
335
|
|
|
274
336
|
def insert_update_delete(
|
|
275
337
|
self,
|
|
276
338
|
sql: str,
|
|
277
|
-
parameters: Optional[
|
|
339
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
278
340
|
/,
|
|
279
|
-
|
|
280
|
-
connection: Optional[
|
|
341
|
+
*filters: "StatementFilter",
|
|
342
|
+
connection: "Optional[DuckDBConnection]" = None,
|
|
281
343
|
**kwargs: Any,
|
|
282
344
|
) -> int:
|
|
283
345
|
connection = self._connection(connection)
|
|
284
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
346
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
285
347
|
with self._with_cursor(connection) as cursor:
|
|
286
|
-
|
|
287
|
-
|
|
348
|
+
params = [] if parameters is None else parameters
|
|
349
|
+
cursor.execute(sql, params)
|
|
350
|
+
return getattr(cursor, "rowcount", -1)
|
|
288
351
|
|
|
289
352
|
@overload
|
|
290
353
|
def insert_update_delete_returning(
|
|
@@ -292,7 +355,7 @@ class DuckDBDriver(
|
|
|
292
355
|
sql: str,
|
|
293
356
|
parameters: "Optional[StatementParameterType]" = None,
|
|
294
357
|
/,
|
|
295
|
-
|
|
358
|
+
*filters: "StatementFilter",
|
|
296
359
|
connection: "Optional[DuckDBConnection]" = None,
|
|
297
360
|
schema_type: None = None,
|
|
298
361
|
**kwargs: Any,
|
|
@@ -303,7 +366,7 @@ class DuckDBDriver(
|
|
|
303
366
|
sql: str,
|
|
304
367
|
parameters: "Optional[StatementParameterType]" = None,
|
|
305
368
|
/,
|
|
306
|
-
|
|
369
|
+
*filters: "StatementFilter",
|
|
307
370
|
connection: "Optional[DuckDBConnection]" = None,
|
|
308
371
|
schema_type: "type[ModelDTOT]",
|
|
309
372
|
**kwargs: Any,
|
|
@@ -313,37 +376,38 @@ class DuckDBDriver(
|
|
|
313
376
|
sql: str,
|
|
314
377
|
parameters: "Optional[StatementParameterType]" = None,
|
|
315
378
|
/,
|
|
316
|
-
|
|
379
|
+
*filters: "StatementFilter",
|
|
317
380
|
connection: "Optional[DuckDBConnection]" = None,
|
|
318
381
|
schema_type: "Optional[type[ModelDTOT]]" = None,
|
|
319
382
|
**kwargs: Any,
|
|
320
383
|
) -> "Union[ModelDTOT, dict[str, Any]]":
|
|
321
384
|
connection = self._connection(connection)
|
|
322
|
-
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
385
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
323
386
|
with self._with_cursor(connection) as cursor:
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
result =
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
#
|
|
331
|
-
|
|
387
|
+
params = [] if parameters is None else parameters
|
|
388
|
+
cursor.execute(sql, params)
|
|
389
|
+
result = cursor.fetchall()
|
|
390
|
+
result = self.check_not_found(result)
|
|
391
|
+
column_names = [col[0] for col in cursor.description or []]
|
|
392
|
+
|
|
393
|
+
# Convert to dict and use ResultConverter
|
|
394
|
+
dict_result = dict(zip(column_names, result[0]))
|
|
395
|
+
return self.to_schema(dict_result, schema_type=schema_type)
|
|
332
396
|
|
|
333
397
|
def execute_script(
|
|
334
398
|
self,
|
|
335
399
|
sql: str,
|
|
336
|
-
parameters: Optional[
|
|
400
|
+
parameters: "Optional[StatementParameterType]" = None,
|
|
337
401
|
/,
|
|
338
|
-
|
|
339
|
-
connection: Optional["DuckDBConnection"] = None,
|
|
402
|
+
connection: "Optional[DuckDBConnection]" = None,
|
|
340
403
|
**kwargs: Any,
|
|
341
404
|
) -> str:
|
|
342
405
|
connection = self._connection(connection)
|
|
343
406
|
sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
|
|
344
407
|
with self._with_cursor(connection) as cursor:
|
|
345
|
-
|
|
346
|
-
|
|
408
|
+
params = [] if parameters is None else parameters
|
|
409
|
+
cursor.execute(sql, params)
|
|
410
|
+
return cast("str", getattr(cursor, "statusmessage", "DONE"))
|
|
347
411
|
|
|
348
412
|
# --- Arrow Bulk Operations ---
|
|
349
413
|
|
|
@@ -356,8 +420,35 @@ class DuckDBDriver(
|
|
|
356
420
|
connection: "Optional[DuckDBConnection]" = None,
|
|
357
421
|
**kwargs: Any,
|
|
358
422
|
) -> "ArrowTable":
|
|
423
|
+
"""Execute a SQL query and return results as an Apache Arrow Table.
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
sql: The SQL query string.
|
|
427
|
+
parameters: Parameters for the query.
|
|
428
|
+
connection: Optional connection override.
|
|
429
|
+
**kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
|
|
430
|
+
|
|
431
|
+
Returns:
|
|
432
|
+
An Apache Arrow Table containing the query results.
|
|
433
|
+
"""
|
|
359
434
|
connection = self._connection(connection)
|
|
360
|
-
|
|
435
|
+
|
|
436
|
+
# Extract filters from kwargs if present
|
|
437
|
+
filters = kwargs.pop("filters", [])
|
|
438
|
+
|
|
439
|
+
sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
|
|
361
440
|
with self._with_cursor(connection) as cursor:
|
|
362
|
-
|
|
363
|
-
|
|
441
|
+
params = [] if parameters is None else parameters
|
|
442
|
+
cursor.execute(sql, params)
|
|
443
|
+
return cast("ArrowTable", cursor.fetch_arrow_table())
|
|
444
|
+
|
|
445
|
+
def _connection(self, connection: "Optional[DuckDBConnection]" = None) -> "DuckDBConnection":
|
|
446
|
+
"""Get the connection to use for the operation.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
connection: Optional connection to use.
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
The connection to use.
|
|
453
|
+
"""
|
|
454
|
+
return connection or self.connection
|