sqlspec 0.10.1__py3-none-any.whl → 0.11.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.

@@ -1,17 +1,19 @@
1
1
  import contextlib
2
2
  import logging
3
3
  import re
4
- from collections.abc import Generator, Sequence
4
+ from collections.abc import Generator, Mapping, Sequence
5
5
  from contextlib import contextmanager
6
6
  from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union, cast, overload
7
7
 
8
8
  from adbc_driver_manager.dbapi import Connection, Cursor
9
+ from sqlglot import exp as sqlglot_exp
9
10
 
10
11
  from sqlspec.base import SyncDriverAdapterProtocol
11
- from sqlspec.exceptions import ParameterStyleMismatchError, SQLParsingError
12
- from sqlspec.mixins import SQLTranslatorMixin, SyncArrowBulkOperationsMixin
12
+ from sqlspec.exceptions import SQLParsingError
13
+ from sqlspec.filters import StatementFilter
14
+ from sqlspec.mixins import ResultConverter, SQLTranslatorMixin, SyncArrowBulkOperationsMixin
13
15
  from sqlspec.statement import SQLStatement
14
- from sqlspec.typing import ArrowTable, StatementParameterType
16
+ from sqlspec.typing import ArrowTable, StatementParameterType, is_dict
15
17
 
16
18
  if TYPE_CHECKING:
17
19
  from sqlspec.typing import ArrowTable, ModelDTOT, StatementParameterType, T
@@ -20,39 +22,35 @@ __all__ = ("AdbcConnection", "AdbcDriver")
20
22
 
21
23
  logger = logging.getLogger("sqlspec")
22
24
 
25
+ AdbcConnection = Connection
23
26
 
24
- PARAM_REGEX = re.compile(
25
- r"""(?<![:\w\$]) # Avoid matching ::, \:, etc. and other vendor prefixes
26
- (?:
27
- (?P<dquote>"(?:[^"]|"")*") | # Double-quoted strings
28
- (?P<squote>'(?:[^']|'')*') | # Single-quoted strings
29
- (?P<comment>--.*?\n|\/\*.*?\*\/) | # SQL comments
30
- (?P<lead>[:\$])(?P<var_name>[a-zA-Z_][a-zA-Z0-9_]*) # :name or $name identifier
31
- )
32
- """,
33
- re.VERBOSE | re.DOTALL,
34
- )
27
+ # SQLite named parameter pattern - simple pattern to find parameter references
28
+ SQLITE_PARAM_PATTERN = re.compile(r"(?::|\$|@)([a-zA-Z0-9_]+)")
35
29
 
36
- AdbcConnection = Connection
30
+ # Patterns to identify comments and string literals
31
+ SQL_COMMENT_PATTERN = re.compile(r"--[^\n]*|/\*.*?\*/", re.DOTALL)
32
+ SQL_STRING_PATTERN = re.compile(r"'[^']*'|\"[^\"]*\"")
37
33
 
38
34
 
39
35
  class AdbcDriver(
40
36
  SyncArrowBulkOperationsMixin["AdbcConnection"],
41
37
  SQLTranslatorMixin["AdbcConnection"],
42
38
  SyncDriverAdapterProtocol["AdbcConnection"],
39
+ ResultConverter,
43
40
  ):
44
41
  """ADBC Sync Driver Adapter."""
45
42
 
46
43
  connection: AdbcConnection
47
44
  __supports_arrow__: ClassVar[bool] = True
45
+ dialect: str = "adbc"
48
46
 
49
47
  def __init__(self, connection: "AdbcConnection") -> None:
50
48
  """Initialize the ADBC driver adapter."""
51
49
  self.connection = connection
52
- self.dialect = self._get_dialect(connection)
50
+ self.dialect = self._get_dialect(connection) # Store detected dialect
53
51
 
54
52
  @staticmethod
55
- def _get_dialect(connection: "AdbcConnection") -> str: # noqa: PLR0911
53
+ def _get_dialect(connection: "AdbcConnection") -> str:
56
54
  """Get the database dialect based on the driver name.
57
55
 
58
56
  Args:
@@ -89,91 +87,202 @@ class AdbcDriver(
89
87
  with contextlib.suppress(Exception):
90
88
  cursor.close() # type: ignore[no-untyped-call]
91
89
 
92
- def _process_sql_params(
90
+ def _process_sql_params( # noqa: C901, PLR0912, PLR0915
93
91
  self,
94
92
  sql: str,
95
93
  parameters: "Optional[StatementParameterType]" = None,
96
- /,
94
+ *filters: "StatementFilter",
97
95
  **kwargs: Any,
98
- ) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
99
- # Determine effective parameter type *before* calling SQLStatement
100
- merged_params_type = dict if kwargs else type(parameters)
101
-
102
- # If ADBC + sqlite/duckdb + dictionary params, handle conversion manually
103
- if self.dialect in {"sqlite", "duckdb"} and merged_params_type is dict:
104
- logger.debug(
105
- "ADBC/%s with dict params; bypassing SQLStatement conversion, manually converting to '?' positional.",
106
- self.dialect,
107
- )
108
-
109
- # Combine parameters and kwargs into the actual dictionary to use
110
- parameter_dict = {} # type: ignore[var-annotated]
111
- if isinstance(parameters, dict):
112
- parameter_dict.update(parameters)
113
- if kwargs:
114
- parameter_dict.update(kwargs)
115
-
116
- # Define regex locally to find :name or $name
117
-
118
- processed_sql_parts: list[str] = []
119
- ordered_params = []
120
- last_end = 0
121
- found_params_regex: list[str] = []
122
-
123
- for match in PARAM_REGEX.finditer(sql): # Use original sql
124
- if match.group("dquote") or match.group("squote") or match.group("comment"):
125
- continue
126
-
127
- if match.group("var_name"):
128
- var_name = match.group("var_name")
129
- leading_char = match.group("lead") # : or $
130
- found_params_regex.append(var_name)
131
- # Use match span directly for replacement
132
- start = match.start()
133
- end = match.end()
134
-
135
- if var_name not in parameter_dict:
136
- msg = f"Named parameter '{leading_char}{var_name}' found in SQL but not provided. SQL: {sql}"
137
- raise SQLParsingError(msg)
138
-
139
- processed_sql_parts.extend((sql[last_end:start], "?")) # Force ? style
140
- ordered_params.append(parameter_dict[var_name])
141
- last_end = end
142
-
143
- processed_sql_parts.append(sql[last_end:])
144
-
145
- if not found_params_regex and parameter_dict:
146
- msg = f"ADBC/{self.dialect}: Dict params provided, but no :name or $name placeholders found. SQL: {sql}"
147
- raise ParameterStyleMismatchError(msg)
148
-
149
- # Key validation
150
- provided_keys = set(parameter_dict.keys())
151
- missing_keys = set(found_params_regex) - provided_keys
152
- if missing_keys:
153
- msg = (
154
- f"Named parameters found in SQL ({found_params_regex}) but not provided: {missing_keys}. SQL: {sql}"
96
+ ) -> "tuple[str, Optional[tuple[Any, ...]]]": # Always returns tuple or None for params
97
+ """Process SQL and parameters for ADBC.
98
+
99
+ ADBC drivers generally use positional parameters with '?' placeholders.
100
+ This method processes the SQL statement and transforms parameters into the format
101
+ expected by ADBC drivers.
102
+
103
+ Args:
104
+ sql: The SQL statement to process.
105
+ parameters: The parameters to bind to the statement.
106
+ *filters: Statement filters to apply.
107
+ **kwargs: Additional keyword arguments.
108
+
109
+ Raises:
110
+ SQLParsingError: If the SQL statement cannot be parsed.
111
+
112
+ Returns:
113
+ A tuple of (sql, parameters) ready for execution.
114
+ """
115
+ passed_parameters: Optional[Union[Mapping[str, Any], Sequence[Any]]] = None
116
+ combined_filters_list: list[StatementFilter] = list(filters)
117
+
118
+ if parameters is not None:
119
+ if isinstance(parameters, StatementFilter):
120
+ combined_filters_list.insert(0, parameters)
121
+ # passed_parameters remains None
122
+ else:
123
+ # If parameters is not a StatementFilter, it's actual data parameters.
124
+ passed_parameters = parameters
125
+
126
+ # Special handling for SQLite with non-dict parameters and named placeholders
127
+ if self.dialect == "sqlite" and passed_parameters is not None and not is_dict(passed_parameters):
128
+ # First mask out comments and strings to avoid detecting parameters in those
129
+ comments = list(SQL_COMMENT_PATTERN.finditer(sql))
130
+ strings = list(SQL_STRING_PATTERN.finditer(sql))
131
+
132
+ all_matches = [(m.start(), m.end(), "comment") for m in comments] + [
133
+ (m.start(), m.end(), "string") for m in strings
134
+ ]
135
+ all_matches.sort(reverse=True)
136
+
137
+ for start, end, _ in all_matches:
138
+ sql = sql[:start] + " " * (end - start) + sql[end:]
139
+
140
+ # Find named parameters in clean SQL
141
+ named_params = list(SQLITE_PARAM_PATTERN.finditer(sql))
142
+
143
+ if named_params:
144
+ param_positions = [(m.start(), m.end()) for m in named_params]
145
+ param_positions.sort(reverse=True)
146
+ for start, end in param_positions:
147
+ sql = sql[:start] + "?" + sql[end:]
148
+ if not isinstance(passed_parameters, (list, tuple)):
149
+ passed_parameters = (passed_parameters,)
150
+ passed_parameters = tuple(passed_parameters)
151
+
152
+ # Standard processing for all other cases
153
+ statement = SQLStatement(sql, passed_parameters, kwargs=kwargs, dialect=self.dialect)
154
+
155
+ # Apply any filters from combined_filters_list
156
+ for filter_obj in combined_filters_list:
157
+ statement = statement.apply_filter(filter_obj)
158
+
159
+ processed_sql, processed_params, parsed_expr = statement.process()
160
+
161
+ # Special handling for SQLite dialect with dict parameters
162
+ if self.dialect == "sqlite" and is_dict(processed_params):
163
+ # First, mask out comments and string literals with placeholders
164
+ masked_sql = processed_sql
165
+
166
+ # Replace comments and strings with placeholders
167
+ comments = list(SQL_COMMENT_PATTERN.finditer(masked_sql))
168
+ strings = list(SQL_STRING_PATTERN.finditer(masked_sql))
169
+
170
+ # Sort all matches by their start position (descending)
171
+ all_matches = [(m.start(), m.end(), "comment") for m in comments] + [
172
+ (m.start(), m.end(), "string") for m in strings
173
+ ]
174
+ all_matches.sort(reverse=True)
175
+
176
+ # Replace each match with spaces to preserve positions
177
+ for start, end, _ in all_matches:
178
+ masked_sql = masked_sql[:start] + " " * (end - start) + masked_sql[end:]
179
+
180
+ # Now find parameters in the masked SQL
181
+ param_order = []
182
+ param_spans = [] # Store (start, end) of each parameter
183
+
184
+ for match in SQLITE_PARAM_PATTERN.finditer(masked_sql):
185
+ param_name = match.group(1)
186
+ if param_name in processed_params:
187
+ param_order.append(param_name)
188
+ param_spans.append((match.start(), match.end()))
189
+
190
+ if param_order:
191
+ # Replace parameters with ? placeholders in reverse order to preserve positions
192
+ result_sql = processed_sql
193
+ for i, (start, end) in enumerate(reversed(param_spans)): # noqa: B007
194
+ # Replace :param with ?
195
+ result_sql = result_sql[:start] + "?" + result_sql[start + 1 + len(param_order[-(i + 1)]) :]
196
+
197
+ return result_sql, tuple(processed_params[name] for name in param_order)
198
+
199
+ if processed_params is None:
200
+ return processed_sql, ()
201
+ if (
202
+ isinstance(processed_params, (tuple, list))
203
+ or (processed_params is not None and not isinstance(processed_params, dict))
204
+ ) and parsed_expr is not None:
205
+ # Find all named placeholders
206
+ named_param_nodes = [
207
+ node
208
+ for node in parsed_expr.find_all(sqlglot_exp.Parameter, sqlglot_exp.Placeholder)
209
+ if (isinstance(node, sqlglot_exp.Parameter) and node.name and not node.name.isdigit())
210
+ or (
211
+ isinstance(node, sqlglot_exp.Placeholder)
212
+ and node.this
213
+ and not isinstance(node.this, (sqlglot_exp.Identifier, sqlglot_exp.Literal))
214
+ and not str(node.this).isdigit()
155
215
  )
216
+ ]
217
+
218
+ # If we found named parameters, transform to question marks
219
+ if named_param_nodes:
220
+
221
+ def convert_to_qmark(node: sqlglot_exp.Expression) -> sqlglot_exp.Expression:
222
+ if (isinstance(node, sqlglot_exp.Parameter) and node.name and not node.name.isdigit()) or (
223
+ isinstance(node, sqlglot_exp.Placeholder)
224
+ and node.this
225
+ and not isinstance(node.this, (sqlglot_exp.Identifier, sqlglot_exp.Literal))
226
+ and not str(node.this).isdigit()
227
+ ):
228
+ return sqlglot_exp.Placeholder()
229
+ return node
230
+
231
+ # Transform the SQL
232
+ processed_sql = parsed_expr.transform(convert_to_qmark, copy=True).sql(dialect=self.dialect)
233
+
234
+ # If it's a scalar parameter, ensure it's wrapped in a tuple
235
+ if not isinstance(processed_params, (tuple, list)):
236
+ processed_params = (processed_params,) # type: ignore[unreachable]
237
+
238
+ # 6. Handle dictionary parameters
239
+ if is_dict(processed_params):
240
+ # Skip conversion if there's no parsed expression to work with
241
+ if parsed_expr is None:
242
+ msg = f"ADBC ({self.dialect}): Failed to parse SQL with dictionary parameters. Cannot determine parameter order."
156
243
  raise SQLParsingError(msg)
157
- extra_keys = provided_keys - set(found_params_regex)
158
- if extra_keys:
159
- logger.debug("Extra parameters provided for ADBC/%s: %s", self.dialect, extra_keys)
160
- # Allow extra keys
161
-
162
- final_sql = "".join(processed_sql_parts)
163
- final_params = tuple(ordered_params)
164
- return final_sql, final_params
165
- # For all other cases (other dialects, or non-dict params for sqlite/duckdb),
166
- # use the standard SQLStatement processing.
167
- stmt = SQLStatement(sql=sql, parameters=parameters, dialect=self.dialect, kwargs=kwargs or None)
168
- return stmt.process()
244
+
245
+ # Collect named parameters in the order they appear in the SQL
246
+ named_params = []
247
+ for node in parsed_expr.find_all(sqlglot_exp.Parameter, sqlglot_exp.Placeholder):
248
+ if isinstance(node, sqlglot_exp.Parameter) and node.name and node.name in processed_params:
249
+ named_params.append(node.name) # type: ignore[arg-type]
250
+ elif (
251
+ isinstance(node, sqlglot_exp.Placeholder)
252
+ and isinstance(node.this, str)
253
+ and node.this in processed_params
254
+ ):
255
+ named_params.append(node.this) # type: ignore[arg-type]
256
+
257
+ # If we found named parameters, convert them to ? placeholders
258
+ if named_params:
259
+ # Transform SQL to use ? placeholders
260
+ def convert_to_qmark(node: sqlglot_exp.Expression) -> sqlglot_exp.Expression:
261
+ if isinstance(node, sqlglot_exp.Parameter) and node.name and node.name in processed_params:
262
+ return sqlglot_exp.Placeholder() # Anonymous ? placeholder
263
+ if (
264
+ isinstance(node, sqlglot_exp.Placeholder)
265
+ and isinstance(node.this, str)
266
+ and node.this in processed_params
267
+ ):
268
+ return sqlglot_exp.Placeholder() # Anonymous ? placeholder
269
+ return node
270
+
271
+ return parsed_expr.transform(convert_to_qmark, copy=True).sql(dialect=self.dialect), tuple(
272
+ processed_params[name] # type: ignore[index]
273
+ for name in named_params
274
+ )
275
+ return processed_sql, tuple(processed_params.values())
276
+ if isinstance(processed_params, (list, tuple)):
277
+ return processed_sql, tuple(processed_params)
278
+ return processed_sql, (processed_params,)
169
279
 
170
280
  @overload
171
281
  def select(
172
282
  self,
173
283
  sql: str,
174
284
  parameters: "Optional[StatementParameterType]" = None,
175
- /,
176
- *,
285
+ *filters: "StatementFilter",
177
286
  connection: "Optional[AdbcConnection]" = None,
178
287
  schema_type: None = None,
179
288
  **kwargs: Any,
@@ -183,8 +292,7 @@ class AdbcDriver(
183
292
  self,
184
293
  sql: str,
185
294
  parameters: "Optional[StatementParameterType]" = None,
186
- /,
187
- *,
295
+ *filters: "StatementFilter",
188
296
  connection: "Optional[AdbcConnection]" = None,
189
297
  schema_type: "type[ModelDTOT]",
190
298
  **kwargs: Any,
@@ -193,38 +301,42 @@ class AdbcDriver(
193
301
  self,
194
302
  sql: str,
195
303
  parameters: Optional["StatementParameterType"] = None,
196
- /,
197
- *,
304
+ *filters: "StatementFilter",
198
305
  connection: Optional["AdbcConnection"] = None,
199
306
  schema_type: "Optional[type[ModelDTOT]]" = None,
200
307
  **kwargs: Any,
201
308
  ) -> "Sequence[Union[ModelDTOT, dict[str, Any]]]":
202
309
  """Fetch data from the database.
