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.

@@ -6,12 +6,14 @@ 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
12
  from sqlspec.exceptions import ParameterStyleMismatchError, SQLParsingError
12
- from sqlspec.mixins import SQLTranslatorMixin, SyncArrowBulkOperationsMixin
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,83 +87,197 @@ 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
  /,
95
+ *filters: "StatementFilter",
97
96
  **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)
97
+ ) -> "tuple[str, Optional[tuple[Any, ...]]]": # Always returns tuple or None for params
98
+ """Process SQL and parameters for ADBC.
99
+
100
+ ADBC drivers generally use positional parameters with '?' placeholders.
101
+ This method processes the SQL statement and transforms parameters into the format
102
+ expected by ADBC drivers.
103
+
104
+ Args:
105
+ sql: The SQL statement to process.
106
+ parameters: The parameters to bind to the statement.
107
+ *filters: Statement filters to apply.
108
+ **kwargs: Additional keyword arguments.
148
109
 
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}"
110
+ Raises:
111
+ ParameterStyleMismatchError: If positional parameters are mixed with keyword arguments.
112
+ SQLParsingError: If the SQL statement cannot be parsed.
113
+
114
+ Returns:
115
+ A tuple of (sql, parameters) ready for execution.
116
+ """
117
+ # Special handling for SQLite with non-dict parameters and named placeholders
118
+ if self.dialect == "sqlite" and parameters is not None and not is_dict(parameters):
119
+ # First mask out comments and strings to avoid detecting parameters in those
120
+ comments = list(SQL_COMMENT_PATTERN.finditer(sql))
121
+ strings = list(SQL_STRING_PATTERN.finditer(sql))
122
+
123
+ all_matches = [(m.start(), m.end(), "comment") for m in comments] + [
124
+ (m.start(), m.end(), "string") for m in strings
125
+ ]
126
+ all_matches.sort(reverse=True)
127
+
128
+ for start, end, _ in all_matches:
129
+ sql = sql[:start] + " " * (end - start) + sql[end:]
130
+
131
+ # Find named parameters in clean SQL
132
+ named_params = list(SQLITE_PARAM_PATTERN.finditer(sql))
133
+
134
+ if named_params:
135
+ param_positions = [(m.start(), m.end()) for m in named_params]
136
+ param_positions.sort(reverse=True)
137
+ for start, end in param_positions:
138
+ sql = sql[:start] + "?" + sql[end:]
139
+ if not isinstance(parameters, (list, tuple)):
140
+ return sql, (parameters,)
141
+ return sql, tuple(parameters)
142
+
143
+ # Standard processing for all other cases
144
+ merged_params = parameters
145
+ if kwargs:
146
+ if is_dict(parameters):
147
+ merged_params = {**parameters, **kwargs}
148
+ elif parameters is not None:
149
+ msg = "Cannot mix positional parameters with keyword arguments for adbc driver."
150
+ raise ParameterStyleMismatchError(msg)
151
+ else:
152
+ merged_params = kwargs
153
+
154
+ # 2. Create SQLStatement with dialect and process
155
+ statement = SQLStatement(sql, merged_params, dialect=self.dialect)
156
+
157
+ # Apply any filters
158
+ for filter_obj in filters:
159
+ statement = statement.apply_filter(filter_obj)
160
+
161
+ processed_sql, processed_params, parsed_expr = statement.process()
162
+
163
+ # Special handling for SQLite dialect with dict parameters
164
+ if self.dialect == "sqlite" and is_dict(processed_params):
165
+ # First, mask out comments and string literals with placeholders
166
+ masked_sql = processed_sql
167
+
168
+ # Replace comments and strings with placeholders
169
+ comments = list(SQL_COMMENT_PATTERN.finditer(masked_sql))
170
+ strings = list(SQL_STRING_PATTERN.finditer(masked_sql))
171
+
172
+ # Sort all matches by their start position (descending)
173
+ all_matches = [(m.start(), m.end(), "comment") for m in comments] + [
174
+ (m.start(), m.end(), "string") for m in strings
175
+ ]
176
+ all_matches.sort(reverse=True)
177
+
178
+ # Replace each match with spaces to preserve positions
179
+ for start, end, _ in all_matches:
180
+ masked_sql = masked_sql[:start] + " " * (end - start) + masked_sql[end:]
181
+
182
+ # Now find parameters in the masked SQL
183
+ param_order = []
184
+ param_spans = [] # Store (start, end) of each parameter
185
+
186
+ for match in SQLITE_PARAM_PATTERN.finditer(masked_sql):
187
+ param_name = match.group(1)
188
+ if param_name in processed_params:
189
+ param_order.append(param_name)
190
+ param_spans.append((match.start(), match.end()))
191
+
192
+ if param_order:
193
+ # Replace parameters with ? placeholders in reverse order to preserve positions
194
+ result_sql = processed_sql
195
+ for i, (start, end) in enumerate(reversed(param_spans)): # noqa: B007
196
+ # Replace :param with ?
197
+ result_sql = result_sql[:start] + "?" + result_sql[start + 1 + len(param_order[-(i + 1)]) :]
198
+
199
+ return result_sql, tuple(processed_params[name] for name in param_order)
200
+
201
+ if processed_params is None:
202
+ return processed_sql, ()
203
+ if (
204
+ isinstance(processed_params, (tuple, list))
205
+ or (processed_params is not None and not isinstance(processed_params, dict))
206
+ ) and parsed_expr is not None:
207
+ # Find all named placeholders
208
+ named_param_nodes = [
209
+ node
210
+ for node in parsed_expr.find_all(sqlglot_exp.Parameter, sqlglot_exp.Placeholder)
211
+ if (isinstance(node, sqlglot_exp.Parameter) and node.name and not node.name.isdigit())
212
+ or (
213
+ isinstance(node, sqlglot_exp.Placeholder)
214
+ and node.this
215
+ and not isinstance(node.this, (sqlglot_exp.Identifier, sqlglot_exp.Literal))
216
+ and not str(node.this).isdigit()
155
217
  )
218
+ ]
219
+
220
+ # If we found named parameters, transform to question marks
221
+ if named_param_nodes:
222
+
223
+ def convert_to_qmark(node: sqlglot_exp.Expression) -> sqlglot_exp.Expression:
224
+ if (isinstance(node, sqlglot_exp.Parameter) and node.name and not node.name.isdigit()) or (
225
+ isinstance(node, sqlglot_exp.Placeholder)
226
+ and node.this
227
+ and not isinstance(node.this, (sqlglot_exp.Identifier, sqlglot_exp.Literal))
228
+ and not str(node.this).isdigit()
229
+ ):
230
+ return sqlglot_exp.Placeholder()
231
+ return node
232
+
233
+ # Transform the SQL
234
+ processed_sql = parsed_expr.transform(convert_to_qmark, copy=True).sql(dialect=self.dialect)
235
+
236
+ # If it's a scalar parameter, ensure it's wrapped in a tuple
237
+ if not isinstance(processed_params, (tuple, list)):
238
+ processed_params = (processed_params,) # type: ignore[unreachable]
239
+
240
+ # 6. Handle dictionary parameters
241
+ if is_dict(processed_params):
242
+ # Skip conversion if there's no parsed expression to work with
243
+ if parsed_expr is None:
244
+ msg = f"ADBC ({self.dialect}): Failed to parse SQL with dictionary parameters. Cannot determine parameter order."
156
245
  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()
