feat(bigquery): support queryResultsFormat and compressionCodec in query_and_wait - #18027
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces support for the Arrow query results format and compression codecs in query_and_wait. It adds the query_results_format and compression_codec parameters, prevents standard iteration on RowIterator when the format is Arrow, and implements _download_arrow_from_job_id to retrieve Arrow results via the BigQuery Storage Read API. The review feedback highlights several important improvements: adding formatOptions to the _supported_by_jobs_query allowlist to prevent unnecessary fallbacks to jobs.insert, raising an error instead of silently skipping record batches when the schema is missing, safely retrieving totalRows to avoid potential KeyErrors, and validating key identifiers before constructing the stream name to prevent cryptic API errors.
| 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" | ||
| ) |
There was a problem hiding this comment.
If project, location, or self._job_id is None, the constructed stream_name will contain literal "None" values (e.g., projects/None/locations/None/...), leading to cryptic API errors. Adding explicit validation checks ensures a clear, local error is raised instead.
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
- When a function receives parameters of an unsupported type, it should raise an error instead of silently returning empty values to ensure fail-fast behavior.
|
Hi @alextolpin, Thanks for opening a PR! I'm going to switch this PR to draft since presubmits are failing but please feel free to mark it ready for review once tests are green. Please make sure to file an issue here as per the checklist |
269ed8e to
c5bdd3b
Compare
Created #18047 |
daniel-sanche
left a comment
There was a problem hiding this comment.
Added some comments, and it looks like Gemini caught some potential data loss issues too
| query_results_format (Optional[str]): | ||
| [Beta] The format for query results (e.g. "ARROW"). | ||
| compression_codec (Optional[str]): | ||
| [Beta] Compression codec for Arrow serialization (e.g. "LZ4_FRAME"). |
There was a problem hiding this comment.
What does [Beta] represent here? Does this mean the backend API isn't stable?
There was a problem hiding this comment.
It means that this feature will be in public preview. Generally public previews have the following note in public documentation:
"This feature is subject to the "Pre-GA Offerings Terms" in the General Service Terms section of the Service Specific Terms. Pre-GA features are available "as is" and might have limited support. For more information, see the launch stage descriptions."
71f9a63 to
ff0a978
Compare
3457c95 to
86e972e
Compare
sycai
left a comment
There was a problem hiding this comment.
Could you also address the comments left by the AI code reviewer? Thanks!
done! |
86e972e to
3ddfd28
Compare
| from google.cloud.bigquery.table import RowIterator, _EmptyRowIterator | ||
|
|
||
|
|
||
| class TestQueryResultsFormatOption1(unittest.TestCase): |
There was a problem hiding this comment.
Nit: We prefer the classless pytest-style tests, but acknowledged that the client has a mix of both, so not really worth blocking this PR for that.
Summary of Changes
Adds support for fetching query results in Apache Arrow format directly via
query_and_wait()usingqueryResultsFormat="ARROW"and optional buffer compression (e.g.,compression_codec="LZ4_FRAME").query_and_wait&_job_helpersEnhancements:query_results_formatandcompression_codecparameters (with[Beta]docstring annotations) toclient.query_and_wait(),client._query_and_wait_bigframes(), and_job_helpers.query_and_wait().queryResultsFormatin_job_helpers.keys_allowlistand populatedformatOptions.arrowSerializationOptions.bufferCompressioninjobs.queryREST API request payloads._wait_or_cancel()to accept and preservequery_results_formaton returnedRowIteratorinstances.Arrow Serialization & Direct Job Stream Reading:
RowIterator._download_arrow_from_job_id()to stream Arrow record batches directly fromprojects/{project}/locations/{location}/jobs/{job_id}/streams/_defaultvia the BigQuery Storage Read API.arrowSchemaandarrowRecordBatchfrom the initialjobs.queryREST response (_first_page_response), calculate the starting rowoffset, and resumeread_rows(stream_name, offset=offset).read_rows()or initializingBigQueryReadClientifjobComplete = Trueand all rows were returned within the first page response.Safety & Enforcement:
pages,__iter__, and__next__onRowIteratorand_EmptyRowIteratorto raise a descriptiveValueErrorif non-Arrow iteration is attempted whenqueryResultsFormat="ARROW".Testing:
tests/unit/test_query_results_format_arrow.py(16 passing tests) covering request body formatting, parameter propagation, base64 payload decoding, offset calculation, stream URI construction, and Storage client skipping when all rows are present in the first page.Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: