sqlspec 0.8.0__py3-none-any.whl → 0.9.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.

Files changed (45) hide show
  1. sqlspec/_typing.py +39 -6
  2. sqlspec/adapters/adbc/__init__.py +2 -2
  3. sqlspec/adapters/adbc/config.py +34 -11
  4. sqlspec/adapters/adbc/driver.py +302 -111
  5. sqlspec/adapters/aiosqlite/__init__.py +2 -2
  6. sqlspec/adapters/aiosqlite/config.py +2 -2
  7. sqlspec/adapters/aiosqlite/driver.py +164 -42
  8. sqlspec/adapters/asyncmy/__init__.py +3 -3
  9. sqlspec/adapters/asyncmy/config.py +11 -12
  10. sqlspec/adapters/asyncmy/driver.py +161 -37
  11. sqlspec/adapters/asyncpg/__init__.py +5 -5
  12. sqlspec/adapters/asyncpg/config.py +17 -19
  13. sqlspec/adapters/asyncpg/driver.py +386 -96
  14. sqlspec/adapters/duckdb/__init__.py +2 -2
  15. sqlspec/adapters/duckdb/config.py +2 -2
  16. sqlspec/adapters/duckdb/driver.py +190 -60
  17. sqlspec/adapters/oracledb/__init__.py +8 -8
  18. sqlspec/adapters/oracledb/config/__init__.py +6 -6
  19. sqlspec/adapters/oracledb/config/_asyncio.py +9 -10
  20. sqlspec/adapters/oracledb/config/_sync.py +8 -9
  21. sqlspec/adapters/oracledb/driver.py +384 -45
  22. sqlspec/adapters/psqlpy/__init__.py +0 -0
  23. sqlspec/adapters/psqlpy/config.py +250 -0
  24. sqlspec/adapters/psqlpy/driver.py +481 -0
  25. sqlspec/adapters/psycopg/__init__.py +10 -5
  26. sqlspec/adapters/psycopg/config/__init__.py +6 -6
  27. sqlspec/adapters/psycopg/config/_async.py +12 -12
  28. sqlspec/adapters/psycopg/config/_sync.py +13 -13
  29. sqlspec/adapters/psycopg/driver.py +432 -222
  30. sqlspec/adapters/sqlite/__init__.py +2 -2
  31. sqlspec/adapters/sqlite/config.py +2 -2
  32. sqlspec/adapters/sqlite/driver.py +176 -72
  33. sqlspec/base.py +687 -161
  34. sqlspec/exceptions.py +30 -0
  35. sqlspec/extensions/litestar/config.py +6 -0
  36. sqlspec/extensions/litestar/handlers.py +25 -0
  37. sqlspec/extensions/litestar/plugin.py +8 -1
  38. sqlspec/statement.py +373 -0
  39. sqlspec/typing.py +10 -1
  40. {sqlspec-0.8.0.dist-info → sqlspec-0.9.1.dist-info}/METADATA +144 -2
  41. sqlspec-0.9.1.dist-info/RECORD +61 -0
  42. sqlspec-0.8.0.dist-info/RECORD +0 -57
  43. {sqlspec-0.8.0.dist-info → sqlspec-0.9.1.dist-info}/WHEEL +0 -0
  44. {sqlspec-0.8.0.dist-info → sqlspec-0.9.1.dist-info}/licenses/LICENSE +0 -0
  45. {sqlspec-0.8.0.dist-info → sqlspec-0.9.1.dist-info}/licenses/NOTICE +0 -0
@@ -1,41 +1,73 @@
1
1
  import contextlib
2
+ import logging
2
3
  import re
3
- from collections.abc import Generator
4
+ from collections.abc import Generator, Sequence
4
5
  from contextlib import contextmanager
5
- from typing import TYPE_CHECKING, Any, Optional, Union, cast
6
+ from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union, cast, overload
6
7
 
7
8
  from adbc_driver_manager.dbapi import Connection, Cursor
8
9
 
9
- from sqlspec.base import SyncDriverAdapterProtocol, T
10
+ from sqlspec.base import SyncArrowBulkOperationsMixin, SyncDriverAdapterProtocol, T
11
+ from sqlspec.exceptions import ParameterStyleMismatchError, SQLParsingError
12
+ from sqlspec.statement import SQLStatement
13
+ from sqlspec.typing import ArrowTable, StatementParameterType
10
14
 
11
15
  if TYPE_CHECKING:
12
- from sqlspec.typing import ModelDTOT, StatementParameterType
16
+ from sqlspec.typing import ArrowTable, ModelDTOT, StatementParameterType
13
17
 
14
18
  __all__ = ("AdbcDriver",)
15
19
 
20
+ logger = logging.getLogger("sqlspec")
21
+
16
22
 
17
- # Regex to find :param or %(param)s style placeholders, skipping those inside quotes
18
23
  PARAM_REGEX = re.compile(
19
- r"""
20
- (?P<dquote>"([^"]|\\")*") | # Double-quoted strings
21
- (?P<squote>'([^']|\\')*') | # Single-quoted strings
22
- : (?P<var_name_colon>[a-zA-Z_][a-zA-Z0-9_]*) | # :var_name
23
- % \( (?P<var_name_perc>[a-zA-Z_][a-zA-Z0-9_]*) \) s # %(var_name)s
24
+ r"""(?<![:\w\$]) # Avoid matching ::, \:, etc. and other vendor prefixes
25
+ (?:
26
+ (?P<dquote>"(?:[^"]|"")*") | # Double-quoted strings
27
+ (?P<squote>'(?:[^']|'')*') | # Single-quoted strings
28
+ (?P<comment>--.*?\n|\/\*.*?\*\/) | # SQL comments
29
+ (?P<lead>[:\$])(?P<var_name>[a-zA-Z_][a-zA-Z0-9_]*) # :name or $name identifier
30
+ )
24
31
  """,
25
- re.VERBOSE,
32
+ re.VERBOSE | re.DOTALL,
26
33
  )
27
34
 
28
35
 
29
- class AdbcDriver(SyncDriverAdapterProtocol["Connection"]):
36
+ class AdbcDriver(SyncArrowBulkOperationsMixin["Connection"], SyncDriverAdapterProtocol["Connection"]):
30
37
  """ADBC Sync Driver Adapter."""
31
38
 
32
39
  connection: Connection
40
+ __supports_arrow__: ClassVar[bool] = True
33
41
 
34
42
  def __init__(self, connection: "Connection") -> None:
35
43
  """Initialize the ADBC driver adapter."""
36
44
  self.connection = connection
37
- # Potentially introspect connection.paramstyle here if needed in the future
38
- # For now, assume 'qmark' based on typical ADBC DBAPI behavior
45
+ self.dialect = self._get_dialect(connection)
46
+
47
+ @staticmethod
48
+ def _get_dialect(connection: "Connection") -> str: # noqa: PLR0911
49
+ """Get the database dialect based on the driver name.
50
+
51
+ Args:
52
+ connection: The ADBC connection object.
53
+
54
+ Returns:
55
+ The database dialect.
56
+ """
57
+ driver_name = connection.adbc_get_info()["vendor_name"].lower()
58
+ if "postgres" in driver_name:
59
+ return "postgres"
60
+ if "bigquery" in driver_name:
61
+ return "bigquery"
62
+ if "sqlite" in driver_name:
63
+ return "sqlite"
64
+ if "duckdb" in driver_name:
65
+ return "duckdb"
66
+ if "mysql" in driver_name:
67
+ return "mysql"
68
+ if "snowflake" in driver_name:
69
+ return "snowflake"
70
+ return "postgres" # default to postgresql dialect
39
71
 
40
72
  @staticmethod
41
73
  def _cursor(connection: "Connection", *args: Any, **kwargs: Any) -> "Cursor":
@@ -51,87 +83,122 @@ class AdbcDriver(SyncDriverAdapterProtocol["Connection"]):
51
83
  cursor.close() # type: ignore[no-untyped-call]
52
84
 
53
85
  def _process_sql_params(
54
- self, sql: str, parameters: "Optional[StatementParameterType]" = None
86
+ self,
87
+ sql: str,
88
+ parameters: "Optional[StatementParameterType]" = None,
89
+ /,
90
+ **kwargs: Any,
55
91
  ) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
56
- """Process SQL query and parameters for DB-API execution.
57
-
58
- Converts named parameters (:name or %(name)s) to positional parameters specified by `self.param_style`
59
- if the input parameters are a dictionary.
60
-
61
- Args:
62
- sql: The SQL query string.
63
- parameters: The parameters for the query (dict, tuple, list, or None).
64
-
65
- Returns:
66
- A tuple containing the processed SQL string and the processed parameters
67
- (always a tuple or None if the input was a dictionary, otherwise the original type).
68
-
69
- Raises:
70
- ValueError: If a named parameter in the SQL is not found in the dictionary
71
- or if a parameter in the dictionary is not used in the SQL.
72
- """
73
- if not isinstance(parameters, dict) or not parameters:
74
- # If parameters are not a dict, or empty dict, assume positional/no params
75
- # Let the underlying driver handle tuples/lists directly
76
- return self._process_sql_statement(sql), parameters
77
-
78
- processed_sql = ""
79
- processed_params_list: list[Any] = []
80
- last_end = 0
81
- found_params: set[str] = set()
82
-
83
- for match in PARAM_REGEX.finditer(sql):
84
- if match.group("dquote") is not None or match.group("squote") is not None:
85
- # Skip placeholders within quotes
86
- continue
87
-
88
- # Get name from whichever group matched
89
- var_name = match.group("var_name_colon") or match.group("var_name_perc")
90
-
91
- if var_name is None: # Should not happen with the new regex structure
92
- continue
93
-
94
- if var_name not in parameters:
95
- placeholder = match.group(0) # Get the full matched placeholder
96
- msg = f"Named parameter '{placeholder}' found in SQL but not provided in parameters dictionary."
97
- raise ValueError(msg)
98
-
99
- # Append segment before the placeholder
100
- processed_sql += sql[last_end : match.start()]
101
- # Append the driver's positional placeholder
102
- processed_sql += self.param_style
103
- processed_params_list.append(parameters[var_name])
104
- found_params.add(var_name)
105
- last_end = match.end()
106
-
107
- # Append the rest of the SQL string
108
- processed_sql += sql[last_end:]
109
-
110
- # Check if all provided parameters were used
111
- unused_params = set(parameters.keys()) - found_params
112
- if unused_params:
113
- msg = f"Parameters provided but not found in SQL: {unused_params}"
114
- # Depending on desired strictness, this could be a warning or an error
115
- # For now, let's raise an error for clarity
116
- raise ValueError(msg)
117
-
118
- return self._process_sql_statement(processed_sql), tuple(processed_params_list)
119
-
92
+ # Determine effective parameter type *before* calling SQLStatement
93
+ merged_params_type = dict if kwargs else type(parameters)
94
+
95
+ # If ADBC + sqlite/duckdb + dictionary params, handle conversion manually
96
+ if self.dialect in {"sqlite", "duckdb"} and merged_params_type is dict:
97
+ logger.debug(
98
+ "ADBC/%s with dict params; bypassing SQLStatement conversion, manually converting to '?' positional.",
99
+ self.dialect,
100
+ )
101
+
102
+ # Combine parameters and kwargs into the actual dictionary to use
103
+ parameter_dict = {} # type: ignore[var-annotated]
104
+ if isinstance(parameters, dict):
105
+ parameter_dict.update(parameters)
106
+ if kwargs:
107
+ parameter_dict.update(kwargs)
108
+
109
+ # Define regex locally to find :name or $name
110
+
111
+ processed_sql_parts: list[str] = []
112
+ ordered_params = []
113
+ last_end = 0
114
+ found_params_regex: list[str] = []
115
+
116
+ for match in PARAM_REGEX.finditer(sql): # Use original sql
117
+ if match.group("dquote") or match.group("squote") or match.group("comment"):
118
+ continue
119
+
120
+ if match.group("var_name"):
121
+ var_name = match.group("var_name")
122
+ leading_char = match.group("lead") # : or $
123
+ found_params_regex.append(var_name)
124
+ # Use match span directly for replacement
125
+ start = match.start()
126
+ end = match.end()
127
+
128
+ if var_name not in parameter_dict:
129
+ msg = f"Named parameter '{leading_char}{var_name}' found in SQL but not provided. SQL: {sql}"
130
+ raise SQLParsingError(msg)
131
+
132
+ processed_sql_parts.extend((sql[last_end:start], "?")) # Force ? style
133
+ ordered_params.append(parameter_dict[var_name])
134
+ last_end = end
135
+
136
+ processed_sql_parts.append(sql[last_end:])
137
+
138
+ if not found_params_regex and parameter_dict:
139
+ msg = f"ADBC/{self.dialect}: Dict params provided, but no :name or $name placeholders found. SQL: {sql}"
140
+ raise ParameterStyleMismatchError(msg)
141
+
142
+ # Key validation
143
+ provided_keys = set(parameter_dict.keys())
144
+ missing_keys = set(found_params_regex) - provided_keys
145
+ if missing_keys:
146
+ msg = (
147
+ f"Named parameters found in SQL ({found_params_regex}) but not provided: {missing_keys}. SQL: {sql}"
148
+ )
149
+ raise SQLParsingError(msg)
150
+ extra_keys = provided_keys - set(found_params_regex)
151
+ if extra_keys:
152
+ logger.debug("Extra parameters provided for ADBC/%s: %s", self.dialect, extra_keys)
153
+ # Allow extra keys
154
+
155
+ final_sql = "".join(processed_sql_parts)
156
+ final_params = tuple(ordered_params)
157
+ return final_sql, final_params
158
+ # For all other cases (other dialects, or non-dict params for sqlite/duckdb),
159
+ # use the standard SQLStatement processing.
160
+ stmt = SQLStatement(sql=sql, parameters=parameters, dialect=self.dialect, kwargs=kwargs or None)
161
+ return stmt.process()
162
+
163
+ @overload
164
+ def select(
165
+ self,
166
+ sql: str,
167
+ parameters: "Optional[StatementParameterType]" = None,
168
+ /,
169
+ *,
170
+ connection: "Optional[Connection]" = None,
171
+ schema_type: None = None,
172
+ **kwargs: Any,
173
+ ) -> "Sequence[dict[str, Any]]": ...
174
+ @overload
175
+ def select(
176
+ self,
177
+ sql: str,
178
+ parameters: "Optional[StatementParameterType]" = None,
179
+ /,
180
+ *,
181
+ connection: "Optional[Connection]" = None,
182
+ schema_type: "type[ModelDTOT]",
183
+ **kwargs: Any,
184
+ ) -> "Sequence[ModelDTOT]": ...
120
185
  def select(
121
186
  self,
122
187
  sql: str,
123
188
  parameters: Optional["StatementParameterType"] = None,
124
189
  /,
190
+ *,
125
191
  connection: Optional["Connection"] = None,
126
192
  schema_type: "Optional[type[ModelDTOT]]" = None,
127
- ) -> "list[Union[ModelDTOT, dict[str, Any]]]":
193
+ **kwargs: Any,
194
+ ) -> "Sequence[Union[ModelDTOT, dict[str, Any]]]":
128
195
  """Fetch data from the database.
129
196
 
130
197
  Returns:
131
198
  List of row data as either model instances or dictionaries.
132
199
  """
133
200
  connection = self._connection(connection)
134
- sql, parameters = self._process_sql_params(sql, parameters)
201
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
135
202
  with self._with_cursor(connection) as cursor:
136
203
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
137
204
  results = cursor.fetchall() # pyright: ignore
@@ -144,13 +211,37 @@ class AdbcDriver(SyncDriverAdapterProtocol["Connection"]):
144
211
  return [cast("ModelDTOT", schema_type(**dict(zip(column_names, row)))) for row in results] # pyright: ignore[reportUnknownArgumentType,reportUnknownVariableType]
145
212
  return [dict(zip(column_names, row)) for row in results] # pyright: ignore[reportUnknownArgumentType,reportUnknownVariableType]
146
213
 
214
+ @overload
215
+ def select_one(
216
+ self,
217
+ sql: str,
218
+ parameters: "Optional[StatementParameterType]" = None,
219
+ /,
220
+ *,
221
+ connection: "Optional[Connection]" = None,
222
+ schema_type: None = None,
223
+ **kwargs: Any,
224
+ ) -> "dict[str, Any]": ...
225
+ @overload
226
+ def select_one(
227
+ self,
228
+ sql: str,
229
+ parameters: "Optional[StatementParameterType]" = None,
230
+ /,
231
+ *,
232
+ connection: "Optional[Connection]" = None,
233
+ schema_type: "type[ModelDTOT]",
234
+ **kwargs: Any,
235
+ ) -> "ModelDTOT": ...
147
236
  def select_one(
148
237
  self,
149
238
  sql: str,
150
239
  parameters: Optional["StatementParameterType"] = None,
151
240
  /,
241
+ *,
152
242
  connection: Optional["Connection"] = None,
153
243
  schema_type: "Optional[type[ModelDTOT]]" = None,
244
+ **kwargs: Any,
154
245
  ) -> "Union[ModelDTOT, dict[str, Any]]":
155
246
  """Fetch one row from the database.
156
247
 
@@ -158,7 +249,7 @@ class AdbcDriver(SyncDriverAdapterProtocol["Connection"]):
158
249
  The first row of the query results.
159
250
  """
160
251
  connection = self._connection(connection)
161
- sql, parameters = self._process_sql_params(sql, parameters)
252
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
162
253
  with self._with_cursor(connection) as cursor:
163
254
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
164
255
  result = cursor.fetchone() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
@@ -168,13 +259,37 @@ class AdbcDriver(SyncDriverAdapterProtocol["Connection"]):
168
259
  return dict(zip(column_names, result)) # pyright: ignore[reportUnknownArgumentType, reportUnknownVariableType]
169
260
  return schema_type(**dict(zip(column_names, result))) # type: ignore[return-value]
170
261
 
262
+ @overload
263
+ def select_one_or_none(
264
+ self,
265
+ sql: str,
266
+ parameters: "Optional[StatementParameterType]" = None,
267
+ /,
268
+ *,
269
+ connection: "Optional[Connection]" = None,
270
+ schema_type: None = None,
271
+ **kwargs: Any,
272
+ ) -> "Optional[dict[str, Any]]": ...
273
+ @overload
274
+ def select_one_or_none(
275
+ self,
276
+ sql: str,
277
+ parameters: "Optional[StatementParameterType]" = None,
278
+ /,
279
+ *,
280
+ connection: "Optional[Connection]" = None,
281
+ schema_type: "type[ModelDTOT]",
282
+ **kwargs: Any,
283
+ ) -> "Optional[ModelDTOT]": ...
171
284
  def select_one_or_none(
172
285
  self,
173
286
  sql: str,
174
287
  parameters: Optional["StatementParameterType"] = None,
175
288
  /,
289
+ *,
176
290
  connection: Optional["Connection"] = None,
177
291
  schema_type: "Optional[type[ModelDTOT]]" = None,
292
+ **kwargs: Any,
178
293
  ) -> "Optional[Union[ModelDTOT, dict[str, Any]]]":
179
294
  """Fetch one row from the database.
180
295
 
@@ -182,7 +297,7 @@ class AdbcDriver(SyncDriverAdapterProtocol["Connection"]):
182
297
  The first row of the query results.
183
298
  """
184
299
  connection = self._connection(connection)
185
- sql, parameters = self._process_sql_params(sql, parameters)
300
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
186
301
  with self._with_cursor(connection) as cursor:
187
302
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
188
303
  result = cursor.fetchone() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
@@ -193,13 +308,37 @@ class AdbcDriver(SyncDriverAdapterProtocol["Connection"]):
193
308
  return dict(zip(column_names, result)) # pyright: ignore[reportUnknownArgumentType, reportUnknownVariableType]
194
309
  return schema_type(**dict(zip(column_names, result))) # type: ignore[return-value]
195
310
 
311
+ @overload
312
+ def select_value(
313
+ self,
314
+ sql: str,
315
+ parameters: "Optional[StatementParameterType]" = None,
316
+ /,
317
+ *,
318
+ connection: "Optional[Connection]" = None,
319
+ schema_type: None = None,
320
+ **kwargs: Any,
321
+ ) -> "Any": ...
322
+ @overload
323
+ def select_value(
324
+ self,
325
+ sql: str,
326
+ parameters: "Optional[StatementParameterType]" = None,
327
+ /,
328
+ *,
329
+ connection: "Optional[Connection]" = None,
330
+ schema_type: "type[T]",
331
+ **kwargs: Any,
332
+ ) -> "T": ...
196
333
  def select_value(
197
334
  self,
198
335
  sql: str,
199
336
  parameters: Optional["StatementParameterType"] = None,
200
337
  /,
338
+ *,
201
339
  connection: Optional["Connection"] = None,
202
340
  schema_type: "Optional[type[T]]" = None,
341
+ **kwargs: Any,
203
342
  ) -> "Union[T, Any]":
204
343
  """Fetch a single value from the database.