203
310
 
311
+ Args:
312
+ sql: The SQL query string.
313
+ parameters: The parameters for the query (dict, tuple, list, or None).
314
+ *filters: Statement filters to apply.
315
+ connection: Optional connection override.
316
+ schema_type: Optional schema class for the result.
317
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
318
+
204
319
  Returns:
205
320
  List of row data as either model instances or dictionaries.
206
321
  """
207
322
  connection = self._connection(connection)
208
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
323
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
324
+
209
325
  with self._with_cursor(connection) as cursor:
210
326
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
211
327
  results = cursor.fetchall() # pyright: ignore
212
328
  if not results:
213
329
  return []
330
+ column_names = [column[0] for column in cursor.description or []]
214
331
 
215
- column_names = [col[0] for col in cursor.description or []] # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
216
-
217
- if schema_type is not None:
218
- return [cast("ModelDTOT", schema_type(**dict(zip(column_names, row)))) for row in results] # pyright: ignore[reportUnknownArgumentType,reportUnknownVariableType]
219
- return [dict(zip(column_names, row)) for row in results] # pyright: ignore[reportUnknownArgumentType,reportUnknownVariableType]
332
+ return self.to_schema([dict(zip(column_names, row)) for row in results], schema_type=schema_type)
220
333
 
221
334
  @overload
222
335
  def select_one(
223
336
  self,
224
337
  sql: str,
225
338
  parameters: "Optional[StatementParameterType]" = None,
226
- /,
227
- *,
339
+ *filters: "StatementFilter",
228
340
  connection: "Optional[AdbcConnection]" = None,
229
341
  schema_type: None = None,
230
342
  **kwargs: Any,
@@ -234,8 +346,7 @@ class AdbcDriver(
234
346
  self,
235
347
  sql: str,
236
348
  parameters: "Optional[StatementParameterType]" = None,
237
- /,
238
- *,
349
+ *filters: "StatementFilter",
239
350
  connection: "Optional[AdbcConnection]" = None,
240
351
  schema_type: "type[ModelDTOT]",
241
352
  **kwargs: Any,
@@ -243,36 +354,41 @@ class AdbcDriver(
243
354
  def select_one(
244
355
  self,
245
356
  sql: str,
246
- parameters: Optional["StatementParameterType"] = None,
247
- /,
248
- *,
249
- connection: Optional["AdbcConnection"] = None,
357
+ parameters: "Optional[StatementParameterType]" = None,
358
+ *filters: "StatementFilter",
359
+ connection: "Optional[AdbcConnection]" = None,
250
360
  schema_type: "Optional[type[ModelDTOT]]" = None,
251
361
  **kwargs: Any,
252
362
  ) -> "Union[ModelDTOT, dict[str, Any]]":
253
363
  """Fetch one row from the database.
