Skip to content

feat(bigquery): support queryResultsFormat and compressionCodec in query_and_wait - #18027

Merged
chalmerlowe merged 14 commits into
googleapis:mainfrom
alextolpin:arrow_iterable
Aug 13, 2026
Merged

feat(bigquery): support queryResultsFormat and compressionCodec in query_and_wait#18027
chalmerlowe merged 14 commits into
googleapis:mainfrom
alextolpin:arrow_iterable

Conversation

@alextolpin

Copy link
Copy Markdown
Contributor

Summary of Changes

Adds support for fetching query results in Apache Arrow format directly via query_and_wait() using queryResultsFormat="ARROW" and optional buffer compression (e.g., compression_codec="LZ4_FRAME").

  1. query_and_wait & _job_helpers Enhancements:

    • Added query_results_format and compression_codec parameters (with [Beta] docstring annotations) to client.query_and_wait(), client._query_and_wait_bigframes(), and _job_helpers.query_and_wait().
    • Included queryResultsFormat in _job_helpers.keys_allowlist and populated formatOptions.arrowSerializationOptions.bufferCompression in jobs.query REST API request payloads.
    • Refactored _wait_or_cancel() to accept and preserve query_results_format on returned RowIterator instances.
  2. Arrow Serialization & Direct Job Stream Reading:

    • Added RowIterator._download_arrow_from_job_id() to stream Arrow record batches directly from projects/{project}/locations/{location}/jobs/{job_id}/streams/_default via the BigQuery Storage Read API.
    • Added logic to decode base64 inline arrowSchema and arrowRecordBatch from the initial jobs.query REST response (_first_page_response), calculate the starting row offset, and resume read_rows(stream_name, offset=offset).
    • Added an optimization to skip calling read_rows() or initializing BigQueryReadClient if jobComplete = True and all rows were returned within the first page response.
  3. Safety & Enforcement:

    • Overrode pages, __iter__, and __next__ on RowIterator and _EmptyRowIterator to raise a descriptive ValueError if non-Arrow iteration is attempted when queryResultsFormat="ARROW".
  4. Testing:

    • Added comprehensive unit test suite in 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:

  • Make sure to open an issue as a bug/issue before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea
  • Ensure the tests and linter pass
  • Code coverage does not decrease (if any source code was changed)
  • Appropriate docs were updated (if necessary)

@alextolpin
alextolpin requested review from a team as code owners August 7, 2026 14:40
@alextolpin
alextolpin requested review from sycai and removed request for a team August 7, 2026 14:40

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread packages/google-cloud-bigquery/google/cloud/bigquery/table.py
Comment thread packages/google-cloud-bigquery/google/cloud/bigquery/table.py Outdated
Comment on lines +2341 to +2345
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"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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
  1. 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.

@parthea

parthea commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

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

Make sure to open an issue as a [bug/issue](https://github.com/googleapis/google-cloud-python/issues) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea

@alextolpin

Copy link
Copy Markdown
Contributor Author

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

Make sure to open an issue as a [bug/issue](https://github.com/googleapis/google-cloud-python/issues) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea

Created #18047

@daniel-sanche daniel-sanche left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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").

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does [Beta] represent here? Does this mean the backend API isn't stable?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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."

Comment thread packages/google-cloud-bigquery/google/cloud/bigquery/table.py
Comment thread packages/google-cloud-bigquery/google/cloud/bigquery/client.py Outdated
Comment thread packages/google-cloud-bigquery/google/cloud/bigquery/table.py Outdated
Comment thread packages/google-cloud-bigquery/google/cloud/bigquery/table.py
Comment thread packages/google-cloud-bigquery/google/cloud/bigquery/table.py Outdated
Comment thread packages/google-cloud-bigquery/google/cloud/bigquery/table.py Outdated
Comment thread packages/google-cloud-bigquery/google/cloud/bigquery/table.py Outdated
@tswast tswast added the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Aug 12, 2026
@yoshi-kokoro yoshi-kokoro removed the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Aug 12, 2026
@tswast tswast added the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Aug 12, 2026
@yoshi-kokoro yoshi-kokoro removed the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Aug 12, 2026

@sycai sycai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also address the comments left by the AI code reviewer? Thanks!

Comment thread packages/google-cloud-bigquery/google/cloud/bigquery/client.py
@tswast tswast added the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Aug 12, 2026
@yoshi-kokoro yoshi-kokoro removed the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Aug 12, 2026
@alextolpin

Copy link
Copy Markdown
Contributor Author

Could you also address the comments left by the AI code reviewer? Thanks!

done!

@tswast tswast added the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Aug 12, 2026
@yoshi-kokoro yoshi-kokoro removed the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Aug 12, 2026
from google.cloud.bigquery.table import RowIterator, _EmptyRowIterator


class TestQueryResultsFormatOption1(unittest.TestCase):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@sycai sycai added the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Aug 13, 2026
@yoshi-kokoro yoshi-kokoro removed the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Aug 13, 2026
@chalmerlowe
chalmerlowe merged commit d172408 into googleapis:main Aug 13, 2026
46 of 49 checks passed
@alextolpin
alextolpin deleted the arrow_iterable branch August 13, 2026 17:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants