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

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 +167 -108
  5. sqlspec/adapters/aiosqlite/__init__.py +2 -2
  6. sqlspec/adapters/aiosqlite/config.py +2 -2
  7. sqlspec/adapters/aiosqlite/driver.py +28 -39
  8. sqlspec/adapters/asyncmy/__init__.py +3 -3
  9. sqlspec/adapters/asyncmy/config.py +11 -12
  10. sqlspec/adapters/asyncmy/driver.py +25 -34
  11. sqlspec/adapters/asyncpg/__init__.py +5 -5
  12. sqlspec/adapters/asyncpg/config.py +17 -19
  13. sqlspec/adapters/asyncpg/driver.py +249 -93
  14. sqlspec/adapters/duckdb/__init__.py +2 -2
  15. sqlspec/adapters/duckdb/config.py +2 -2
  16. sqlspec/adapters/duckdb/driver.py +49 -49
  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 +114 -41
  22. sqlspec/adapters/psqlpy/__init__.py +0 -0
  23. sqlspec/adapters/psqlpy/config.py +258 -0
  24. sqlspec/adapters/psqlpy/driver.py +335 -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 +180 -218
  30. sqlspec/adapters/sqlite/__init__.py +2 -2
  31. sqlspec/adapters/sqlite/config.py +2 -2
  32. sqlspec/adapters/sqlite/driver.py +43 -41
  33. sqlspec/base.py +275 -153
  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 +6 -1
  38. sqlspec/statement.py +373 -0
  39. sqlspec/typing.py +10 -1
  40. {sqlspec-0.8.0.dist-info → sqlspec-0.9.0.dist-info}/METADATA +4 -1
  41. sqlspec-0.9.0.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.0.dist-info}/WHEEL +0 -0
  44. {sqlspec-0.8.0.dist-info → sqlspec-0.9.0.dist-info}/licenses/LICENSE +0 -0
  45. {sqlspec-0.8.0.dist-info → sqlspec-0.9.0.dist-info}/licenses/NOTICE +0 -0
@@ -1,9 +1,13 @@
1
+ import logging
2
+ import re
1
3
  from typing import TYPE_CHECKING, Any, Optional, Union, cast
2
4
 
3
5
  from asyncpg import Connection
4
6
  from typing_extensions import TypeAlias
5
7
 
6
8
  from sqlspec.base import AsyncDriverAdapterProtocol, T
9
+ from sqlspec.exceptions import SQLParsingError
10
+ from sqlspec.statement import PARAM_REGEX, SQLStatement
7
11
 
8
12
  if TYPE_CHECKING:
9
13
  from asyncpg.connection import Connection
@@ -11,33 +15,196 @@ if TYPE_CHECKING:
11
15
 
12
16
  from sqlspec.typing import ModelDTOT, StatementParameterType
13
17
 
14
- __all__ = ("AsyncpgDriver",)
18
+ __all__ = ("AsyncpgConnection", "AsyncpgDriver")
15
19
 
20
+ logger = logging.getLogger("sqlspec")
16
21
 
17
- PgConnection: TypeAlias = "Union[Connection[Any], PoolConnectionProxy[Any]]" # pyright: ignore[reportMissingTypeArgument]
22
+ # Regex to find '?' placeholders, skipping those inside quotes or SQL comments
23
+ # Simplified version, assumes standard SQL quoting/comments
24
+ QMARK_REGEX = re.compile(
25
+ r"""(?P<dquote>"[^"]*") | # Double-quoted strings
26
+ (?P<squote>\'[^\']*\') | # Single-quoted strings
27
+ (?P<comment>--[^\n]*|/\*.*?\*/) | # SQL comments (single/multi-line)
28
+ (?P<qmark>\?) # The question mark placeholder
29
+ """,
30
+ re.VERBOSE | re.DOTALL,
31
+ )
18
32
 
33
+ AsyncpgConnection: TypeAlias = "Union[Connection[Any], PoolConnectionProxy[Any]]" # pyright: ignore[reportMissingTypeArgument]
19
34
 
20
- class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
35
+
36
+ class AsyncpgDriver(AsyncDriverAdapterProtocol["AsyncpgConnection"]):
21
37
  """AsyncPG Postgres Driver Adapter."""
22
38
 
