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.

@@ -1,5 +1,6 @@
1
1
  import contextlib
2
2
  import datetime
3
+ import logging
3
4
  from collections.abc import Iterator, Sequence
4
5
  from decimal import Decimal
5
6
  from typing import (
@@ -12,19 +13,21 @@ from typing import (
12
13
  overload,
13
14
  )
14
15
 
15
- import sqlglot
16
16
  from google.cloud import bigquery
17
17
  from google.cloud.bigquery import Client
18
18
  from google.cloud.bigquery.job import QueryJob, QueryJobConfig
19
19
  from google.cloud.exceptions import NotFound
20
20
 
21
21
  from sqlspec.base import SyncDriverAdapterProtocol
22
- from sqlspec.exceptions import NotFoundError, SQLSpecError
22
+ from sqlspec.exceptions import NotFoundError, ParameterStyleMismatchError, SQLSpecError
23
+ from sqlspec.filters import StatementFilter
23
24
  from sqlspec.mixins import (
25
+ ResultConverter,
24
26
  SQLTranslatorMixin,
25
27
  SyncArrowBulkOperationsMixin,
26
28
  SyncParquetExportMixin,
27
29
  )
30
+ from sqlspec.statement import SQLStatement
28
31
  from sqlspec.typing import ArrowTable, ModelDTOT, StatementParameterType, T
29
32
 
30
33
  if TYPE_CHECKING:
@@ -35,12 +38,15 @@ __all__ = ("BigQueryConnection", "BigQueryDriver")
35
38
 
36
39
  BigQueryConnection = Client
37
40
 
41
+ logger = logging.getLogger("sqlspec")
42
+
38
43
 
39
44
  class BigQueryDriver(
40
45
  SyncDriverAdapterProtocol["BigQueryConnection"],
41
46
  SyncArrowBulkOperationsMixin["BigQueryConnection"],
42
47
  SyncParquetExportMixin["BigQueryConnection"],
43
48
  SQLTranslatorMixin["BigQueryConnection"],
49
+ ResultConverter,
44
50
  ):
45
51
  """Synchronous BigQuery Driver Adapter."""
46
52
 
@@ -55,7 +61,7 @@ class BigQueryDriver(
55
61
  )
56
62
 
57
63
  @staticmethod
58
- def _get_bq_param_type(value: Any) -> "tuple[Optional[str], Optional[str]]": # noqa: PLR0911, PLR0912
64
+ def _get_bq_param_type(value: Any) -> "tuple[Optional[str], Optional[str]]":
59
65
  if isinstance(value, bool):
60
66
  return "BOOL", None
61
67
  if isinstance(value, int):
@@ -105,10 +111,56 @@ class BigQueryDriver(
105
111
 
106
112
  return None, None # Unsupported type
107
113
 
108
- def _run_query_job( # noqa: C901, PLR0912, PLR0915 (User change)
114
+ def _process_sql_params(
109
115
  self,
110
116
  sql: str,
111
117
  parameters: "Optional[StatementParameterType]" = None,
118
+ /,
119
+ *filters: StatementFilter,
120
+ **kwargs: Any,
121
+ ) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
122
+ """Process SQL and parameters using SQLStatement with dialect support.
123
+
124
+ Args:
125
+ sql: The SQL statement to process.
126
+ parameters: The parameters to bind to the statement.
127
+ *filters: Statement filters to apply.
128
+ **kwargs: Additional keyword arguments.
129
+
130
+ Raises:
131
+ ParameterStyleMismatchError: If pre-formatted BigQuery parameters are mixed with keyword arguments.
132
+
133
+ Returns:
134
+ A tuple of (sql, parameters) ready for execution.
135
+ """
136
+ # Special case: check for pre-formatted BQ parameters
137
+ if (
138
+ isinstance(parameters, (list, tuple))
139
+ and parameters
140
+ and all(isinstance(p, (bigquery.ScalarQueryParameter, bigquery.ArrayQueryParameter)) for p in parameters)
141
+ ):
142
+ if kwargs:
143
+ msg = "Cannot mix pre-formatted BigQuery parameters with keyword arguments."
144
+ raise ParameterStyleMismatchError(msg)
145
+ return sql, parameters
146
+
147
+ statement = SQLStatement(sql, parameters, kwargs=kwargs, dialect=self.dialect)
148
+
149
+ # Apply any filters
150
+ for filter_obj in filters:
151
+ statement = statement.apply_filter(filter_obj)
152
+
153
+ # Process the statement for execution
154
+ processed_sql, processed_params, _ = statement.process()
155
+
156
+ return processed_sql, processed_params
157
+
158
+ def _run_query_job(
159
+ self,
160
+ sql: str,
161
+ parameters: "Optional[StatementParameterType]" = None,
162
+ /,
163
+ *filters: StatementFilter,
112
164
  connection: "Optional[BigQueryConnection]" = None,
113
165
  job_config: "Optional[QueryJobConfig]" = None,
114
166
  is_script: bool = False,
@@ -125,108 +177,62 @@ class BigQueryDriver(
125
177
  else:
126
178
  final_job_config = QueryJobConfig() # Create a fresh config
127
179
 
128
- # --- Parameter Handling Logic --- Start
129
- params: Union[dict[str, Any], list[Any], None] = None
130
- param_style: Optional[str] = None # 'named' (@), 'qmark' (?)
131
- use_preformatted_params = False
132
- final_sql = sql # Default to original SQL
180
+ # Process SQL and parameters
181
+ final_sql, processed_params = self._process_sql_params(sql, parameters, *filters, **kwargs)
133
182
 
134
- # Check for pre-formatted BQ parameters first
183
+ # Handle pre-formatted parameters
135
184
  if (
136
- isinstance(parameters, (list, tuple))
137
- and parameters
138
- and all(isinstance(p, (bigquery.ScalarQueryParameter, bigquery.ArrayQueryParameter)) for p in parameters)
185
+ isinstance(processed_params, (list, tuple))
186
+ and processed_params
187
+ and all(
188
+ isinstance(p, (bigquery.ScalarQueryParameter, bigquery.ArrayQueryParameter)) for p in processed_params
189
+ )
139
190
  ):
140
- if kwargs:
141
- msg = "Cannot mix pre-formatted BigQuery parameters with keyword arguments."
142
- raise SQLSpecError(msg)
143
- use_preformatted_params = True
144
- final_job_config.query_parameters = list(parameters)
145
- # Keep final_sql = sql, as it should match the pre-formatted named params
146
-
147
- # Determine parameter style and merge standard parameters ONLY if not preformatted
148
- if not use_preformatted_params:
149
- if isinstance(parameters, dict):
150
- params = {**parameters, **kwargs}
151
- param_style = "named"
152
- elif isinstance(parameters, (list, tuple)):
153
- if kwargs:
154
- msg = "Cannot mix positional parameters with keyword arguments."
155
- raise SQLSpecError(msg)
156
- # Check if it's primitives for qmark style
157
- if all(
158
- not isinstance(p, (bigquery.ScalarQueryParameter, bigquery.ArrayQueryParameter)) for p in parameters
159
- ):
160
- params = list(parameters)
161
- param_style = "qmark"
162
- else:
163
- # Mixed list or non-BQ parameter objects
164
- msg = "Invalid mix of parameter types in list. Use only primitive values or only BigQuery QueryParameter objects."
165
- raise SQLSpecError(msg)
166
-
167
- elif kwargs:
168
- params = kwargs
169
- param_style = "named"
170
- elif parameters is not None and not isinstance(
171
- parameters, (bigquery.ScalarQueryParameter, bigquery.ArrayQueryParameter)
172
- ):
173
- # Could be a single primitive value for positional
174
- params = [parameters]
175
- param_style = "qmark"
176
- elif parameters is not None: # Single BQ parameter object
177
- msg = "Single BigQuery QueryParameter objects should be passed within a list."
178
- raise SQLSpecError(msg)
179
-
180
- # Use sqlglot to transpile ONLY if not a script and not preformatted
181
- if not is_script and not use_preformatted_params:
182
- try:
183
- # Transpile for syntax normalization/dialect conversion if needed
184
- # Use BigQuery dialect for both reading and writing
185
- final_sql = sqlglot.transpile(sql, read=self.dialect, write=self.dialect)[0]
186
- except Exception as e:
187
- # Catch potential sqlglot errors
188
- msg = f"SQL transpilation failed using sqlglot: {e!s}" # Adjusted message
189
- raise SQLSpecError(msg) from e
190
- # else: If preformatted_params, final_sql remains the original sql
191
-
192
- # --- Parameter Handling Logic --- (Moved outside the transpilation try/except)
193
- # Prepare BQ parameters based on style, ONLY if not preformatted
194
- if not use_preformatted_params:
195
- if param_style == "named" and params:
196
- # Convert dict params to BQ ScalarQueryParameter
197
- if isinstance(params, dict):
198
- final_job_config.query_parameters = [
199
- bigquery.ScalarQueryParameter(name, self._get_bq_param_type(value)[0], value)
200
- for name, value in params.items()
201
- ]
202
- else:
203
- # This path should ideally not be reached if param_style logic is correct
204
- msg = f"Internal error: Parameter style is 'named' but parameters are not a dict: {type(params)}"
205
- raise SQLSpecError(msg)
206
- elif param_style == "qmark" and params:
207
- # Convert list params to BQ ScalarQueryParameter
208
- final_job_config.query_parameters = [
209
- bigquery.ScalarQueryParameter(None, self._get_bq_param_type(value)[0], value) for value in params
210
- ]
211
-
212
- # --- Parameter Handling Logic --- End
213
-
214
- # Determine which kwargs to pass to the actual query method.
215
- # We only want to pass kwargs that were *not* treated as SQL parameters.
191
+ final_job_config.query_parameters = list(processed_params)
192
+ # Convert regular parameters to BigQuery parameters
193
+ elif isinstance(processed_params, dict):
194
+ # Convert dict params to BQ ScalarQueryParameter
195
+ final_job_config.query_parameters = [
196
+ bigquery.ScalarQueryParameter(name, self._get_bq_param_type(value)[0], value)
197
+ for name, value in processed_params.items()
198
+ ]
199
+ elif isinstance(processed_params, (list, tuple)):
200
+ # Convert list params to BQ ScalarQueryParameter
201
+ final_job_config.query_parameters = [
202
+ bigquery.ScalarQueryParameter(None, self._get_bq_param_type(value)[0], value)
203
+ for value in processed_params
204
+ ]
205
+
206
+ # Determine which kwargs to pass to the actual query method
207
+ # We only want to pass kwargs that were *not* treated as SQL parameters
216
208
  final_query_kwargs = {}
217
209
  if parameters is not None and kwargs: # Params came via arg, kwargs are separate
218
210
  final_query_kwargs = kwargs
219
- # Else: If params came via kwargs, they are already handled, so don't pass them again.
211
+ # Else: If params came via kwargs, they are already handled, so don't pass them again
220
212
 
221
213
  # Execute query
222
214
  return conn.query(
223
215
  final_sql,
224
216
  job_config=final_job_config,
225
- **final_query_kwargs, # Pass only relevant kwargs
217
+ **final_query_kwargs,
226
218
  )
227
219
 
228
- @staticmethod
220
+ @overload
229
221
  def _rows_to_results(
222
+ self,
223
+ rows: "Iterator[Row]",
224
+ schema: "Sequence[SchemaField]",
225
+ schema_type: "type[ModelDTOT]",
226
+ ) -> Sequence[ModelDTOT]: ...
227
+ @overload
228
+ def _rows_to_results(
229
+ self,
230
+ rows: "Iterator[Row]",
231
+ schema: "Sequence[SchemaField]",
232
+ schema_type: None = None,
233
+ ) -> Sequence[dict[str, Any]]: ...
234
+ def _rows_to_results(
235
+ self,
230
236
  rows: "Iterator[Row]",
231
237
  schema: "Sequence[SchemaField]",
232
238
  schema_type: "Optional[type[ModelDTOT]]" = None,
@@ -249,14 +255,8 @@ class BigQueryDriver(
249
255
  row_dict[key] = value # type: ignore[assignment]
250
256
  else:
251
257
  row_dict[key] = value
252
- # Use the processed dictionary for the final result
253
- if schema_type:
254
- processed_results.append(schema_type(**row_dict))
255
- else:
256
- processed_results.append(row_dict) # type: ignore[arg-type]
257
- if schema_type:
258
- return cast("Sequence[ModelDTOT]", processed_results)
259
- return cast("Sequence[dict[str, Any]]", processed_results)
258
+ processed_results.append(row_dict)
259
+ return self.to_schema(processed_results, schema_type=schema_type)
260
260
 
261
261
  @overload
262
262
  def select(
@@ -264,7 +264,7 @@ class BigQueryDriver(
264
264
  sql: str,
265
265
  parameters: "Optional[StatementParameterType]" = None,
266
266
  /,
267
- *,
267
+ *filters: StatementFilter,
268
268
  connection: "Optional[BigQueryConnection]" = None,
269
269
  schema_type: None = None,
270
270
  **kwargs: Any,
@@ -275,7 +275,7 @@ class BigQueryDriver(
275
275
  sql: str,
276
276
  parameters: "Optional[StatementParameterType]" = None,
277
277
  /,
278
- *,
278
+ *filters: StatementFilter,
279
279
  connection: "Optional[BigQueryConnection]" = None,
280
280
  schema_type: "type[ModelDTOT]",
281
281
  **kwargs: Any,
@@ -285,13 +285,29 @@ class BigQueryDriver(
285
285
  sql: str,
286
286
  parameters: "Optional[StatementParameterType]" = None,
287
287
  /,
288
- *,
288
+ *filters: StatementFilter,
289
289
  connection: "Optional[BigQueryConnection]" = None,
290
290
  schema_type: "Optional[type[ModelDTOT]]" = None,
291
291
  job_config: "Optional[QueryJobConfig]" = None,
292
292
  **kwargs: Any,
293
293
  ) -> "Sequence[Union[ModelDTOT, dict[str, Any]]]":
294
- query_job = self._run_query_job(sql, parameters, connection, job_config, **kwargs)
294
+ """Fetch data from the database.
295
+
296
+ Args:
297
+ sql: The SQL query string.
298
+ parameters: The parameters for the query (dict, tuple, list, or None).
299
+ *filters: Statement filters to apply.
300
+ connection: Optional connection override.
301
+ schema_type: Optional schema class for the result.
302
+ job_config: Optional job configuration.
303
+ **kwargs: Additional keyword arguments to merge with parameters if parameters is a dict.
304
+
305
+ Returns:
306
+ List of row data as either model instances or dictionaries.
307
+ """
308
+ query_job = self._run_query_job(
309
+ sql, parameters, *filters, connection=connection, job_config=job_config, **kwargs
310
+ )
295
311
  return self._rows_to_results(query_job.result(), query_job.result().schema, schema_type)
296
312
 
297
313
  @overload
@@ -300,7 +316,7 @@ class BigQueryDriver(
300
316
  sql: str,
301
317
  parameters: "Optional[StatementParameterType]" = None,
302
318
  /,
303
- *,
319
+ *filters: StatementFilter,
304
320
  connection: "Optional[BigQueryConnection]" = None,
305
321
  schema_type: None = None,
306
322
  **kwargs: Any,
@@ -311,7 +327,7 @@ class BigQueryDriver(
311
327
  sql: str,
312
328
  parameters: "Optional[StatementParameterType]" = None,
313
329
  /,
314
- *,
330
+ *filters: StatementFilter,
315
331
  connection: "Optional[BigQueryConnection]" = None,
316
332
  schema_type: "type[ModelDTOT]",
317
333
  **kwargs: Any,
@@ -321,13 +337,15 @@ class BigQueryDriver(
321
337
  sql: str,
322
338
  parameters: "Optional[StatementParameterType]" = None,
323
339
  /,
324
- *,
340
+ *filters: StatementFilter,
325
341
  connection: "Optional[BigQueryConnection]" = None,
326
342
  schema_type: "Optional[type[ModelDTOT]]" = None,
327
343
  job_config: "Optional[QueryJobConfig]" = None,
328
344
  **kwargs: Any,
329
345
  ) -> "Union[ModelDTOT, dict[str, Any]]":
330
- query_job = self._run_query_job(sql, parameters, connection, job_config, **kwargs)
346
+ query_job = self._run_query_job(
347
+ sql, parameters, *filters, connection=connection, job_config=job_config, **kwargs
348
+ )
331
349
  rows_iterator = query_job.result()
332
350
  try:
333
351
  # Pass the iterator containing only the first row to _rows_to_results
@@ -350,7 +368,7 @@ class BigQueryDriver(
350
368
  sql: str,
351
369
  parameters: "Optional[StatementParameterType]" = None,
352
370
  /,
353
- *,
371
+ *filters: StatementFilter,
354
372
  connection: "Optional[BigQueryConnection]" = None,
355
373
  schema_type: None = None,
356
374
  **kwargs: Any,
@@ -361,7 +379,7 @@ class BigQueryDriver(
361
379
  sql: str,
362
380
  parameters: "Optional[StatementParameterType]" = None,
363
381
  /,
364
- *,
382
+ *filters: StatementFilter,
365
383
  connection: "Optional[BigQueryConnection]" = None,
366
384
  schema_type: "type[ModelDTOT]",
367
385
  **kwargs: Any,
@@ -371,13 +389,15 @@ class BigQueryDriver(
371
389
  sql: str,
372
390
  parameters: "Optional[StatementParameterType]" = None,
373
391
  /,
374
- *,
392
+ *filters: StatementFilter,
375
393
  connection: "Optional[BigQueryConnection]" = None,
376
394
  schema_type: "Optional[type[ModelDTOT]]" = None,
377
395
  job_config: "Optional[QueryJobConfig]" = None,
378
396
  **kwargs: Any,
379
397
  ) -> "Optional[Union[ModelDTOT, dict[str, Any]]]":
380
- query_job = self._run_query_job(sql, parameters, connection, job_config, **kwargs)
398
+ query_job = self._run_query_job(
399
+ sql, parameters, *filters, connection=connection, job_config=job_config, **kwargs
400
+ )
381
401
  rows_iterator = query_job.result()
382
402
  try:
383
403
  first_row = next(rows_iterator)
@@ -395,7 +415,7 @@ class BigQueryDriver(
395
415
  sql: str,
396
416
  parameters: "Optional[StatementParameterType]" = None,
397
417
  /,
398
- *,
418
+ *filters: StatementFilter,
399
419
  connection: "Optional[BigQueryConnection]" = None,
400
420
  schema_type: "Optional[type[T]]" = None,
401
421
  job_config: "Optional[QueryJobConfig]" = None,
@@ -407,7 +427,7 @@ class BigQueryDriver(
407
427
  sql: str,
408
428
  parameters: "Optional[StatementParameterType]" = None,
409
429
  /,
410
- *,
430
+ *filters: StatementFilter,
411
431
  connection: "Optional[BigQueryConnection]" = None,
412
432
  schema_type: "type[T]",
413
433
  **kwargs: Any,
@@ -417,14 +437,14 @@ class BigQueryDriver(
417
437
  sql: str,
418
438
  parameters: "Optional[StatementParameterType]" = None,
419
439
  /,
420
- *,
440
+ *filters: StatementFilter,
421
441
  connection: "Optional[BigQueryConnection]" = None,
422
442
  schema_type: "Optional[type[T]]" = None,
423
443
  job_config: "Optional[QueryJobConfig]" = None,
424
444
  **kwargs: Any,
425
445
  ) -> Union[T, Any]:
426
446
  query_job = self._run_query_job(
427
- sql=sql, parameters=parameters, connection=connection, job_config=job_config, **kwargs
447
+ sql, parameters, *filters, connection=connection, job_config=job_config, **kwargs
428
448
  )
429
449
  rows = query_job.result()
430
450
  try:
@@ -447,7 +467,7 @@ class BigQueryDriver(
447
467
  sql: str,
448
468
  parameters: "Optional[StatementParameterType]" = None,
449
469
  /,
450
- *,
470
+ *filters: StatementFilter,
451
471
  connection: "Optional[BigQueryConnection]" = None,
452
472
  schema_type: None = None,
453
473
  **kwargs: Any,
@@ -458,7 +478,7 @@ class BigQueryDriver(
458
478
  sql: str,
459
479
  parameters: "Optional[StatementParameterType]" = None,
460
480
  /,
461
- *,
481
+ *filters: StatementFilter,
462
482
  connection: "Optional[BigQueryConnection]" = None,
463
483
  schema_type: "type[T]",
464
484
  **kwargs: Any,
@@ -468,14 +488,19 @@ class BigQueryDriver(
468
488
  sql: str,
469
489
  parameters: "Optional[StatementParameterType]" = None,
470
490
  /,
471
- *,
491
+ *filters: StatementFilter,
472
492
  connection: "Optional[BigQueryConnection]" = None,
473
493
  schema_type: "Optional[type[T]]" = None,
474
494
  job_config: "Optional[QueryJobConfig]" = None,
475
495
  **kwargs: Any,
476
496
  ) -> "Optional[Union[T, Any]]":
477
497
  query_job = self._run_query_job(
478
- sql=sql, parameters=parameters, connection=connection, job_config=job_config, **kwargs
498
+ sql,
499
+ parameters,
500
+ *filters,
501
+ connection=connection,
502
+ job_config=job_config,
503
+ **kwargs,
479
504
  )
480
505
  rows = query_job.result()
481
506
  try:
@@ -496,7 +521,7 @@ class BigQueryDriver(
496
521
  sql: str,
497
522
  parameters: Optional[StatementParameterType] = None,
498
523
  /,
499
- *,
524
+ *filters: StatementFilter,
500
525
  connection: Optional["BigQueryConnection"] = None,
501
526
  job_config: Optional[QueryJobConfig] = None,
502
527
  **kwargs: Any,
@@ -507,7 +532,7 @@ class BigQueryDriver(
507
532
  int: The number of rows affected by the DML statement.
508
533
  """
509
534
  query_job = self._run_query_job(
510
- sql=sql, parameters=parameters, connection=connection, job_config=job_config, **kwargs
535
+ sql, parameters, *filters, connection=connection, job_config=job_config, **kwargs
511
536
  )
512
537
  # DML statements might not return rows, check job properties
513
538
  # num_dml_affected_rows might be None initially, wait might be needed
@@ -520,7 +545,7 @@ class BigQueryDriver(
520
545
  sql: str,
521
546
  parameters: "Optional[StatementParameterType]" = None,
522
547
  /,
523
- *,
548
+ *filters: StatementFilter,
524
549
  connection: "Optional[BigQueryConnection]" = None,
525
550
  schema_type: None = None,
526
551
  **kwargs: Any,
@@ -531,7 +556,7 @@ class BigQueryDriver(
531
556
  sql: str,
532
557
  parameters: "Optional[StatementParameterType]" = None,
533
558
  /,
534
- *,
559
+ *filters: StatementFilter,
535
560
  connection: "Optional[BigQueryConnection]" = None,
536
561
  schema_type: "type[ModelDTOT]",
537
562
  **kwargs: Any,
@@ -541,7 +566,7 @@ class BigQueryDriver(
541
566
  sql: str,
542
567
  parameters: "Optional[StatementParameterType]" = None,
543
568
  /,
544
- *,
569
+ *filters: StatementFilter,
545
570
  connection: "Optional[BigQueryConnection]" = None,
546
571
  schema_type: "Optional[type[ModelDTOT]]" = None,
547
572
  job_config: "Optional[QueryJobConfig]" = None,
@@ -556,7 +581,6 @@ class BigQueryDriver(
556
581
  sql: str, # Expecting a script here
557
582
  parameters: "Optional[StatementParameterType]" = None, # Parameters might be complex in scripts
558
583
  /,
559
- *,
560
584
  connection: "Optional[BigQueryConnection]" = None,
561
585
  job_config: "Optional[QueryJobConfig]" = None,
562
586
  **kwargs: Any,
@@ -567,8 +591,8 @@ class BigQueryDriver(
567
591
  str: The job ID of the executed script.
568
592
  """
569
593
  query_job = self._run_query_job(
570
- sql=sql,
571
- parameters=parameters,
594
+ sql,
595
+ parameters,
572
596
  connection=connection,
573
597
  job_config=job_config,
574
598
  is_script=True,
@@ -578,12 +602,12 @@ class BigQueryDriver(
578
602
 
579
603
  # --- Mixin Implementations ---
580
604
 
581
- def select_arrow( # pyright: ignore # noqa: PLR0912
605
+ def select_arrow( # pyright: ignore
582
606
  self,
583
607
  sql: str,
584
608
  parameters: "Optional[StatementParameterType]" = None,
585
609
  /,
586
- *,
610
+ *filters: StatementFilter,
587
611
  connection: "Optional[BigQueryConnection]" = None,
588
612
  job_config: "Optional[QueryJobConfig]" = None,
589
613
  **kwargs: Any,
@@ -591,41 +615,13 @@ class BigQueryDriver(
591
615
  conn = self._connection(connection)
592
616
  final_job_config = job_config or self._default_query_job_config or QueryJobConfig()
593
617
 
594
- # Determine parameter style and merge parameters (Similar to _run_query_job)
595
- params: Union[dict[str, Any], list[Any], None] = None
596
- param_style: Optional[str] = None # 'named' (@), 'qmark' (?)
597
-
598
- if isinstance(parameters, dict):
599
- params = {**parameters, **kwargs}
600
- param_style = "named"
601
- elif isinstance(parameters, (list, tuple)):
602
- if kwargs:
603
- msg = "Cannot mix positional parameters with keyword arguments."
604
- raise SQLSpecError(msg)
605
- params = list(parameters)
606
- param_style = "qmark"
607
- elif kwargs:
608
- params = kwargs
609
- param_style = "named"
610
- elif parameters is not None:
611
- params = [parameters]
612
- param_style = "qmark"
613
-
614
- # Use sqlglot to transpile and bind parameters
615
- try:
616
- transpiled_sql = sqlglot.transpile(sql, args=params or {}, read=None, write=self.dialect)[0]
617
- except Exception as e:
618
- msg = f"SQL transpilation/binding failed using sqlglot: {e!s}"
619
- raise SQLSpecError(msg) from e
618
+ # Process SQL and parameters using SQLStatement
619
+ processed_sql, processed_params = self._process_sql_params(sql, parameters, *filters, **kwargs)
620
620
 
621
- # Prepare BigQuery specific parameters if named style was used
622
- if param_style == "named" and params:
623
- if not isinstance(params, dict):
624
- # This should be logically impossible due to how param_style is set
625
- msg = "Internal error: named parameter style detected but params is not a dict."
626
- raise SQLSpecError(msg)
621
+ # Convert parameters to BigQuery format
622
+ if isinstance(processed_params, dict):
627
623
  query_parameters = []
628
- for key, value in params.items():
624
+ for key, value in processed_params.items():
629
625
  param_type, array_element_type = self._get_bq_param_type(value)
630
626
 
631
627
  if param_type == "ARRAY" and array_element_type:
@@ -636,15 +632,17 @@ class BigQueryDriver(
636
632
  msg = f"Unsupported parameter type for BigQuery Arrow named parameter '{key}': {type(value)}"
637
633
  raise SQLSpecError(msg)
638
634
  final_job_config.query_parameters = query_parameters
639
- elif param_style == "qmark" and params:
640
- # Positional params handled by client library
641
- pass
635
+ elif isinstance(processed_params, (list, tuple)):
636
+ # Convert sequence parameters
637
+ final_job_config.query_parameters = [
638
+ bigquery.ScalarQueryParameter(None, self._get_bq_param_type(value)[0], value)
639
+ for value in processed_params
640
+ ]
642
641
 
643
642
  # Execute the query and get Arrow table
644
643
  try:
645
- query_job = conn.query(transpiled_sql, job_config=final_job_config)
644
+ query_job = conn.query(processed_sql, job_config=final_job_config)
646
645
  arrow_table = query_job.to_arrow() # Waits for job completion
647
-
648
646
  except Exception as e:
649
647
  msg = f"BigQuery Arrow query execution failed: {e!s}"
650
648
  raise SQLSpecError(msg) from e
@@ -655,7 +653,7 @@ class BigQueryDriver(
655
653
  sql: str, # Expects table ID: project.dataset.table
656
654
  parameters: "Optional[StatementParameterType]" = None,
657
655
  /,
658
- *,
656
+ *filters: StatementFilter,
659
657
  destination_uri: "Optional[str]" = None,
660
658
  connection: "Optional[BigQueryConnection]" = None,
661
659
  job_config: "Optional[bigquery.ExtractJobConfig]" = None,
@@ -699,3 +697,14 @@ class BigQueryDriver(
699
697
  if extract_job.errors:
700
698
  msg = f"BigQuery Parquet export failed: {extract_job.errors}"
701
699
  raise SQLSpecError(msg)
700
+
701
+ def _connection(self, connection: "Optional[BigQueryConnection]" = None) -> "BigQueryConnection":
702
+ """Get the connection to use for the operation.
703
+
704
+ Args:
705
+ connection: Optional connection to use.
706
+
707
+ Returns:
708
+ The connection to use.
709
+ """
710
+ return connection or self.connection