sqlspec 0.11.0__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,7 +1,7 @@
1
1
  import contextlib
2
2
  import datetime
3
3
  import logging
4
- from collections.abc import Iterator, Sequence
4
+ from collections.abc import Iterator, Mapping, Sequence
5
5
  from decimal import Decimal
6
6
  from typing import (
7
7
  TYPE_CHECKING,
@@ -69,8 +69,6 @@ class BigQueryDriver(
69
69
  if isinstance(value, float):
70
70
  return "FLOAT64", None
71
71
  if isinstance(value, Decimal):
72
- # Precision/scale might matter, but BQ client handles conversion.
73
- # Defaulting to BIGNUMERIC, NUMERIC might be desired in some cases though (User change)
74
72
  return "BIGNUMERIC", None
75
73
  if isinstance(value, str):
76
74
  return "STRING", None
@@ -78,23 +76,17 @@ class BigQueryDriver(
78
76
  return "BYTES", None
79
77
  if isinstance(value, datetime.date):
80
78
  return "DATE", None
81
- # DATETIME is for timezone-naive values
82
79
  if isinstance(value, datetime.datetime) and value.tzinfo is None:
83
80
  return "DATETIME", None
84
- # TIMESTAMP is for timezone-aware values
85
81
  if isinstance(value, datetime.datetime) and value.tzinfo is not None:
86
82
  return "TIMESTAMP", None
87
83
  if isinstance(value, datetime.time):
88
84
  return "TIME", None
89
85
 
90
- # Handle Arrays - Determine element type
91
86
  if isinstance(value, (list, tuple)):
92
87
  if not value:
93
- # Cannot determine type of empty array, BQ requires type.
94
- # Raise or default? Defaulting is risky. Let's raise.
95
88
  msg = "Cannot determine BigQuery ARRAY type for empty sequence."
96
89
  raise SQLSpecError(msg)
97
- # Infer type from first element
98
90
  first_element = value[0]
99
91
  element_type, _ = BigQueryDriver._get_bq_param_type(first_element)
100
92
  if element_type is None:
@@ -102,55 +94,59 @@ class BigQueryDriver(
102
94
  raise SQLSpecError(msg)
103
95
  return "ARRAY", element_type
104
96
 
105
- # Handle Structs (basic dict mapping) - Requires careful handling
106
- # if isinstance(value, dict):
107
- # # This requires recursive type mapping for sub-fields.
108
- # # For simplicity, users might need to construct StructQueryParameter manually.
109
- # # return "STRUCT", None # Placeholder if implementing # noqa: ERA001
110
- # raise SQLSpecError("Automatic STRUCT mapping not implemented. Please use bigquery.StructQueryParameter.") # noqa: ERA001
111
-
112
- return None, None # Unsupported type
97
+ return None, None
113
98
 
114
99
  def _process_sql_params(
115
100
  self,
116
101
  sql: str,
117
102
  parameters: "Optional[StatementParameterType]" = None,
118
- /,
119
- *filters: StatementFilter,
103
+ *filters: "StatementFilter",
120
104
  **kwargs: Any,
121
105
  ) -> "tuple[str, Optional[Union[tuple[Any, ...], list[Any], dict[str, Any]]]]":
122
106
  """Process SQL and parameters using SQLStatement with dialect support.
123
107
 
108
+ This method also handles the separation of StatementFilter instances that might be
109
+ passed in the 'parameters' argument.
110
+
124
111
  Args:
125
112
  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.
113
+ parameters: The parameters to bind to the statement. This can be a
114
+ Mapping (dict), Sequence (list/tuple), a single StatementFilter, or None.
115
+ *filters: Additional statement filters to apply.
116
+ **kwargs: Additional keyword arguments (treated as named parameters for the SQL statement).
129
117
 
130
118
  Raises:
131
119
  ParameterStyleMismatchError: If pre-formatted BigQuery parameters are mixed with keyword arguments.
132
120
 
133
121
  Returns:
134
- A tuple of (sql, parameters) ready for execution.
122
+ A tuple of (processed_sql, processed_parameters) ready for execution.
135
123
  """
136
- # Special case: check for pre-formatted BQ parameters
124
+ passed_parameters: Optional[Union[Mapping[str, Any], Sequence[Any]]] = None
125
+ combined_filters_list: list[StatementFilter] = list(filters)
126
+
127
+ if parameters is not None:
128
+ if isinstance(parameters, StatementFilter):
129
+ combined_filters_list.insert(0, parameters)
130
+ else:
131
+ passed_parameters = parameters
132
+
137
133
  if (
138
- isinstance(parameters, (list, tuple))
139
- and parameters
140
- and all(isinstance(p, (bigquery.ScalarQueryParameter, bigquery.ArrayQueryParameter)) for p in parameters)
134
+ isinstance(passed_parameters, (list, tuple))
135
+ and passed_parameters
136
+ and all(
137
+ isinstance(p, (bigquery.ScalarQueryParameter, bigquery.ArrayQueryParameter)) for p in passed_parameters
138
+ )
141
139
  ):
142
140
  if kwargs:
143
141
  msg = "Cannot mix pre-formatted BigQuery parameters with keyword arguments."
144
142
  raise ParameterStyleMismatchError(msg)
145
- return sql, parameters
143
+ return sql, passed_parameters
146
144
 
147
- statement = SQLStatement(sql, parameters, kwargs=kwargs, dialect=self.dialect)
145
+ statement = SQLStatement(sql, passed_parameters, kwargs=kwargs, dialect=self.dialect)
148
146
 
149
- # Apply any filters
150
- for filter_obj in filters:
147
+ for filter_obj in combined_filters_list:
151
148
  statement = statement.apply_filter(filter_obj)
152
149
 
153
- # Process the statement for execution
154
150
  processed_sql, processed_params, _ = statement.process()
155
151
 
156
152
  return processed_sql, processed_params
@@ -159,8 +155,7 @@ class BigQueryDriver(
159
155
  self,
160
156
  sql: str,
161
157
  parameters: "Optional[StatementParameterType]" = None,
162
- /,
163
- *filters: StatementFilter,
158
+ *filters: "StatementFilter",
164
159
  connection: "Optional[BigQueryConnection]" = None,
165
160
  job_config: "Optional[QueryJobConfig]" = None,
166
161
  is_script: bool = False,
@@ -168,19 +163,15 @@ class BigQueryDriver(
168
163
  ) -> "QueryJob":
169
164
  conn = self._connection(connection)
170
165
 
171
- # Determine the final job config, creating a new one if necessary
172
- # to avoid modifying a shared default config.
173
166
  if job_config:
174
- final_job_config = job_config # Use the provided config directly
167
+ final_job_config = job_config
175
168
  elif self._default_query_job_config:
176
- final_job_config = QueryJobConfig()
169
+ final_job_config = QueryJobConfig.from_api_repr(self._default_query_job_config.to_api_repr()) # type: ignore[assignment]
177
170
  else:
178
- final_job_config = QueryJobConfig() # Create a fresh config
171
+ final_job_config = QueryJobConfig()
179
172
 
180
- # Process SQL and parameters
181
173
  final_sql, processed_params = self._process_sql_params(sql, parameters, *filters, **kwargs)
182
174
 
183
- # Handle pre-formatted parameters
184
175
  if (
185
176
  isinstance(processed_params, (list, tuple))
186
177
  and processed_params
@@ -189,31 +180,24 @@ class BigQueryDriver(
189
180
  )
190
181
  ):
191
182
  final_job_config.query_parameters = list(processed_params)
192
- # Convert regular parameters to BigQuery parameters
193
183
  elif isinstance(processed_params, dict):
194
- # Convert dict params to BQ ScalarQueryParameter
195
184
  final_job_config.query_parameters = [
196
185
  bigquery.ScalarQueryParameter(name, self._get_bq_param_type(value)[0], value)
197
186
  for name, value in processed_params.items()
198
187
  ]
199
188
  elif isinstance(processed_params, (list, tuple)):
200
- # Convert list params to BQ ScalarQueryParameter
201
189
  final_job_config.query_parameters = [
202
190
  bigquery.ScalarQueryParameter(None, self._get_bq_param_type(value)[0], value)
203
191
  for value in processed_params
204
192
  ]
205
193
 
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
208
194
  final_query_kwargs = {}
209
- if parameters is not None and kwargs: # Params came via arg, kwargs are separate
195
+ if parameters is not None and kwargs:
210
196
  final_query_kwargs = kwargs
211
- # Else: If params came via kwargs, they are already handled, so don't pass them again
212
197
 
213
- # Execute query
214
198
  return conn.query(
215
199
  final_sql,
216
- job_config=final_job_config,
200
+ job_config=final_job_config, # pyright: ignore
217
201
  **final_query_kwargs,
218
202
  )
219
203
 
@@ -238,15 +222,12 @@ class BigQueryDriver(
238
222
  schema_type: "Optional[type[ModelDTOT]]" = None,
239
223
  ) -> Sequence[Union[ModelDTOT, dict[str, Any]]]:
240
224
  processed_results = []
241
- # Create a quick lookup map for schema fields from the passed schema
242
225
  schema_map = {field.name: field for field in schema}
243
226
 
244
227
  for row in rows:
245
- # row here is now a Row object from the iterator
246
228
  row_dict = {}
247
- for key, value in row.items(): # Use row.items() on the Row object
229
+ for key, value in row.items():
248
230
  field = schema_map.get(key)
249
- # Workaround remains the same
250
231
  if field and field.field_type == "TIMESTAMP" and isinstance(value, str) and "." in value:
251
232
  try:
252
233
  parsed_value = datetime.datetime.fromtimestamp(float(value), tz=datetime.timezone.utc)
@@ -263,8 +244,7 @@ class BigQueryDriver(
263
244
  self,
264
245
  sql: str,
265
246
  parameters: "Optional[StatementParameterType]" = None,
266
- /,
267
- *filters: StatementFilter,
247
+ *filters: "StatementFilter",
268
248
  connection: "Optional[BigQueryConnection]" = None,
269
249
  schema_type: None = None,
270
250
  **kwargs: Any,
@@ -274,8 +254,7 @@ class BigQueryDriver(
274
254
  self,
275
255
  sql: str,
276
256
  parameters: "Optional[StatementParameterType]" = None,
277
- /,
278
- *filters: StatementFilter,
257
+ *filters: "StatementFilter",
279
258
  connection: "Optional[BigQueryConnection]" = None,
280
259
  schema_type: "type[ModelDTOT]",
281
260
  **kwargs: Any,
@@ -284,27 +263,12 @@ class BigQueryDriver(
284
263
  self,
285
264
  sql: str,
286
265
  parameters: "Optional[StatementParameterType]" = None,
287
- /,
288
- *filters: StatementFilter,
266
+ *filters: "StatementFilter",
289
267
  connection: "Optional[BigQueryConnection]" = None,
290
268
  schema_type: "Optional[type[ModelDTOT]]" = None,
291
269
  job_config: "Optional[QueryJobConfig]" = None,
292
270
  **kwargs: Any,
293
271
  ) -> "Sequence[Union[ModelDTOT, dict[str, Any]]]":
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
272
  query_job = self._run_query_job(
309
273
  sql, parameters, *filters, connection=connection, job_config=job_config, **kwargs
310
274
  )
@@ -315,8 +279,7 @@ class BigQueryDriver(
315
279
  self,
316
280
  sql: str,
317
281
  parameters: "Optional[StatementParameterType]" = None,
318
- /,
319
- *filters: StatementFilter,
282
+ *filters: "StatementFilter",
320
283
  connection: "Optional[BigQueryConnection]" = None,
321
284
  schema_type: None = None,
322
285
  **kwargs: Any,
@@ -326,8 +289,7 @@ class BigQueryDriver(
326
289
  self,
327
290
  sql: str,
328
291
  parameters: "Optional[StatementParameterType]" = None,
329
- /,
330
- *filters: StatementFilter,
292
+ *filters: "StatementFilter",
331
293
  connection: "Optional[BigQueryConnection]" = None,
332
294
  schema_type: "type[ModelDTOT]",
333
295
  **kwargs: Any,
@@ -336,8 +298,7 @@ class BigQueryDriver(
336
298
  self,
337
299
  sql: str,
338
300
  parameters: "Optional[StatementParameterType]" = None,
339
- /,
340
- *filters: StatementFilter,
301
+ *filters: "StatementFilter",
341
302
  connection: "Optional[BigQueryConnection]" = None,
342
303
  schema_type: "Optional[type[ModelDTOT]]" = None,
343
304
  job_config: "Optional[QueryJobConfig]" = None,
@@ -348,14 +309,8 @@ class BigQueryDriver(
348
309
  )
349
310
  rows_iterator = query_job.result()
350
311
  try:
351
- # Pass the iterator containing only the first row to _rows_to_results
352
- # This ensures the timestamp workaround is applied consistently.
353
- # We need to pass the original iterator for schema access, but only consume one row.
354
312
  first_row = next(rows_iterator)
355
- # Create a simple iterator yielding only the first row for processing
356
313
  single_row_iter = iter([first_row])
357
- # We need RowIterator type for schema, create mock/proxy if needed, or pass schema
358
- # Let's try passing schema directly to _rows_to_results (requires modifying it)
359
314
  results = self._rows_to_results(single_row_iter, rows_iterator.schema, schema_type)
360
315
  return results[0]
361
316
  except StopIteration:
@@ -367,8 +322,7 @@ class BigQueryDriver(
367
322
  self,
368
323
  sql: str,
369
324
  parameters: "Optional[StatementParameterType]" = None,
370
- /,
371
- *filters: StatementFilter,
325
+ *filters: "StatementFilter",
372
326
  connection: "Optional[BigQueryConnection]" = None,
373
327
  schema_type: None = None,
374
328
  **kwargs: Any,
@@ -378,8 +332,7 @@ class BigQueryDriver(
378
332
  self,
379
333
  sql: str,
380
334
  parameters: "Optional[StatementParameterType]" = None,
381
- /,
382
- *filters: StatementFilter,
335
+ *filters: "StatementFilter",
383
336
  connection: "Optional[BigQueryConnection]" = None,
384
337
  schema_type: "type[ModelDTOT]",
385
338
  **kwargs: Any,
@@ -388,8 +341,7 @@ class BigQueryDriver(
388
341
  self,
389
342
  sql: str,
390
343
  parameters: "Optional[StatementParameterType]" = None,
391
- /,
392
- *filters: StatementFilter,
344
+ *filters: "StatementFilter",
393
345
  connection: "Optional[BigQueryConnection]" = None,
394
346
  schema_type: "Optional[type[ModelDTOT]]" = None,
395
347
  job_config: "Optional[QueryJobConfig]" = None,
@@ -401,9 +353,7 @@ class BigQueryDriver(
401
353
  rows_iterator = query_job.result()
402
354
  try:
403
355
  first_row = next(rows_iterator)
404
- # Create a simple iterator yielding only the first row for processing
405
356
  single_row_iter = iter([first_row])
406
- # Pass schema directly
407
357
  results = self._rows_to_results(single_row_iter, rows_iterator.schema, schema_type)
408
358
  return results[0]
409
359
  except StopIteration:
@@ -414,8 +364,7 @@ class BigQueryDriver(
414
364
  self,
415
365
  sql: str,
416
366
  parameters: "Optional[StatementParameterType]" = None,
417
- /,
418
- *filters: StatementFilter,
367
+ *filters: "StatementFilter",
419
368
  connection: "Optional[BigQueryConnection]" = None,
420
369
  schema_type: "Optional[type[T]]" = None,
421
370
  job_config: "Optional[QueryJobConfig]" = None,
@@ -426,8 +375,7 @@ class BigQueryDriver(
426
375
  self,
427
376
  sql: str,
428
377
  parameters: "Optional[StatementParameterType]" = None,
429
- /,
430
- *filters: StatementFilter,
378
+ *filters: "StatementFilter",
431
379
  connection: "Optional[BigQueryConnection]" = None,
432
380
  schema_type: "type[T]",
433
381
  **kwargs: Any,
@@ -436,8 +384,7 @@ class BigQueryDriver(
436
384
  self,
437
385
  sql: str,
438
386
  parameters: "Optional[StatementParameterType]" = None,
439
- /,
440
- *filters: StatementFilter,
387
+ *filters: "StatementFilter",
441
388
  connection: "Optional[BigQueryConnection]" = None,
442
389
  schema_type: "Optional[type[T]]" = None,
443
390
  job_config: "Optional[QueryJobConfig]" = None,
@@ -450,8 +397,7 @@ class BigQueryDriver(
450
397
  try:
451
398
  first_row = next(iter(rows))
452
399
  value = first_row[0]
453
- # Apply timestamp workaround if necessary
454
- field = rows.schema[0] # Get schema for the first column
400
+ field = rows.schema[0]
455
401
  if field and field.field_type == "TIMESTAMP" and isinstance(value, str) and "." in value:
456
402
  with contextlib.suppress(ValueError):
457
403
  value = datetime.datetime.fromtimestamp(float(value), tz=datetime.timezone.utc)
@@ -466,8 +412,7 @@ class BigQueryDriver(
466
412
  self,
467
413
  sql: str,
468
414
  parameters: "Optional[StatementParameterType]" = None,
469
- /,
470
- *filters: StatementFilter,
415
+ *filters: "StatementFilter",
471
416
  connection: "Optional[BigQueryConnection]" = None,
472
417
  schema_type: None = None,
473
418
  **kwargs: Any,
@@ -477,8 +422,7 @@ class BigQueryDriver(
477
422
  self,
478
423
  sql: str,
479
424
  parameters: "Optional[StatementParameterType]" = None,
480
- /,
481
- *filters: StatementFilter,
425
+ *filters: "StatementFilter",
482
426
  connection: "Optional[BigQueryConnection]" = None,
483
427
  schema_type: "type[T]",
484
428
  **kwargs: Any,
@@ -487,8 +431,7 @@ class BigQueryDriver(
487
431
  self,
488
432
  sql: str,
489
433
  parameters: "Optional[StatementParameterType]" = None,
490
- /,
491
- *filters: StatementFilter,
434
+ *filters: "StatementFilter",
492
435
  connection: "Optional[BigQueryConnection]" = None,
493
436
  schema_type: "Optional[type[T]]" = None,
494
437
  job_config: "Optional[QueryJobConfig]" = None,
@@ -506,8 +449,7 @@ class BigQueryDriver(
506
449
  try:
507
450
  first_row = next(iter(rows))
508
451
  value = first_row[0]
509
- # Apply timestamp workaround if necessary
510
- field = rows.schema[0] # Get schema for the first column
452
+ field = rows.schema[0]
511
453
  if field and field.field_type == "TIMESTAMP" and isinstance(value, str) and "." in value:
512
454
  with contextlib.suppress(ValueError):
513
455
  value = datetime.datetime.fromtimestamp(float(value), tz=datetime.timezone.utc)
@@ -520,32 +462,23 @@ class BigQueryDriver(
520
462
  self,
521
463
  sql: str,
522
464
  parameters: Optional[StatementParameterType] = None,
523
- /,
524
- *filters: StatementFilter,
465
+ *filters: "StatementFilter",
525
466
  connection: Optional["BigQueryConnection"] = None,
526
467
  job_config: Optional[QueryJobConfig] = None,
527
468
  **kwargs: Any,
528
469
  ) -> int:
529
- """Executes INSERT, UPDATE, DELETE and returns affected row count.
530
-
531
- Returns:
532
- int: The number of rows affected by the DML statement.
533
- """
534
470
  query_job = self._run_query_job(
535
471
  sql, parameters, *filters, connection=connection, job_config=job_config, **kwargs
536
472
  )
537
- # DML statements might not return rows, check job properties
538
- # num_dml_affected_rows might be None initially, wait might be needed
539
- query_job.result() # Ensure completion
540
- return query_job.num_dml_affected_rows or 0 # Return 0 if None
473
+ query_job.result()
474
+ return query_job.num_dml_affected_rows or 0
541
475
 
542
476
  @overload
543
477
  def insert_update_delete_returning(
544
478
  self,
545
479
  sql: str,
546
480
  parameters: "Optional[StatementParameterType]" = None,
547
- /,
548
- *filters: StatementFilter,
481
+ *filters: "StatementFilter",
549
482
  connection: "Optional[BigQueryConnection]" = None,
550
483
  schema_type: None = None,
551
484
  **kwargs: Any,
@@ -555,8 +488,7 @@ class BigQueryDriver(
555
488
  self,
556
489
  sql: str,
557
490
  parameters: "Optional[StatementParameterType]" = None,
558
- /,
559
- *filters: StatementFilter,
491
+ *filters: "StatementFilter",
560
492
  connection: "Optional[BigQueryConnection]" = None,
561
493
  schema_type: "type[ModelDTOT]",
562
494
  **kwargs: Any,
@@ -565,31 +497,23 @@ class BigQueryDriver(
565
497
  self,
566
498
  sql: str,
567
499
  parameters: "Optional[StatementParameterType]" = None,
568
- /,
569
- *filters: StatementFilter,
500
+ *filters: "StatementFilter",
570
501
  connection: "Optional[BigQueryConnection]" = None,
571
502
  schema_type: "Optional[type[ModelDTOT]]" = None,
572
503
  job_config: "Optional[QueryJobConfig]" = None,
573
504
  **kwargs: Any,
574
505
  ) -> Union[ModelDTOT, dict[str, Any]]:
575
- """BigQuery DML RETURNING equivalent is complex, often requires temp tables or scripting."""
576
506
  msg = "BigQuery does not support `RETURNING` clauses directly in the same way as some other SQL databases. Consider multi-statement queries or alternative approaches."
577
507
  raise NotImplementedError(msg)
578
508
 
579
509
  def execute_script(
580
510
  self,
581
- sql: str, # Expecting a script here
582
- parameters: "Optional[StatementParameterType]" = None, # Parameters might be complex in scripts
583
- /,
511
+ sql: str,
512
+ parameters: "Optional[StatementParameterType]" = None,
584
513
  connection: "Optional[BigQueryConnection]" = None,
585
514
  job_config: "Optional[QueryJobConfig]" = None,
586
515
  **kwargs: Any,
587
516
  ) -> str:
588
- """Executes a BigQuery script and returns the job ID.
589
-
590
- Returns:
591
- str: The job ID of the executed script.
592
- """
593
517
  query_job = self._run_query_job(
594
518
  sql,
595
519
  parameters,
@@ -600,14 +524,11 @@ class BigQueryDriver(
600
524
  )
601
525
  return str(query_job.job_id)
602
526
 
603
- # --- Mixin Implementations ---
604
-
605
527
  def select_arrow( # pyright: ignore
606
528
  self,
607
529
  sql: str,
608
530
  parameters: "Optional[StatementParameterType]" = None,
609
- /,
610
- *filters: StatementFilter,
531
+ *filters: "StatementFilter",
611
532
  connection: "Optional[BigQueryConnection]" = None,
612
533
  job_config: "Optional[QueryJobConfig]" = None,
613
534
  **kwargs: Any,
@@ -615,10 +536,8 @@ class BigQueryDriver(
615
536
  conn = self._connection(connection)
616
537
  final_job_config = job_config or self._default_query_job_config or QueryJobConfig()
617
538
 
618
- # Process SQL and parameters using SQLStatement
619
539
  processed_sql, processed_params = self._process_sql_params(sql, parameters, *filters, **kwargs)
620
540
 
621
- # Convert parameters to BigQuery format
622
541
  if isinstance(processed_params, dict):
623
542
  query_parameters = []
624
543
  for key, value in processed_params.items():
@@ -633,16 +552,14 @@ class BigQueryDriver(
633
552
  raise SQLSpecError(msg)
634
553
  final_job_config.query_parameters = query_parameters
635
554
  elif isinstance(processed_params, (list, tuple)):
636
- # Convert sequence parameters
637
555
  final_job_config.query_parameters = [
638
556
  bigquery.ScalarQueryParameter(None, self._get_bq_param_type(value)[0], value)
639
557
  for value in processed_params
640
558
  ]
641
559
 
642
- # Execute the query and get Arrow table
643
560
  try:
644
561
  query_job = conn.query(processed_sql, job_config=final_job_config)
645
- arrow_table = query_job.to_arrow() # Waits for job completion
562
+ arrow_table = query_job.to_arrow()
646
563
  except Exception as e:
647
564
  msg = f"BigQuery Arrow query execution failed: {e!s}"
648
565
  raise SQLSpecError(msg) from e
@@ -650,31 +567,34 @@ class BigQueryDriver(
650
567
 
651
568
  def select_to_parquet(
652
569
  self,
653
- sql: str, # Expects table ID: project.dataset.table
570
+ sql: str,
654
571
  parameters: "Optional[StatementParameterType]" = None,
655
- /,
656
- *filters: StatementFilter,
572
+ *filters: "StatementFilter",
657
573
  destination_uri: "Optional[str]" = None,
658
574
  connection: "Optional[BigQueryConnection]" = None,
659
575
  job_config: "Optional[bigquery.ExtractJobConfig]" = None,
660
576
  **kwargs: Any,
661
577
  ) -> None:
662
- """Exports a BigQuery table to Parquet files in Google Cloud Storage.
663
-
664
- Raises:
665
- NotImplementedError: If the SQL is not a fully qualified table ID or if parameters are provided.
666
- NotFoundError: If the source table is not found.
667
- SQLSpecError: If the Parquet export fails.
668
- """
669
578
  if destination_uri is None:
670
579
  msg = "destination_uri is required"
671
580
  raise SQLSpecError(msg)
672
581
  conn = self._connection(connection)
673
- if "." not in sql or parameters is not None:
674
- msg = "select_to_parquet currently expects a fully qualified table ID (project.dataset.table) as the `sql` argument and no `parameters`."
582
+
583
+ if parameters is not None:
584
+ msg = (
585
+ "select_to_parquet expects a fully qualified table ID (e.g., 'project.dataset.table') "
586
+ "as the `sql` argument and does not support `parameters`."
587
+ )
675
588
  raise NotImplementedError(msg)
676
589
 
677
- source_table_ref = bigquery.TableReference.from_string(sql, default_project=conn.project)
590
+ try:
591
+ source_table_ref = bigquery.TableReference.from_string(sql, default_project=conn.project)
592
+ except ValueError as e:
593
+ msg = (
594
+ "select_to_parquet expects a fully qualified table ID (e.g., 'project.dataset.table') "
595
+ f"as the `sql` argument. Parsing failed for input '{sql}': {e!s}"
596
+ )
597
+ raise NotImplementedError(msg) from e
678
598
 
679
599
  final_extract_config = job_config or bigquery.ExtractJobConfig() # type: ignore[no-untyped-call]
680
600
  final_extract_config.destination_format = bigquery.DestinationFormat.PARQUET
@@ -684,9 +604,8 @@ class BigQueryDriver(
684
604
  source_table_ref,
685
605
  destination_uri,
686
606
  job_config=final_extract_config,
687
- # Location is correctly inferred by the client library
688
607
  )
689
- extract_job.result() # Wait for completion
608
+ extract_job.result()
690
609
 
691
610
  except NotFound:
692
611
  msg = f"Source table not found for Parquet export: {source_table_ref}"
@@ -699,12 +618,4 @@ class BigQueryDriver(
699
618
  raise SQLSpecError(msg)
700
619
 
701
620
  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
621
  return connection or self.connection