254
364
 
365
+ Args:
366
+ sql: The SQL query string.
367
+ parameters: The parameters for the query (dict, tuple, list, or None).
368
+ *filters: Statement filters to apply.
369
+ connection: Optional connection override.
370
+ schema_type: Optional schema class for the result.
371
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
372
+
255
373
  Returns:
256
374
  The first row of the query results.
257
375
  """
258
376
  connection = self._connection(connection)
259
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
377
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
378
+
260
379
  with self._with_cursor(connection) as cursor:
261
- cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
262
- result = cursor.fetchone() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
263
- result = self.check_not_found(result) # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType,reportUnknownArgumentType]
264
- column_names = [c[0] for c in cursor.description or []] # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
265
- if schema_type is None:
266
- return dict(zip(column_names, result)) # pyright: ignore[reportUnknownArgumentType, reportUnknownVariableType]
267
- return schema_type(**dict(zip(column_names, result))) # type: ignore[return-value]
380
+ cursor.execute(sql, parameters)
381
+ result = cursor.fetchone()
382
+ result = self.check_not_found(result)
383
+ column_names = [column[0] for column in cursor.description or []]
384
+ return self.to_schema(dict(zip(column_names, result)), schema_type=schema_type)
268
385
 
269
386
  @overload
270
387
  def select_one_or_none(
271
388
  self,
272
389
  sql: str,
273
390
  parameters: "Optional[StatementParameterType]" = None,
274
- /,
275
- *,
391
+ *filters: "StatementFilter",
276
392
  connection: "Optional[AdbcConnection]" = None,
277
393
  schema_type: None = None,
278
394
  **kwargs: Any,
@@ -282,8 +398,7 @@ class AdbcDriver(
282
398
  self,
283
399
  sql: str,
284
400
  parameters: "Optional[StatementParameterType]" = None,
285
- /,
286
- *,
401
+ *filters: "StatementFilter",
287
402
  connection: "Optional[AdbcConnection]" = None,
288
403
  schema_type: "type[ModelDTOT]",
289
404
  **kwargs: Any,
@@ -292,36 +407,41 @@ class AdbcDriver(
292
407
  self,
293
408
  sql: str,
294
409
  parameters: Optional["StatementParameterType"] = None,
295
- /,
296
- *,
410
+ *filters: "StatementFilter",
297
411
  connection: Optional["AdbcConnection"] = None,
298
412
  schema_type: "Optional[type[ModelDTOT]]" = None,
299
413
  **kwargs: Any,
300
414
  ) -> "Optional[Union[ModelDTOT, dict[str, Any]]]":
301
- """Fetch one row from the database.
415
+ """Fetch one row from the database or return None if no rows found.
416
+
417
+ Args:
418
+ sql: The SQL query string.
419
+ parameters: The parameters for the query (dict, tuple, list, or None).
420
+ *filters: Statement filters to apply.
421
+ connection: Optional connection override.
422
+ schema_type: Optional schema class for the result.
423
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
302
424
 
303
425
  Returns:
304
- The first row of the query results.
426
+ The first row of the query results, or None if no results found.
305
427
  """