246
+
247
+ # Collect named parameters in the order they appear in the SQL
248
+ named_params = []
249
+ for node in parsed_expr.find_all(sqlglot_exp.Parameter, sqlglot_exp.Placeholder):
250
+ if isinstance(node, sqlglot_exp.Parameter) and node.name and node.name in processed_params:
251
+ named_params.append(node.name) # type: ignore[arg-type]
252
+ elif (
253
+ isinstance(node, sqlglot_exp.Placeholder)
254
+ and isinstance(node.this, str)
255
+ and node.this in processed_params
256
+ ):
257
+ named_params.append(node.this) # type: ignore[arg-type]
258
+
259
+ # If we found named parameters, convert them to ? placeholders
260
+ if named_params:
261
+ # Transform SQL to use ? placeholders
262
+ def convert_to_qmark(node: sqlglot_exp.Expression) -> sqlglot_exp.Expression:
263
+ if isinstance(node, sqlglot_exp.Parameter) and node.name and node.name in processed_params:
264
+ return sqlglot_exp.Placeholder() # Anonymous ? placeholder
265
+ if (
266
+ isinstance(node, sqlglot_exp.Placeholder)
267
+ and isinstance(node.this, str)
268
+ and node.this in processed_params
269
+ ):
270
+ return sqlglot_exp.Placeholder() # Anonymous ? placeholder
271
+ return node
272
+
273
+ return parsed_expr.transform(convert_to_qmark, copy=True).sql(dialect=self.dialect), tuple(
274
+ processed_params[name] # type: ignore[index]
275
+ for name in named_params
276
+ )
277
+ return processed_sql, tuple(processed_params.values())
278
+ if isinstance(processed_params, (list, tuple)):
279
+ return processed_sql, tuple(processed_params)
280
+ return processed_sql, (processed_params,)
169
281
 