23
- connection: "PgConnection"
39
+ connection: "AsyncpgConnection"
40
+ dialect: str = "postgres"
24
41
 
25
- def __init__(self, connection: "PgConnection") -> None:
42
+ def __init__(self, connection: "AsyncpgConnection") -> None:
26
43
  self.connection = connection
27
44
 
28
- def _process_sql_params(
29
- self, sql: str, parameters: "Optional[StatementParameterType]" = None
30
- ) -> "tuple[str, Union[tuple[Any, ...], list[Any], dict[str, Any]]]":
31
- sql, parameters = super()._process_sql_params(sql, parameters)
32
- return sql, parameters if parameters is not None else ()
45
+ def _process_sql_params( # noqa: C901, PLR0912, PLR0915
46
+ self,
47
+ sql: str,
48
+ parameters: "Optional[StatementParameterType]" = None,
49
+ /,
50
+ **kwargs: Any,
51
+ ) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
52
+ # Use SQLStatement for parameter validation and merging first
53
+ # It also handles potential dialect-specific logic if implemented there.
54
+ stmt = SQLStatement(sql=sql, parameters=parameters, dialect=self.dialect, kwargs=kwargs or None)
55
+ sql, parameters = stmt.process()
56
+
57
+ # Case 1: Parameters are effectively a dictionary (either passed as dict or via kwargs merged by SQLStatement)
58
+ if isinstance(parameters, dict):
59
+ processed_sql_parts: list[str] = []
60
+ ordered_params = []
61
+ last_end = 0
62
+ param_index = 1
63
+ found_params_regex: list[str] = []
64
+
65
+ # Manually parse the PROCESSED SQL for :name -> $n conversion
66
+ for match in PARAM_REGEX.finditer(sql):
67
+ # Skip matches inside quotes or comments
68
+ if match.group("dquote") or match.group("squote") or match.group("comment"):
69
+ continue
70
+
71
+ if match.group("var_name"): # Finds :var_name
72
+ var_name = match.group("var_name")
73
+ found_params_regex.append(var_name)
74
+ start = match.start("var_name") - 1 # Include the ':'
75
+ end = match.end("var_name")
76
+
77
+ # SQLStatement should have already validated parameter existence,
78
+ # but we double-check here during ordering.
79
+ if var_name not in parameters:
80
+ # This should ideally not happen if SQLStatement validation is robust.
81
+ msg = (
82
+ f"Named parameter ':{var_name}' found in SQL but missing from processed parameters. "
83
+ f"Processed SQL: {sql}"
84
+ )
85
+ raise SQLParsingError(msg)
86
+
87
+ processed_sql_parts.extend((sql[last_end:start], f"${param_index}"))
88
+ ordered_params.append(parameters[var_name])
89
+ last_end = end
90
+ param_index += 1
91
+
92
+ processed_sql_parts.append(sql[last_end:])
93
+ final_sql = "".join(processed_sql_parts)
94
+
95
+ # --- Validation ---
96
+ # Check if named placeholders were found if dict params were provided
97
+ # SQLStatement might handle this validation, but a warning here can be useful.
98
+ if not found_params_regex and parameters:
99
+ logger.warning(
100
+ "Dict params provided (%s), but no :name placeholders found. SQL: %s",
101
+ list(parameters.keys()),
102
+ sql,
103
+ )
104
+ # If no placeholders, return original SQL from SQLStatement and empty tuple for asyncpg
105
+ return sql, ()
106
+
107
+ # Additional checks (potentially redundant if SQLStatement covers them):
108
+ # 1. Ensure all found placeholders have corresponding params (covered by check inside loop)
109
+ # 2. Ensure all provided params correspond to a placeholder
110
+ provided_keys = set(parameters.keys())
111
+ found_keys = set(found_params_regex)
112
+ unused_keys = provided_keys - found_keys
113
+ if unused_keys:
114
+ # SQLStatement might handle this, but log a warning just in case.
115
+ logger.warning(
116
+ "Parameters provided but not used in SQL: %s. SQL: %s",
117
+ unused_keys,
118
+ sql,
119
+ )
120
+
121
+ return final_sql, tuple(ordered_params) # asyncpg expects a sequence
122
+
123
+ # Case 2: Parameters are effectively a sequence/scalar (merged by SQLStatement)
124
+ if isinstance(parameters, (list, tuple)):
125
+ # Parameters are a sequence, need to convert ? -> $n
126
+ sequence_processed_parts: list[str] = []
127
+ param_index = 1
128
+ last_end = 0
129
+ qmark_found = False
130
+
131
+ # Manually parse the PROCESSED SQL to find '?' outside comments/quotes and convert to $n
132
+ for match in QMARK_REGEX.finditer(sql):
133
+ if match.group("dquote") or match.group("squote") or match.group("comment"):
134
+ continue # Skip quotes and comments
135
+
136
+ if match.group("qmark"):
137
+ qmark_found = True
138
+ start = match.start("qmark")
139
+ end = match.end("qmark")
140
+ sequence_processed_parts.extend((sql[last_end:start], f"${param_index}"))
141
+ last_end = end
142
+ param_index += 1
143
+
144
+ sequence_processed_parts.append(sql[last_end:])
145
+ final_sql = "".join(sequence_processed_parts)
146
+
147
+ # --- Validation ---
148
+ # Check if '?' was found if parameters were provided
149
+ if parameters and not qmark_found:
150
+ # SQLStatement might allow this, log a warning.
151
+ logger.warning(
152
+ "Sequence/scalar parameters provided, but no '?' placeholders found. SQL: %s",
153
+ sql,
154
+ )
155
+ # Return PROCESSED SQL from SQLStatement as no conversion happened here
156
+ return sql, parameters
157
+
158
+ # Check parameter count match (using count from manual parsing vs count from stmt)
159
+ expected_params = param_index - 1
160
+ actual_params = len(parameters)
161
+ if expected_params != actual_params:
162
+ msg = (
163
+ f"Parameter count mismatch: Processed SQL expected {expected_params} parameters ('$n'), "
164
+ f"but {actual_params} were provided by SQLStatement. "
165
+ f"Final Processed SQL: {final_sql}"
166
+ )
167
+ raise SQLParsingError(msg)
168
+
169
+ return final_sql, parameters
170
+
171
+ # Case 3: Parameters are None (as determined by SQLStatement)
172
+ # processed_params is None
173
+ # Check if the SQL contains any placeholders unexpectedly
174
+ # Check for :name style
175
+ named_placeholders_found = False
176
+ for match in PARAM_REGEX.finditer(sql):
177
+ if not (match.group("dquote") or match.group("squote") or match.group("comment")) and match.group(
178
+ "var_name"
179
+ ):
180
+ named_placeholders_found = True
181
+ break
182
+ if named_placeholders_found:
183
+ msg = f"Processed SQL contains named parameters (:name) but no parameters were provided. SQL: {sql}"
184
+ raise SQLParsingError(msg)
185
+
186
+ # Check for ? style
187
+ qmark_placeholders_found = False
188
+ for match in QMARK_REGEX.finditer(sql):
189
+ if not (match.group("dquote") or match.group("squote") or match.group("comment")) and match.group("qmark"):
190
+ qmark_placeholders_found = True
191
+ break
192
+ if qmark_placeholders_found:
193
+ msg = f"Processed SQL contains positional parameters (?) but no parameters were provided. SQL: {sql}"
194
+ raise SQLParsingError(msg)
195
+
196
+ # No parameters provided and none found in SQL, return original SQL from SQLStatement and empty tuple
197
+ return sql, () # asyncpg expects a sequence, even if empty
33
198
 