306
428
  connection = self._connection(connection)
307
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
429
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
430
+
308
431
  with self._with_cursor(connection) as cursor:
309
432
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
310
433
  result = cursor.fetchone() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
311
434
  if result is None:
312
435
  return None
313
- column_names = [c[0] for c in cursor.description or []] # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
314
- if schema_type is None:
315
- return dict(zip(column_names, result)) # pyright: ignore[reportUnknownArgumentType, reportUnknownVariableType]
316
- return schema_type(**dict(zip(column_names, result))) # type: ignore[return-value]
436
+ column_names = [column[0] for column in cursor.description or []]
437
+ return self.to_schema(dict(zip(column_names, result)), schema_type=schema_type)
317
438
 
318
439
  @overload
319
440
  def select_value(
320
441
  self,
321
442
  sql: str,
322
443
  parameters: "Optional[StatementParameterType]" = None,
323
- /,
324
- *,
444
+ *filters: "StatementFilter",
325
445
  connection: "Optional[AdbcConnection]" = None,
326
446
  schema_type: None = None,
327
447
  **kwargs: Any,
@@ -331,8 +451,7 @@ class AdbcDriver(
331
451
  self,
332
452
  sql: str,
333
453
  parameters: "Optional[StatementParameterType]" = None,
334
- /,
335
- *,
454
+ *filters: "StatementFilter",
336
455
  connection: "Optional[AdbcConnection]" = None,
337
456
  schema_type: "type[T]",
338
457
  **kwargs: Any,
@@ -340,20 +459,28 @@ class AdbcDriver(
340
459
  def select_value(
341
460
  self,
342
461
  sql: str,
343
- parameters: Optional["StatementParameterType"] = None,
344
- /,
345
- *,
346
- connection: Optional["AdbcConnection"] = None,
462
+ parameters: "Optional[StatementParameterType]" = None,
463
+ *filters: "StatementFilter",
464
+ connection: "Optional[AdbcConnection]" = None,
347
465
  schema_type: "Optional[type[T]]" = None,
348
466
  **kwargs: Any,
349
467
  ) -> "Union[T, Any]":
350
468
  """Fetch a single value from the database.
351
469
 
470
+ Args:
471
+ sql: The SQL query string.
472
+ parameters: The parameters for the query (dict, tuple, list, or None).
473
+ *filters: Statement filters to apply.
474
+ connection: Optional connection override.
475
+ schema_type: Optional type to convert the result to.
476
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
477
+
352
478
  Returns:
353
- The first value from the first row of results, or None if no results.
479
+ The first value of the first row of the query results.
354
480
  """
355
481
  connection = self._connection(connection)
356
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
482
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
483
+
357
484
  with self._with_cursor(connection) as cursor:
358
485
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
359
486
  result = cursor.fetchone() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
@@ -367,8 +494,7 @@ class AdbcDriver(
367
494
  self,
368
495
  sql: str,
369
496
  parameters: "Optional[StatementParameterType]" = None,
370
- /,
371
- *,
497
+ *filters: "StatementFilter",
372
498
  connection: "Optional[AdbcConnection]" = None,
373
499
  schema_type: None = None,
374
500
  **kwargs: Any,
@@ -378,8 +504,7 @@ class AdbcDriver(
378
504
  self,
379
505
  sql: str,
380
506
  parameters: "Optional[StatementParameterType]" = None,
381
- /,
382
- *,
507
+ *filters: "StatementFilter",
383
508
  connection: "Optional[AdbcConnection]" = None,
384
509
  schema_type: "type[T]",
385
510
  **kwargs: Any,
@@ -387,20 +512,28 @@ class AdbcDriver(
387
512
  def select_value_or_none(
388
513
  self,
389
514
  sql: str,
390
- parameters: Optional["StatementParameterType"] = None,
391
- /,
392
- *,
393
- connection: Optional["AdbcConnection"] = None,
515
+ parameters: "Optional[StatementParameterType]" = None,
516
+ *filters: "StatementFilter",
517
+ connection: "Optional[AdbcConnection]" = None,
394
518
  schema_type: "Optional[type[T]]" = None,
395
519
  **kwargs: Any,
396
520
  ) -> "Optional[Union[T, Any]]":
397
- """Fetch a single value from the database.
521
+ """Fetch a single value or None if not found.
522
+
523
+ Args:
524
+ sql: The SQL query string.
525
+ parameters: The parameters for the query (dict, tuple, list, or None).
526
+ *filters: Statement filters to apply.
527
+ connection: Optional connection override.
528
+ schema_type: Optional type to convert the result to.
529
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
398
530
 
399
531
  Returns:
400
- The first value from the first row of results, or None if no results.
532
+ The first value of the first row of the query results, or None if no results found.
401
533
  """
402
534
  connection = self._connection(connection)
403
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
535
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
536
+
404
537
  with self._with_cursor(connection) as cursor:
405
538
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
406
539
  result = cursor.fetchone() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
@@ -413,19 +546,25 @@ class AdbcDriver(
413
546
  def insert_update_delete(
414
547
  self,
415
548
  sql: str,
416
- parameters: Optional["StatementParameterType"] = None,
417
- /,
418
- *,
419
- connection: Optional["AdbcConnection"] = None,
549
+ parameters: "Optional[StatementParameterType]" = None,
550
+ *filters: "StatementFilter",
551
+ connection: "Optional[AdbcConnection]" = None,
420
552
  **kwargs: Any,
421
553
  ) -> int:
422
- """Insert, update, or delete data from the database.
554
+ """Execute an insert, update, or delete statement.
555
+
556
+ Args:
557
+ sql: The SQL statement string.
558
+ parameters: The parameters for the statement (dict, tuple, list, or None).
559
+ *filters: Statement filters to apply.
560
+ connection: Optional connection override.
561
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
423
562
 
424
563
  Returns:
425
564
  Row count affected by the operation.
426
565
  """
427
566
  connection = self._connection(connection)
428
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
567
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
429
568
 
430
569
  with self._with_cursor(connection) as cursor:
431
570
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
@@ -436,8 +575,7 @@ class AdbcDriver(
436
575
  self,
437
576
  sql: str,
438
577
  parameters: "Optional[StatementParameterType]" = None,
439
- /,
440
- *,
578
+ *filters: "StatementFilter",
441
579
  connection: "Optional[AdbcConnection]" = None,
442
580
  schema_type: None = None,
443
581
  **kwargs: Any,
@@ -447,8 +585,7 @@ class AdbcDriver(
447
585
  self,
448
586
  sql: str,
449
587
  parameters: "Optional[StatementParameterType]" = None,
450
- /,
451
- *,
588
+ *filters: "StatementFilter",
452
589
  connection: "Optional[AdbcConnection]" = None,
453
590
  schema_type: "type[ModelDTOT]",
454
591
  **kwargs: Any,
@@ -456,52 +593,57 @@ class AdbcDriver(
456
593
  def insert_update_delete_returning(
457
594
  self,
458
595
  sql: str,
459
- parameters: Optional["StatementParameterType"] = None,
460
- /,
461
- *,
462
- connection: Optional["AdbcConnection"] = None,
596
+ parameters: "Optional[StatementParameterType]" = None,
597
+ *filters: "StatementFilter",
598
+ connection: "Optional[AdbcConnection]" = None,
463
599
  schema_type: "Optional[type[ModelDTOT]]" = None,
464
600
  **kwargs: Any,
465
601
  ) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
466
- """Insert, update, or delete data from the database and return result.
602
+ """Insert, update, or delete data with RETURNING clause.
603
+
604
+ Args:
605
+ sql: The SQL statement string.
606
+ parameters: The parameters for the statement (dict, tuple, list, or None).
607
+ *filters: Statement filters to apply.
608
+ connection: Optional connection override.
609
+ schema_type: Optional schema class for the result.
610
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
467
611
 
468
612
  Returns:
469
- The first row of results.
613
+ The returned row data, or None if no row returned.
470
614
  """
471
615
  connection = self._connection(connection)
472
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
616
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
617
+
473
618
  with self._with_cursor(connection) as cursor:
474
619
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
475
620
  result = cursor.fetchall() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
476
621
  if not result:
477
622
  return None
478
-
479
- first_row = result[0]
480
-
481
623
  column_names = [c[0] for c in cursor.description or []] # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
482
-
483
- result_dict = dict(zip(column_names, first_row))
484
-
485
- if schema_type is None:
486
- return result_dict
487
- return cast("ModelDTOT", schema_type(**result_dict))
624
+ return self.to_schema(dict(zip(column_names, result[0])), schema_type=schema_type)
488
625
 
489
626
  def execute_script(
490
627
  self,
491
628
  sql: str,
492
- parameters: Optional["StatementParameterType"] = None,
493
- /,
494
- *,
495
- connection: Optional["AdbcConnection"] = None,
629
+ parameters: "Optional[StatementParameterType]" = None,
630
+ connection: "Optional[AdbcConnection]" = None,
496
631
  **kwargs: Any,
497
632
  ) -> str:
498
- """Execute a script.
633
+ """Execute a SQL script.
634
+
635
+ Args:
636
+ sql: The SQL script to execute.
637
+ parameters: The parameters for the script (dict, tuple, list, or None).
638
+ *filters: Statement filters to apply.
639
+ connection: Optional connection override.
640
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
499
641
 
500
642
  Returns:
501
- Status message for the operation.
643
+ A success message.
502
644
  """
503
645
  connection = self._connection(connection)
504
- sql, parameters = self._process_sql_params(sql, parameters)
646
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
505
647
 
506
648
  with self._with_cursor(connection) as cursor:
507
649
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
@@ -509,23 +651,29 @@ class AdbcDriver(
509
651
 
510
652
  # --- Arrow Bulk Operations ---
511
653
 
512
- def select_arrow( # pyright: ignore[reportUnknownParameterType]
654
+ def select_arrow(
513
655
  self,
514
656
  sql: str,
515
657
  parameters: "Optional[StatementParameterType]" = None,
516
- /,
517
- *,
658
+ *filters: "StatementFilter",
518
659
  connection: "Optional[AdbcConnection]" = None,
519
660
  **kwargs: Any,
520
- ) -> "ArrowTable":
661
+ ) -> "ArrowTable": # pyright: ignore[reportUnknownVariableType]
521
662
  """Execute a SQL query and return results as an Apache Arrow Table.
522
663
 
664
+ Args:
665
+ sql: The SQL query string.
666
+ parameters: The parameters for the query (dict, tuple, list, or None).
667
+ *filters: Statement filters to apply.
668
+ connection: Optional connection override.
669
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
670
+
523
671
  Returns:
524
- The results of the query as an Apache Arrow Table.
672
+ An Arrow Table containing the query results.
525
673
  """
526
- conn = self._connection(connection)
527
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
674
+ connection = self._connection(connection)
675
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
528
676
 
529
- with self._with_cursor(conn) as cursor:
677
+ with self._with_cursor(connection) as cursor:
530
678
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
531
679
  return cast("ArrowTable", cursor.fetch_arrow_table()) # pyright: ignore[reportUnknownMemberType]