170
282
  @overload
171
283
  def select(
@@ -173,7 +285,7 @@ class AdbcDriver(
173
285
  sql: str,
174
286
  parameters: "Optional[StatementParameterType]" = None,
175
287
  /,
176
- *,
288
+ *filters: "StatementFilter",
177
289
  connection: "Optional[AdbcConnection]" = None,
178
290
  schema_type: None = None,
179
291
  **kwargs: Any,
@@ -184,7 +296,7 @@ class AdbcDriver(
184
296
  sql: str,
185
297
  parameters: "Optional[StatementParameterType]" = None,
186
298
  /,
187
- *,
299
+ *filters: "StatementFilter",
188
300
  connection: "Optional[AdbcConnection]" = None,
189
301
  schema_type: "type[ModelDTOT]",
190
302
  **kwargs: Any,
@@ -194,29 +306,35 @@ class AdbcDriver(
194
306
  sql: str,
195
307
  parameters: Optional["StatementParameterType"] = None,
196
308
  /,
197
- *,
309
+ *filters: "StatementFilter",
198
310
  connection: Optional["AdbcConnection"] = None,
199
311
  schema_type: "Optional[type[ModelDTOT]]" = None,
200
312
  **kwargs: Any,
201
313
  ) -> "Sequence[Union[ModelDTOT, dict[str, Any]]]":
202
314
  """Fetch data from the database.
203
315
 
316
+ Args:
317
+ sql: The SQL query string.
318
+ parameters: The parameters for the query (dict, tuple, list, or None).
319
+ *filters: Statement filters to apply.
320
+ connection: Optional connection override.
321
+ schema_type: Optional schema class for the result.
322
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
323
+
204
324
  Returns:
205
325
  List of row data as either model instances or dictionaries.
206
326
  """
207
327
  connection = self._connection(connection)
208
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
328
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
329
+
209
330
  with self._with_cursor(connection) as cursor:
210
331
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
211
332
  results = cursor.fetchall() # pyright: ignore
212
333
  if not results:
213
334
  return []
335
+ column_names = [column[0] for column in cursor.description or []]
214
336
 
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]
337
+ return self.to_schema([dict(zip(column_names, row)) for row in results], schema_type=schema_type)
220
338
 
221
339
  @overload
222
340
  def select_one(
@@ -224,7 +342,7 @@ class AdbcDriver(
224
342
  sql: str,
225
343
  parameters: "Optional[StatementParameterType]" = None,
226
344
  /,
227
- *,
345
+ *filters: "StatementFilter",
228
346
  connection: "Optional[AdbcConnection]" = None,
229
347
  schema_type: None = None,
230
348
  **kwargs: Any,
@@ -235,7 +353,7 @@ class AdbcDriver(
235
353
  sql: str,
236
354
  parameters: "Optional[StatementParameterType]" = None,
237
355
  /,
238
- *,
356
+ *filters: "StatementFilter",
239
357
  connection: "Optional[AdbcConnection]" = None,
240
358
  schema_type: "type[ModelDTOT]",
241
359
  **kwargs: Any,
@@ -243,28 +361,35 @@ class AdbcDriver(
243
361
  def select_one(
244
362
  self,
245
363
  sql: str,
246
- parameters: Optional["StatementParameterType"] = None,
364
+ parameters: "Optional[StatementParameterType]" = None,
247
365
  /,
248
- *,
249
- connection: Optional["AdbcConnection"] = None,
366
+ *filters: "StatementFilter",
367
+ connection: "Optional[AdbcConnection]" = None,
250
368
  schema_type: "Optional[type[ModelDTOT]]" = None,
251
369
  **kwargs: Any,
252
370
  ) -> "Union[ModelDTOT, dict[str, Any]]":
253
371
  """Fetch one row from the database.
254
372
 
373
+ Args:
374
+ sql: The SQL query string.
375
+ parameters: The parameters for the query (dict, tuple, list, or None).
376
+ *filters: Statement filters to apply.
377
+ connection: Optional connection override.
378
+ schema_type: Optional schema class for the result.
379
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
380
+
255
381
  Returns:
256
382
  The first row of the query results.
257
383
  """
258
384
  connection = self._connection(connection)
259
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
385
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
386
+
260
387
  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]
388
+ cursor.execute(sql, parameters)
389
+ result = cursor.fetchone()
390
+ result = self.check_not_found(result)
391
+ column_names = [column[0] for column in cursor.description or []]
392
+ return self.to_schema(dict(zip(column_names, result)), schema_type=schema_type)
268
393
 
269
394
  @overload
270
395
  def select_one_or_none(
@@ -272,7 +397,7 @@ class AdbcDriver(
272
397
  sql: str,
273
398
  parameters: "Optional[StatementParameterType]" = None,
274
399
  /,
275
- *,
400
+ *filters: "StatementFilter",
276
401
  connection: "Optional[AdbcConnection]" = None,
277
402
  schema_type: None = None,
278
403
  **kwargs: Any,
@@ -283,7 +408,7 @@ class AdbcDriver(
283
408
  sql: str,
284
409
  parameters: "Optional[StatementParameterType]" = None,
285
410
  /,
286
- *,
411
+ *filters: "StatementFilter",
287
412
  connection: "Optional[AdbcConnection]" = None,
288
413
  schema_type: "type[ModelDTOT]",
289
414
  **kwargs: Any,
@@ -293,27 +418,34 @@ class AdbcDriver(
293
418
  sql: str,
294
419
  parameters: Optional["StatementParameterType"] = None,
295
420
  /,
296
- *,
421
+ *filters: "StatementFilter",
297
422
  connection: Optional["AdbcConnection"] = None,
298
423
  schema_type: "Optional[type[ModelDTOT]]" = None,
299
424
  **kwargs: Any,
300
425
  ) -> "Optional[Union[ModelDTOT, dict[str, Any]]]":
301
- """Fetch one row from the database.
426
+ """Fetch one row from the database or return None if no rows found.
427
+
428
+ Args:
429
+ sql: The SQL query string.
430
+ parameters: The parameters for the query (dict, tuple, list, or None).
431
+ *filters: Statement filters to apply.
432
+ connection: Optional connection override.
433
+ schema_type: Optional schema class for the result.
434
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
302
435
 
303
436
  Returns:
304
- The first row of the query results.
437
+ The first row of the query results, or None if no results found.
305
438
  """
306
439
  connection = self._connection(connection)
307
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
440
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
441
+
308
442
  with self._with_cursor(connection) as cursor:
309
443
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
310
444
  result = cursor.fetchone() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
311
445
  if result is None:
312
446
  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]
447
+ column_names = [column[0] for column in cursor.description or []]
448
+ return self.to_schema(dict(zip(column_names, result)), schema_type=schema_type)
317
449
 
318
450
  @overload
319
451
  def select_value(
@@ -321,7 +453,7 @@ class AdbcDriver(
321
453
  sql: str,
322
454
  parameters: "Optional[StatementParameterType]" = None,
323
455
  /,
324
- *,
456
+ *filters: StatementFilter,
325
457
  connection: "Optional[AdbcConnection]" = None,
326
458
  schema_type: None = None,
327
459
  **kwargs: Any,
@@ -332,7 +464,7 @@ class AdbcDriver(
332
464
  sql: str,
333
465
  parameters: "Optional[StatementParameterType]" = None,
334
466
  /,
335
- *,
467
+ *filters: StatementFilter,
336
468
  connection: "Optional[AdbcConnection]" = None,
337
469
  schema_type: "type[T]",
338
470
  **kwargs: Any,
@@ -340,20 +472,29 @@ class AdbcDriver(
340
472
  def select_value(
341
473
  self,
342
474
  sql: str,
343
- parameters: Optional["StatementParameterType"] = None,
475
+ parameters: "Optional[StatementParameterType]" = None,
344
476
  /,
345
- *,
346
- connection: Optional["AdbcConnection"] = None,
477
+ *filters: StatementFilter,
478
+ connection: "Optional[AdbcConnection]" = None,
347
479
  schema_type: "Optional[type[T]]" = None,
348
480
  **kwargs: Any,
349
481
  ) -> "Union[T, Any]":
350
482
  """Fetch a single value from the database.
351
483
 
484
+ Args:
485
+ sql: The SQL query string.
486
+ parameters: The parameters for the query (dict, tuple, list, or None).
487
+ *filters: Statement filters to apply.
488
+ connection: Optional connection override.
489
+ schema_type: Optional type to convert the result to.
490
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
491
+
352
492
  Returns:
353
- The first value from the first row of results, or None if no results.
493
+ The first value of the first row of the query results.
354
494
  """
355
495
  connection = self._connection(connection)
356
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
496
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
497
+
357
498
  with self._with_cursor(connection) as cursor:
358
499
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
359
500
  result = cursor.fetchone() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
@@ -368,7 +509,7 @@ class AdbcDriver(
368
509
  sql: str,
369
510
  parameters: "Optional[StatementParameterType]" = None,
370
511
  /,
371
- *,
512
+ *filters: StatementFilter,
372
513
  connection: "Optional[AdbcConnection]" = None,
373
514
  schema_type: None = None,
374
515
  **kwargs: Any,
@@ -379,7 +520,7 @@ class AdbcDriver(
379
520
  sql: str,
380
521
  parameters: "Optional[StatementParameterType]" = None,
381
522
  /,
382
- *,
523
+ *filters: StatementFilter,
383
524
  connection: "Optional[AdbcConnection]" = None,
384
525
  schema_type: "type[T]",
385
526
  **kwargs: Any,
@@ -387,20 +528,29 @@ class AdbcDriver(
387
528
  def select_value_or_none(
388
529
  self,
389
530
  sql: str,
390
- parameters: Optional["StatementParameterType"] = None,
531
+ parameters: "Optional[StatementParameterType]" = None,
391
532
  /,
392
- *,
393
- connection: Optional["AdbcConnection"] = None,
533
+ *filters: StatementFilter,
534
+ connection: "Optional[AdbcConnection]" = None,
394
535
  schema_type: "Optional[type[T]]" = None,
395
536
  **kwargs: Any,
396
537
  ) -> "Optional[Union[T, Any]]":
397
- """Fetch a single value from the database.
538
+ """Fetch a single value or None if not found.
539
+
540
+ Args:
541
+ sql: The SQL query string.
542
+ parameters: The parameters for the query (dict, tuple, list, or None).
543
+ *filters: Statement filters to apply.
544
+ connection: Optional connection override.
545
+ schema_type: Optional type to convert the result to.
546
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
398
547
 
399
548
  Returns:
400
- The first value from the first row of results, or None if no results.
549
+ The first value of the first row of the query results, or None if no results found.
401
550
  """
402
551
  connection = self._connection(connection)
403
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
552
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
553
+
404
554
  with self._with_cursor(connection) as cursor:
405
555
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
406
556
  result = cursor.fetchone() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
@@ -413,19 +563,26 @@ class AdbcDriver(
413
563
  def insert_update_delete(
414
564
  self,
415
565
  sql: str,
416
- parameters: Optional["StatementParameterType"] = None,
566
+ parameters: "Optional[StatementParameterType]" = None,
417
567
  /,
418
- *,
419
- connection: Optional["AdbcConnection"] = None,
568
+ *filters: "StatementFilter",
569
+ connection: "Optional[AdbcConnection]" = None,
420
570
  **kwargs: Any,
421
571
  ) -> int:
422
- """Insert, update, or delete data from the database.
572
+ """Execute an insert, update, or delete statement.
573
+
574
+ Args:
575
+ sql: The SQL statement to execute.
576
+ parameters: The parameters for the statement (dict, tuple, list, or None).
577
+ *filters: Statement filters to apply.
578
+ connection: Optional connection override.
579
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
423
580
 
424
581
  Returns:
425
- Row count affected by the operation.
582
+ The number of rows affected by the statement.
426
583
  """
427
584
  connection = self._connection(connection)
428
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
585
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
429
586
 
430
587
  with self._with_cursor(connection) as cursor:
431
588
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
@@ -437,7 +594,7 @@ class AdbcDriver(
437
594
  sql: str,
438
595
  parameters: "Optional[StatementParameterType]" = None,
439
596
  /,
440
- *,
597
+ *filters: StatementFilter,
441
598
  connection: "Optional[AdbcConnection]" = None,
442
599
  schema_type: None = None,
443
600
  **kwargs: Any,
@@ -448,7 +605,7 @@ class AdbcDriver(
448
605
  sql: str,
449
606
  parameters: "Optional[StatementParameterType]" = None,
450
607
  /,
451
- *,
608
+ *filters: StatementFilter,
452
609
  connection: "Optional[AdbcConnection]" = None,
453
610
  schema_type: "type[ModelDTOT]",
454
611
  **kwargs: Any,
@@ -456,52 +613,59 @@ class AdbcDriver(
456
613
  def insert_update_delete_returning(
457
614
  self,
458
615
  sql: str,
459
- parameters: Optional["StatementParameterType"] = None,
616
+ parameters: "Optional[StatementParameterType]" = None,
460
617
  /,
461
- *,
462
- connection: Optional["AdbcConnection"] = None,
618
+ *filters: StatementFilter,
619
+ connection: "Optional[AdbcConnection]" = None,
463
620
  schema_type: "Optional[type[ModelDTOT]]" = None,
464
621
  **kwargs: Any,
465
622
  ) -> "Optional[Union[dict[str, Any], ModelDTOT]]":
466
623
  """Insert, update, or delete data from the database and return result.
467
624
 
625
+ Args:
626
+ sql: The SQL statement to execute.
627
+ parameters: The parameters for the statement (dict, tuple, list, or None).
628
+ *filters: Statement filters to apply.
629
+ connection: Optional connection override.
630
+ schema_type: Optional schema class for the result.
631
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
632
+
468
633
  Returns:
469
634
  The first row of results.
470
635
  """
471
636
  connection = self._connection(connection)
472
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
637
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
638
+
473
639
  with self._with_cursor(connection) as cursor:
474
640
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
475
641
  result = cursor.fetchall() # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
476
642
  if not result:
477
643
  return None
478
-
479
- first_row = result[0]
480
-
481
644
  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))
645
+ return self.to_schema(dict(zip(column_names, result[0])), schema_type=schema_type)
488
646
 
489
647
  def execute_script(
490
648
  self,
491
649
  sql: str,
492
- parameters: Optional["StatementParameterType"] = None,
650
+ parameters: "Optional[StatementParameterType]" = None,
493
651
  /,
494
- *,
495
- connection: Optional["AdbcConnection"] = None,
652
+ connection: "Optional[AdbcConnection]" = None,
496
653
  **kwargs: Any,
497
654
  ) -> str:
498
- """Execute a script.
655
+ """Execute a SQL script.
656
+
657
+ Args:
658
+ sql: The SQL script to execute.
659
+ parameters: The parameters for the script (dict, tuple, list, or None).
660
+ *filters: Statement filters to apply.
661
+ connection: Optional connection override.
662
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
499
663
 
500
664
  Returns:
501
- Status message for the operation.
665
+ A success message.
502
666
  """
503
667
  connection = self._connection(connection)
504
- sql, parameters = self._process_sql_params(sql, parameters)
668
+ sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
505
669
 
506
670
  with self._with_cursor(connection) as cursor:
507
671
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
@@ -514,18 +678,25 @@ class AdbcDriver(
514
678
  sql: str,
515
679
  parameters: "Optional[StatementParameterType]" = None,
516
680
  /,
517
- *,
681
+ *filters: StatementFilter,
518
682
  connection: "Optional[AdbcConnection]" = None,
519
683
  **kwargs: Any,
520
- ) -> "ArrowTable":
684
+ ) -> "ArrowTable": # pyright: ignore[reportUnknownVariableType]
521
685
  """Execute a SQL query and return results as an Apache Arrow Table.
522
686
 
687
+ Args:
688
+ sql: The SQL query string.
689
+ parameters: The parameters for the query (dict, tuple, list, or None).
690
+ *filters: Statement filters to apply.
691
+ connection: Optional connection override.
692
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
693
+
523
694
  Returns:
524
- The results of the query as an Apache Arrow Table.
695
+ An Apache Arrow Table containing the query results.
525
696
  """
526
- conn = self._connection(connection)
527
- sql, parameters = self._process_sql_params(sql, parameters, **kwargs)
697
+ connection = self._connection(connection)
698
+ sql, parameters = self._process_sql_params(sql, parameters, *filters, **kwargs)
528
699
 
529
- with self._with_cursor(conn) as cursor:
700
+ with self._with_cursor(connection) as cursor:
530
701
  cursor.execute(sql, parameters) # pyright: ignore[reportUnknownMemberType]
531
702
  return cast("ArrowTable", cursor.fetch_arrow_table()) # pyright: ignore[reportUnknownMemberType]