34
199
  async def select(
35
200
  self,
36
201
  sql: str,
37
202
  parameters: Optional["StatementParameterType"] = None,
38
203
  /,
39
- connection: Optional["PgConnection"] = None,
204
+ *,
205
+ connection: Optional["AsyncpgConnection"] = None,
40
206
  schema_type: "Optional[type[ModelDTOT]]" = None,
207
+ **kwargs: Any,
41
208
  ) -> "list[Union[ModelDTOT, dict[str, Any]]]":
42
209
  """Fetch data from the database.
43
210
 
@@ -46,14 +213,16 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
46
213
  parameters: Query parameters.
47
214
  connection: Optional connection to use.
48
215
  schema_type: Optional schema class for the result.
216
+ **kwargs: Additional keyword arguments.
49
217
 
50
218
  Returns:
51
219
  List of row data as either model instances or dictionaries.
52
220
  """
53
221
  connection = self._connection(connection)
54
- sql, parameters = self._process_sql_params(sql, parameters)
222
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
223
+ parameters = parameters if parameters is not None else {}
55
224
 
56
- results = await connection.fetch(sql, *parameters) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
225
+ results = await connection.fetch(sql, *parameters) # pyright: ignore
57
226
  if not results:
58
227
  return []
59
228
  if schema_type is None:
@@ -65,8 +234,10 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
65
234
  sql: str,
66
235
  parameters: Optional["StatementParameterType"] = None,
67
236
  /,
68
- connection: Optional["PgConnection"] = None,
237
+ *,
238
+ connection: Optional["AsyncpgConnection"] = None,
69
239
  schema_type: "Optional[type[ModelDTOT]]" = None,
240
+ **kwargs: Any,
70
241
  ) -> "Union[ModelDTOT, dict[str, Any]]":
71
242
  """Fetch one row from the database.
72
243
 
@@ -75,16 +246,15 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
75
246
  parameters: Query parameters.
76
247
  connection: Optional connection to use.
77
248
  schema_type: Optional schema class for the result.
249
+ **kwargs: Additional keyword arguments.
78
250
 
79
251
  Returns:
80
252
  The first row of the query results.
81
253
  """
82
254
  connection = self._connection(connection)
83
- sql, params = self._process_sql_params(sql, parameters)
84
- # Use empty tuple if params is None
85
- params = params if params is not None else ()
86
-
87
- result = await connection.fetchrow(sql, *params) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
255
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
256
+ parameters = parameters if parameters is not None else {}
257
+ result = await connection.fetchrow(sql, *parameters) # pyright: ignore
88
258
  result = self.check_not_found(result)
89
259
 
90
260
  if schema_type is None:
@@ -97,8 +267,10 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
97
267
  sql: str,
98
268
  parameters: Optional["StatementParameterType"] = None,
99
269
  /,
100
- connection: Optional["PgConnection"] = None,
270
+ *,
271
+ connection: Optional["AsyncpgConnection"] = None,
101
272
  schema_type: "Optional[type[ModelDTOT]]" = None,
273
+ **kwargs: Any,
102
274
  ) -> "Optional[Union[ModelDTOT, dict[str, Any]]]":
103
275
  """Fetch one row from the database.
104
276
 
@@ -107,51 +279,62 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
107
279
  parameters: Query parameters.
108
280
  connection: Optional connection to use.
109
281
  schema_type: Optional schema class for the result.
282
+ **kwargs: Additional keyword arguments.
110
283
 
111
284
  Returns:
112
285
  The first row of the query results.
113
286
  """
114
287
  connection = self._connection(connection)
115
- sql, parameters = self._process_sql_params(sql, parameters)
116
-
117
- result = await connection.fetchrow(sql, *parameters) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
118
- result = self.check_not_found(result)
288
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
289
+ parameters = parameters if parameters is not None else {}
290
+ result = await connection.fetchrow(sql, *parameters) # pyright: ignore
291
+ if result is None:
292
+ return None
119
293
  if schema_type is None:
120
294
  # Always return as dictionary
121
- return dict(result.items()) # type: ignore[attr-defined]
122
- return cast("ModelDTOT", schema_type(**dict(result.items()))) # type: ignore[attr-defined]
295
+ return dict(result.items())
296
+ return cast("ModelDTOT", schema_type(**dict(result.items())))
123
297
 
124
298
  async def select_value(
125
299
  self,
126
300
  sql: str,
127
301
  parameters: "Optional[StatementParameterType]" = None,
128
302
  /,
129
- connection: "Optional[PgConnection]" = None,
303
+ *,
304
+ connection: "Optional[AsyncpgConnection]" = None,
130
305
  schema_type: "Optional[type[T]]" = None,
306
+ **kwargs: Any,
131
307
  ) -> "Union[T, Any]":
132
308
  """Fetch a single value from the database.
133
309
 
310
+ Args:
311
+ sql: SQL statement.
312
+ parameters: Query parameters.
313
+ connection: Optional connection to use.
314
+ schema_type: Optional schema class for the result.
315
+ **kwargs: Additional keyword arguments.
316
+
134
317
  Returns:
135
318
  The first value from the first row of results, or None if no results.
136
319
  """
137
320
  connection = self._connection(connection)
138
- sql, params = self._process_sql_params(sql, parameters)
139
- # Use empty tuple if params is None
140
- params = params if params is not None else ()
141
-
142
- result = await connection.fetchval(sql, *params) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
321
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
322
+ parameters = parameters if parameters is not None else {}
323
+ result = await connection.fetchval(sql, *parameters) # pyright: ignore
143
324
  result = self.check_not_found(result)
144
325
  if schema_type is None:
145
- return result[0]
146
- return schema_type(result[0]) # type: ignore[call-arg]
326
+ return result
327
+ return schema_type(result) # type: ignore[call-arg]
147
328
 
148
329
  async def select_value_or_none(
149
330
  self,
150
331
  sql: str,
151
332
  parameters: "Optional[StatementParameterType]" = None,
152
333
  /,
153
- connection: "Optional[PgConnection]" = None,
334
+ *,
335
+ connection: "Optional[AsyncpgConnection]" = None,
154
336
  schema_type: "Optional[type[T]]" = None,
337
+ **kwargs: Any,
155
338
  ) -> "Optional[Union[T, Any]]":
156
339
  """Fetch a single value from the database.
157
340
 
@@ -159,23 +342,23 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
159
342
  The first value from the first row of results, or None if no results.
160
343
  """
161
344
  connection = self._connection(connection)
162
- sql, params = self._process_sql_params(sql, parameters)
163
- # Use empty tuple if params is None
164
- params = params if params is not None else ()
165
-
166
- result = await connection.fetchval(sql, *params) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
345
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
346
+ parameters = parameters if parameters is not None else {}
347
+ result = await connection.fetchval(sql, *parameters) # pyright: ignore
167
348
  if result is None:
168
349
  return None
169
350
  if schema_type is None:
170
- return result[0]
171
- return schema_type(result[0]) # type: ignore[call-arg]
351
+ return result
352
+ return schema_type(result) # type: ignore[call-arg]
172
353
 
173
354
  async def insert_update_delete(
174
355
  self,
175
356
  sql: str,
176
357
  parameters: Optional["StatementParameterType"] = None,
177
358
  /,
178
- connection: Optional["PgConnection"] = None,
359
+ *,
360
+ connection: Optional["AsyncpgConnection"] = None,
361
+ **kwargs: Any,
179
362
  ) -> int:
180
363
  """Insert, update, or delete data from the database.
181
364
 
@@ -183,16 +366,15 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
183
366
  sql: SQL statement.
184
367
  parameters: Query parameters.
185
368
  connection: Optional connection to use.
369
+ **kwargs: Additional keyword arguments.
186
370
 
187
371
  Returns:
188
372
  Row count affected by the operation.
189
373
  """
190
374
  connection = self._connection(connection)
191
- sql, params = self._process_sql_params(sql, parameters)
192
- # Use empty tuple if params is None
193
- params = params if params is not None else ()
194
-
195
- status = await connection.execute(sql, *params) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
375
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
376
+ parameters = parameters if parameters is not None else {}
377
+ status = await connection.execute(sql, *parameters) # pyright: ignore
196
378
  # AsyncPG returns a string like "INSERT 0 1" where the last number is the affected rows
197
379
  try:
198
380
  return int(status.split()[-1]) # pyright: ignore[reportUnknownMemberType]
@@ -204,26 +386,27 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
204
386
  sql: str,
205
387
  parameters: Optional["StatementParameterType"] = None,
206
388
  /,
207
- connection: Optional["PgConnection"] = None,
389
+ *,
390
+ connection: Optional["AsyncpgConnection"] = None,
208
391
  schema_type: "Optional[type[ModelDTOT]]" = None,
392
+ **kwargs: Any,
209
393
  ) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
210
- """Insert, update, or delete data from the database and return result.
394
+ """Insert, update, or delete data from the database and return the affected row.
211
395
 
212
396
  Args:
213
397
  sql: SQL statement.
214
398
  parameters: Query parameters.
215
399
  connection: Optional connection to use.
216
400
  schema_type: Optional schema class for the result.
401
+ **kwargs: Additional keyword arguments.
217
402
 
218
403
  Returns:
219
- The first row of results.
404
+ The affected row data as either a model instance or dictionary.
220
405
  """
221
406
  connection = self._connection(connection)
222
- sql, params = self._process_sql_params(sql, parameters)
223
- # Use empty tuple if params is None
224
- params = params if params is not None else ()
225
-
226
- result = await connection.fetchrow(sql, *params) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
407
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
408
+ parameters = parameters if parameters is not None else {}
409
+ result = await connection.fetchrow(sql, *parameters) # pyright: ignore
227
410
  if result is None:
228
411
  return None
229
412
  if schema_type is None:
@@ -236,7 +419,9 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
236
419
  sql: str,
237
420
  parameters: Optional["StatementParameterType"] = None,
238
421
  /,
239
- connection: Optional["PgConnection"] = None,
422
+ *,
423
+ connection: Optional["AsyncpgConnection"] = None,
424
+ **kwargs: Any,
240
425
  ) -> str:
241
426
  """Execute a script.
242
427
 
@@ -244,45 +429,16 @@ class AsyncpgDriver(AsyncDriverAdapterProtocol["PgConnection"]):
244
429
  sql: SQL statement.
245
430
  parameters: Query parameters.
246
431
  connection: Optional connection to use.
432
+ **kwargs: Additional keyword arguments.
247
433
 
248
434
  Returns:
249
435
  Status message for the operation.
250
436
  """
251
437
  connection = self._connection(connection)
252
- sql, params = self._process_sql_params(sql, parameters)
253
- # Use empty tuple if params is None
254
- params = params if params is not None else ()
255
-
256
- return await connection.execute(sql, *params) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
257
-
258
- async def execute_script_returning(
259
- self,
260
- sql: str,
261
- parameters: Optional["StatementParameterType"] = None,
262
- /,
263
- connection: Optional["PgConnection"] = None,
264
- schema_type: "Optional[type[ModelDTOT]]" = None,
265
- ) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
266
- """Execute a script and return result.
267
-
268
- Args:
269
- sql: SQL statement.
270
- parameters: Query parameters.
271
- connection: Optional connection to use.
272
- schema_type: Optional schema class for the result.
273
-
274
- Returns:
275
- The first row of results.
276
- """
277
- connection = self._connection(connection)
278
- sql, params = self._process_sql_params(sql, parameters)
279
- # Use empty tuple if params is None
280
- params = params if params is not None else ()
438
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
439
+ parameters = parameters if parameters is not None else {}
440
+ return await connection.execute(sql, *parameters) # pyright: ignore
281
441
 
282
- result = await connection.fetchrow(sql, *params) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
283
- if result is None:
284
- return None
285
- if schema_type is None:
286
- # Always return as dictionary
287
- return dict(result.items()) # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType]
288
- return cast("ModelDTOT", schema_type(**dict(result.items()))) # pyright: ignore[reportUnknownArgumentType, reportUnknownMemberType, reportUnknownVariableType]
442
+ def _connection(self, connection: Optional["AsyncpgConnection"] = None) -> "AsyncpgConnection":
443
+ """Return the connection to use. If None, use the default connection."""
444
+ return connection if connection is not None else self.connection
@@ -1,7 +1,7 @@
1
- from sqlspec.adapters.duckdb.config import DuckDB
1
+ from sqlspec.adapters.duckdb.config import DuckDBConfig
2
2
  from sqlspec.adapters.duckdb.driver import DuckDBDriver
3
3
 
4
4
  __all__ = (
5
- "DuckDB",
5
+ "DuckDBConfig",
6
6
  "DuckDBDriver",
7
7
  )
@@ -14,7 +14,7 @@ if TYPE_CHECKING:
14
14
  from collections.abc import Generator, Sequence
15
15
 
16
16
 
17
- __all__ = ("DuckDB", "ExtensionConfig")
17
+ __all__ = ("DuckDBConfig", "ExtensionConfig")
18
18
 
19
19
 
20
20
  class ExtensionConfig(TypedDict):
@@ -69,7 +69,7 @@ class SecretConfig(TypedDict):
69
69
 
70
70
 
71
71
  @dataclass
72
- class DuckDB(NoPoolSyncConfig["DuckDBPyConnection", "DuckDBDriver"]):
72
+ class DuckDBConfig(NoPoolSyncConfig["DuckDBPyConnection", "DuckDBDriver"]):
73
73
  """Configuration for DuckDB database connections.
74
74
 
75
75
  This class provides configuration options for DuckDB database connections, wrapping all parameters