-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(bigquery): support queryResultsFormat and compressionCodec in query_and_wait #18027
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -16,6 +16,7 @@ | |||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| from __future__ import absolute_import | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import base64 | ||||||||||||||||||||||||||||||||||||||||||||
| import copy | ||||||||||||||||||||||||||||||||||||||||||||
| import datetime | ||||||||||||||||||||||||||||||||||||||||||||
| import functools | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1912,6 +1913,7 @@ def __init__( | |||||||||||||||||||||||||||||||||||||||||||
| created: Optional[datetime.datetime] = None, | ||||||||||||||||||||||||||||||||||||||||||||
| started: Optional[datetime.datetime] = None, | ||||||||||||||||||||||||||||||||||||||||||||
| ended: Optional[datetime.datetime] = None, | ||||||||||||||||||||||||||||||||||||||||||||
| query_results_format: Optional[str] = None, | ||||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||||
| super(RowIterator, self).__init__( | ||||||||||||||||||||||||||||||||||||||||||||
| client, | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1945,6 +1947,29 @@ def __init__( | |||||||||||||||||||||||||||||||||||||||||||
| self._job_created = created | ||||||||||||||||||||||||||||||||||||||||||||
| self._job_started = started | ||||||||||||||||||||||||||||||||||||||||||||
| self._job_ended = ended | ||||||||||||||||||||||||||||||||||||||||||||
| self._query_results_format = query_results_format | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @property | ||||||||||||||||||||||||||||||||||||||||||||
| def pages(self): | ||||||||||||||||||||||||||||||||||||||||||||
| if self._query_results_format == "ARROW": | ||||||||||||||||||||||||||||||||||||||||||||
| raise ValueError( | ||||||||||||||||||||||||||||||||||||||||||||
| "Cannot iterate over non-arrow results when queryResultsFormat is ARROW. Use to_arrow_iterable() or to_arrow() instead." | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| return super().pages | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def __iter__(self): | ||||||||||||||||||||||||||||||||||||||||||||
| if self._query_results_format == "ARROW": | ||||||||||||||||||||||||||||||||||||||||||||
| raise ValueError( | ||||||||||||||||||||||||||||||||||||||||||||
| "Cannot iterate over non-arrow results when queryResultsFormat is ARROW. Use to_arrow_iterable() or to_arrow() instead." | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| return super().__iter__() | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def __next__(self): | ||||||||||||||||||||||||||||||||||||||||||||
| if self._query_results_format == "ARROW": | ||||||||||||||||||||||||||||||||||||||||||||
| raise ValueError( | ||||||||||||||||||||||||||||||||||||||||||||
| "Cannot iterate over non-arrow results when queryResultsFormat is ARROW. Use to_arrow_iterable() or to_arrow() instead." | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| return super().__next__() | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @property | ||||||||||||||||||||||||||||||||||||||||||||
| def _billing_project(self) -> Optional[str]: | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -2226,6 +2251,12 @@ def to_arrow_iterable( | |||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| .. versionadded:: 2.31.0 | ||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||
| if self._query_results_format == "ARROW": | ||||||||||||||||||||||||||||||||||||||||||||
| return self._download_arrow_from_job_id( | ||||||||||||||||||||||||||||||||||||||||||||
| bqstorage_client=bqstorage_client, | ||||||||||||||||||||||||||||||||||||||||||||
| timeout=timeout, | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| self._maybe_warn_max_results(bqstorage_client) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| bqstorage_download = functools.partial( | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -2251,6 +2282,90 @@ def to_arrow_iterable( | |||||||||||||||||||||||||||||||||||||||||||
| bqstorage_client=bqstorage_client, | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def _download_arrow_from_job_id( | ||||||||||||||||||||||||||||||||||||||||||||
| self, | ||||||||||||||||||||||||||||||||||||||||||||
| bqstorage_client: Optional["bigquery_storage.BigQueryReadClient"] = None, | ||||||||||||||||||||||||||||||||||||||||||||
| timeout: Optional[float] = None, | ||||||||||||||||||||||||||||||||||||||||||||
| ) -> Iterator["pyarrow.RecordBatch"]: | ||||||||||||||||||||||||||||||||||||||||||||
| if pyarrow is None: | ||||||||||||||||||||||||||||||||||||||||||||
| raise ValueError(_NO_PYARROW_ERROR) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| offset = 0 | ||||||||||||||||||||||||||||||||||||||||||||
| pa_schema = None | ||||||||||||||||||||||||||||||||||||||||||||
| total_rows = self.total_rows | ||||||||||||||||||||||||||||||||||||||||||||
| job_complete = False | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| if self._first_page_response: | ||||||||||||||||||||||||||||||||||||||||||||
| first_page = self._first_page_response | ||||||||||||||||||||||||||||||||||||||||||||
| self._first_page_response = None | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| job_complete = bool(first_page.get("jobComplete", False)) | ||||||||||||||||||||||||||||||||||||||||||||
| if job_complete: | ||||||||||||||||||||||||||||||||||||||||||||
| total_rows = int(first_page["totalRows"]) | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2302
to
+2304
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the query is a DDL/DML statement (which does not return rows) or if
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| arrow_schema_json = first_page.get("arrowSchema") | ||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(arrow_schema_json, dict): | ||||||||||||||||||||||||||||||||||||||||||||
| schema_bytes = arrow_schema_json.get("serializedSchema") | ||||||||||||||||||||||||||||||||||||||||||||
| if schema_bytes: | ||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(schema_bytes, str): | ||||||||||||||||||||||||||||||||||||||||||||
| schema_bytes = base64.b64decode(schema_bytes) | ||||||||||||||||||||||||||||||||||||||||||||
| pa_schema = pyarrow.ipc.read_schema( | ||||||||||||||||||||||||||||||||||||||||||||
| pyarrow.py_buffer(schema_bytes) | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| arrow_batch_json = first_page.get("arrowRecordBatch") | ||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(arrow_batch_json, dict) and pa_schema is not None: | ||||||||||||||||||||||||||||||||||||||||||||
| batch_bytes = arrow_batch_json.get("serializedRecordBatch") | ||||||||||||||||||||||||||||||||||||||||||||
| if batch_bytes: | ||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(batch_bytes, str): | ||||||||||||||||||||||||||||||||||||||||||||
| batch_bytes = base64.b64decode(batch_bytes) | ||||||||||||||||||||||||||||||||||||||||||||
| batch = pyarrow.ipc.read_record_batch( | ||||||||||||||||||||||||||||||||||||||||||||
| pyarrow.py_buffer(batch_bytes), | ||||||||||||||||||||||||||||||||||||||||||||
| pa_schema, | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| offset += batch.num_rows | ||||||||||||||||||||||||||||||||||||||||||||
| yield batch | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| if job_complete and offset >= total_rows: | ||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| if bqstorage_client is None: | ||||||||||||||||||||||||||||||||||||||||||||
| if self.client is None: | ||||||||||||||||||||||||||||||||||||||||||||
| raise ValueError("RowIterator client is None.") | ||||||||||||||||||||||||||||||||||||||||||||
| bqstorage_client = self.client._ensure_bqstorage_client() | ||||||||||||||||||||||||||||||||||||||||||||
| if bqstorage_client is None: | ||||||||||||||||||||||||||||||||||||||||||||
| raise ValueError( | ||||||||||||||||||||||||||||||||||||||||||||
| "The google-cloud-bigquery-storage library is required to read Arrow results." | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| project = self._project or (self.client.project if self.client else None) | ||||||||||||||||||||||||||||||||||||||||||||
| location = self._location or (self.client.location if self.client else None) | ||||||||||||||||||||||||||||||||||||||||||||
| stream_name = ( | ||||||||||||||||||||||||||||||||||||||||||||
| f"projects/{project}/locations/{location}/jobs/{self._job_id}/streams/_default" | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2341
to
+2345
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If project = self._project or (self.client.project if self.client else None)
location = self._location or (self.client.location if self.client else None)
if not project:
raise ValueError("Project is required to read Arrow results.")
if not location:
raise ValueError("Location is required to read Arrow results.")
if not self._job_id:
raise ValueError("Job ID is required to read Arrow results.")
stream_name = (
f"projects/{project}/locations/{location}/jobs/{self._job_id}/streams/_default"
)References
|
||||||||||||||||||||||||||||||||||||||||||||
| reader = bqstorage_client.read_rows( | ||||||||||||||||||||||||||||||||||||||||||||
| stream_name, offset=offset, timeout=timeout | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| for response in reader: | ||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||
| response.arrow_schema | ||||||||||||||||||||||||||||||||||||||||||||
| and response.arrow_schema.serialized_schema | ||||||||||||||||||||||||||||||||||||||||||||
| and pa_schema is None | ||||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||||
| pa_schema = pyarrow.ipc.read_schema( | ||||||||||||||||||||||||||||||||||||||||||||
| pyarrow.py_buffer(response.arrow_schema.serialized_schema) | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||
| response.arrow_record_batch | ||||||||||||||||||||||||||||||||||||||||||||
| and response.arrow_record_batch.serialized_record_batch | ||||||||||||||||||||||||||||||||||||||||||||
| and pa_schema is not None | ||||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||||
| batch = pyarrow.ipc.read_record_batch( | ||||||||||||||||||||||||||||||||||||||||||||
| pyarrow.py_buffer(response.arrow_record_batch.serialized_record_batch), | ||||||||||||||||||||||||||||||||||||||||||||
| pa_schema, | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| yield batch | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2358
to
+2367
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If
Suggested change
References
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| # If changing the signature of this method, make sure to apply the same | ||||||||||||||||||||||||||||||||||||||||||||
| # changes to job.QueryJob.to_arrow() | ||||||||||||||||||||||||||||||||||||||||||||
| def to_arrow( | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -2357,7 +2472,7 @@ def to_arrow( | |||||||||||||||||||||||||||||||||||||||||||
| # but mypy cannot infer this correlation. We ignore the union-attr error here. | ||||||||||||||||||||||||||||||||||||||||||||
| bqstorage_client._transport.close() # type: ignore[union-attr] | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| if record_batches and bqstorage_client is not None: | ||||||||||||||||||||||||||||||||||||||||||||
| if record_batches and (bqstorage_client is not None or self._query_results_format == "ARROW"): | ||||||||||||||||||||||||||||||||||||||||||||
| return pyarrow.Table.from_batches(record_batches) | ||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||
| # No records (not record_batches), use schema based on BigQuery schema | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -3036,17 +3151,41 @@ class _EmptyRowIterator(RowIterator): | |||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def __init__( | ||||||||||||||||||||||||||||||||||||||||||||
| self, client=None, api_request=None, path=None, schema=(), *args, **kwargs | ||||||||||||||||||||||||||||||||||||||||||||
| self, client=None, api_request=None, path=None, schema=(), *args, query_results_format: Optional[str] = None, **kwargs | ||||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||||
| super().__init__( | ||||||||||||||||||||||||||||||||||||||||||||
| client=client, | ||||||||||||||||||||||||||||||||||||||||||||
| api_request=api_request, | ||||||||||||||||||||||||||||||||||||||||||||
| path=path, | ||||||||||||||||||||||||||||||||||||||||||||
| schema=schema, | ||||||||||||||||||||||||||||||||||||||||||||
| query_results_format=query_results_format, | ||||||||||||||||||||||||||||||||||||||||||||
| *args, | ||||||||||||||||||||||||||||||||||||||||||||
| **kwargs, | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| self._total_rows = 0 | ||||||||||||||||||||||||||||||||||||||||||||
| self._query_results_format = query_results_format | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| @property | ||||||||||||||||||||||||||||||||||||||||||||
| def pages(self): | ||||||||||||||||||||||||||||||||||||||||||||
| if self._query_results_format == "ARROW": | ||||||||||||||||||||||||||||||||||||||||||||
| raise ValueError( | ||||||||||||||||||||||||||||||||||||||||||||
| "Cannot iterate over non-arrow results when queryResultsFormat is ARROW. Use to_arrow_iterable() or to_arrow() instead." | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| return super().pages | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def __iter__(self): | ||||||||||||||||||||||||||||||||||||||||||||
| if self._query_results_format == "ARROW": | ||||||||||||||||||||||||||||||||||||||||||||
| raise ValueError( | ||||||||||||||||||||||||||||||||||||||||||||
| "Cannot iterate over non-arrow results when queryResultsFormat is ARROW. Use to_arrow_iterable() or to_arrow() instead." | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| return iter(()) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def __next__(self): | ||||||||||||||||||||||||||||||||||||||||||||
| if self._query_results_format == "ARROW": | ||||||||||||||||||||||||||||||||||||||||||||
| raise ValueError( | ||||||||||||||||||||||||||||||||||||||||||||
| "Cannot iterate over non-arrow results when queryResultsFormat is ARROW. Use to_arrow_iterable() or to_arrow() instead." | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| raise StopIteration | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def to_arrow( | ||||||||||||||||||||||||||||||||||||||||||||
| self, | ||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -3219,11 +3358,10 @@ def to_arrow_iterable( | |||||||||||||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||||||||||||||
| An iterator yielding a single empty :class:`~pyarrow.RecordBatch`. | ||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||
| if pyarrow is None: | ||||||||||||||||||||||||||||||||||||||||||||
| raise ValueError(_NO_PYARROW_ERROR) | ||||||||||||||||||||||||||||||||||||||||||||
| return iter((pyarrow.record_batch([]),)) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| def __iter__(self): | ||||||||||||||||||||||||||||||||||||||||||||
| return iter(()) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| class PartitionRange(object): | ||||||||||||||||||||||||||||||||||||||||||||
| """Definition of the ranges for range partitioning. | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
formatOptionskey is not included in thekeys_allowlistfor_supported_by_jobs_query. Whencompression_codecis specified,formatOptionsis added to the request body. Since it is missing from the allowlist, any query utilizing a compression codec will unnecessarily fallback to the slowerjobs.insertpath instead of using the optimizedjobs.queryAPI. AddingformatOptionsto the allowlist ensures the fast path is preserved.References