205
344
 
@@ -207,7 +346,7 @@ class AdbcDriver(SyncDriverAdapterProtocol["Connection"]):
207
346
  The first value from the first row of results, or None if no results.
208
347
  """
209
348
  connection = self._connection(connection)
210
- sql, parameters = self._process_sql_params(sql, parameters)
349
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
211
350
  with self._with_cursor(connection) as cursor:
212
351
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
213
352
  result = cursor.fetchone() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
@@ -216,13 +355,37 @@ class AdbcDriver(SyncDriverAdapterProtocol["Connection"]):
216
355
  return result[0] # pyright: ignore[reportUnknownVariableType]
217
356
  return schema_type(result[0]) # type: ignore[call-arg]
218
357
 
358
+ @overload
359
+ def select_value_or_none(
360
+ self,
361
+ sql: str,
362
+ parameters: "Optional[StatementParameterType]" = None,
363
+ /,
364
+ *,
365
+ connection: "Optional[Connection]" = None,
366
+ schema_type: None = None,
367
+ **kwargs: Any,
368
+ ) -> "Optional[Any]": ...
369
+ @overload
370
+ def select_value_or_none(
371
+ self,
372
+ sql: str,
373
+ parameters: "Optional[StatementParameterType]" = None,
374
+ /,
375
+ *,
376
+ connection: "Optional[Connection]" = None,
377
+ schema_type: "type[T]",
378
+ **kwargs: Any,
379
+ ) -> "Optional[T]": ...
219
380
  def select_value_or_none(
220
381
  self,
221
382
  sql: str,
222
383
  parameters: Optional["StatementParameterType"] = None,
223
384
  /,
385
+ *,
224
386
  connection: Optional["Connection"] = None,
225
387
  schema_type: "Optional[type[T]]" = None,
388
+ **kwargs: Any,
226
389
  ) -> "Optional[Union[T, Any]]":
227
390
  """Fetch a single value from the database.
228
391
 
@@ -230,7 +393,7 @@ class AdbcDriver(SyncDriverAdapterProtocol["Connection"]):
230
393
  The first value from the first row of results, or None if no results.
231
394
  """
232
395
  connection = self._connection(connection)
233
- sql, parameters = self._process_sql_params(sql, parameters)
396
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
234
397
  with self._with_cursor(connection) as cursor:
235
398
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
236
399
  result = cursor.fetchone() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
@@ -245,7 +408,9 @@ class AdbcDriver(SyncDriverAdapterProtocol["Connection"]):
245
408
  sql: str,
246
409
  parameters: Optional["StatementParameterType"] = None,
247
410
  /,
411
+ *,
248
412
  connection: Optional["Connection"] = None,
413
+ **kwargs: Any,
249
414
  ) -> int:
250
415
  """Insert, update, or delete data from the database.
251
416
 
@@ -253,19 +418,43 @@ class AdbcDriver(SyncDriverAdapterProtocol["Connection"]):
253
418
  Row count affected by the operation.
254
419
  """
255
420
  connection = self._connection(connection)
256
- sql, parameters = self._process_sql_params(sql, parameters)
421
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
257
422
 
258
423
  with self._with_cursor(connection) as cursor:
259
424
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
260
425
  return cursor.rowcount if hasattr(cursor, "rowcount") else -1
261
426
 
427
+ @overload
428
+ def insert_update_delete_returning(
429
+ self,
430
+ sql: str,
431
+ parameters: "Optional[StatementParameterType]" = None,
432
+ /,
433
+ *,
434
+ connection: "Optional[Connection]" = None,
435
+ schema_type: None = None,
436
+ **kwargs: Any,
437
+ ) -> "dict[str, Any]": ...
438
+ @overload
439
+ def insert_update_delete_returning(
440
+ self,
441
+ sql: str,
442
+ parameters: "Optional[StatementParameterType]" = None,
443
+ /,
444
+ *,
445
+ connection: "Optional[Connection]" = None,
446
+ schema_type: "type[ModelDTOT]",
447
+ **kwargs: Any,
448
+ ) -> "ModelDTOT": ...
262
449
  def insert_update_delete_returning(
263
450
  self,
264
451
  sql: str,
265
452
  parameters: Optional["StatementParameterType"] = None,
266
453
  /,
454
+ *,
267
455
  connection: Optional["Connection"] = None,
268
456
  schema_type: "Optional[type[ModelDTOT]]" = None,
457
+ **kwargs: Any,
269
458
  ) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
270
459
  """Insert, update, or delete data from the database and return result.
271
460
 
@@ -273,25 +462,31 @@ class AdbcDriver(SyncDriverAdapterProtocol["Connection"]):
273
462
  The first row of results.
274
463
  """
275
464
  connection = self._connection(connection)
276
- sql, parameters = self._process_sql_params(sql, parameters)
277
- column_names: list[str] = []
278
-
465
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
279
466
  with self._with_cursor(connection) as cursor:
280
467
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
281
468
  result = cursor.fetchall() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
282
- if len(result) == 0: # pyright: ignore[reportUnknownArgumentType]
469
+ if not result:
283
470
  return None
471
+
472
+ first_row = result[0]
473
+
284
474
  column_names = [c[0] for c in cursor.description or []] # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
285
- if schema_type is not None:
286
- return cast("ModelDTOT", schema_type(**dict(zip(column_names, result[0])))) # pyright: ignore[reportUnknownArgumentType]
287
- return dict(zip(column_names, result[0])) # pyright: ignore[reportUnknownVariableType,reportUnknownArgumentType]
475
+
476
+ result_dict = dict(zip(column_names, first_row))
477
+
478
+ if schema_type is None:
479
+ return result_dict
480
+ return cast("ModelDTOT", schema_type(**result_dict))
288
481
 
289
482
  def execute_script(
290
483
  self,
291
484
  sql: str,
292
485
  parameters: Optional["StatementParameterType"] = None,
293
486
  /,
487
+ *,
294
488
  connection: Optional["Connection"] = None,
489
+ **kwargs: Any,
295
490
  ) -> str:
296
491
  """Execute a script.
297
492
 
@@ -305,29 +500,25 @@ class AdbcDriver(SyncDriverAdapterProtocol["Connection"]):
305
500
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
306
501
  return cast("str", cursor.statusmessage) if hasattr(cursor, "statusmessage") else "DONE" # pyright: ignore[reportUnknownMemberType,reportAttributeAccessIssue]
307
502
 
308
- def execute_script_returning(
503
+ # --- Arrow Bulk Operations ---
504
+
505
+ def select_arrow( # pyright: ignore[reportUnknownParameterType]
309
506
  self,
310
507
  sql: str,
311
- parameters: Optional["StatementParameterType"] = None,
508
+ parameters: "Optional[StatementParameterType]" = None,
312
509
  /,
313
- connection: Optional["Connection"] = None,
314
- schema_type: "Optional[type[ModelDTOT]]" = None,
315
- ) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
316
- """Execute a script and return result.
510
+ *,
511
+ connection: "Optional[Connection]" = None,
512
+ **kwargs: Any,
513
+ ) -> "ArrowTable":
514
+ """Execute a SQL query and return results as an Apache Arrow Table.
317
515
 
318
516
  Returns:
319
- The first row of results.
517
+ The results of the query as an Apache Arrow Table.
320
518
  """
321
- connection = self._connection(connection)
322
- sql, parameters = self._process_sql_params(sql, parameters)
323
- column_names: list[str] = []
519
+ conn = self._connection(connection)
520
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
324
521
 
325
- with self._with_cursor(connection) as cursor:
522
+ with self._with_cursor(conn) as cursor:
326
523
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
327
- result = cursor.fetchall() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
328
- if len(result) == 0: # pyright: ignore[reportUnknownArgumentType]
329
- return None
330
- column_names = [c[0] for c in cursor.description or []] # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
331
- if schema_type is not None:
332
- return cast("ModelDTOT", schema_type(**dict(zip(column_names, result[0])))) # pyright: ignore[reportUnknownArgumentType]
333
- return dict(zip(column_names, result[0])) # pyright: ignore[reportUnknownArgumentType, reportUnknownVariableType]
524
+ return cast("ArrowTable", cursor.fetch_arrow_table()) # pyright: ignore[reportUnknownMemberType]
@@ -1,7 +1,7 @@
1
- from sqlspec.adapters.aiosqlite.config import Aiosqlite
1
+ from sqlspec.adapters.aiosqlite.config import AiosqliteConfig
2
2
  from sqlspec.adapters.aiosqlite.driver import AiosqliteDriver
3
3
 
4
4
  __all__ = (
5
- "Aiosqlite",
5
+ "AiosqliteConfig",
6
6
  "AiosqliteDriver",
7
7
  )
@@ -15,11 +15,11 @@ if TYPE_CHECKING:
15
15
  from typing import Literal
16
16
 
17
17
 
18
- __all__ = ("Aiosqlite",)
18
+ __all__ = ("AiosqliteConfig",)
19
19
 
20
20
 
21
21
  @dataclass
22
- class Aiosqlite(NoPoolAsyncConfig["Connection", "AiosqliteDriver"]):
22
+ class AiosqliteConfig(NoPoolAsyncConfig["Connection", "AiosqliteDriver"]):
23
23
  """Configuration for Aiosqlite database connections.
24
24
 
25
25
  This class provides configuration options for Aiosqlite database connections, wrapping all parameters