Skip to content

fix(bigquery): send the same Arrow options on jobs.query and ReadRows - #18646

Open
ohmayr wants to merge 2 commits into
feat/bigquery-arrow-validate-queryfrom
fix/bigquery-arrow-serialization-options
Open

ohmayr wants to merge 2 commits into
feat/bigquery-arrow-validate-queryfrom
fix/bigquery-arrow-serialization-options

Conversation

@ohmayr

@ohmayr ohmayr commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Sends the same arrowSerializationOptions on jobs.query and ReadRows, so the first page and the stream use the same Arrow encoding. Previously compression_codec was sent in formatOptions, which jobs.query ignores, and never reached ReadRows.

@ohmayr
ohmayr requested review from a team as code owners October 13, 2026 08:15
@ohmayr
ohmayr requested review from sindhuvy and removed request for a team October 13, 2026 08:15

@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 passing arrowSerializationOptions (such as compression codecs) from jobs.query down to ReadRows in the BigQuery Storage API when using the ARROW format. This involves updating RowIterator to store these options, modifying query_and_wait to set them, and updating download_arrow_from_job to pass them to read_rows. Feedback was provided to make _read_rows_options more robust against potential KeyErrors by using .get() with default fallbacks instead of direct dictionary lookups.

Comment on lines +118 to +125
def _read_rows_options(options: Optional[Dict[str, str]]) -> Dict[str, str]:
"""Convert ``jobs.query`` ``arrowSerializationOptions`` to the
``ArrowSerializationOptions`` fields that ``ReadRows`` takes."""
options = options or serialization_options(None)
return {
"buffer_compression": options["bufferCompression"],
"picos_timestamp_precision": options["picosTimestampPrecision"],
}

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

Using direct dictionary lookups like options["bufferCompression"] can raise a KeyError if the dictionary is passed with missing keys (for example, if a user instantiates RowIterator with custom options). It is safer and more robust to use .get() with fallback defaults, which also simplifies the fallback logic when options is None.

Suggested change
def _read_rows_options(options: Optional[Dict[str, str]]) -> Dict[str, str]:
"""Convert ``jobs.query`` ``arrowSerializationOptions`` to the
``ArrowSerializationOptions`` fields that ``ReadRows`` takes."""
options = options or serialization_options(None)
return {
"buffer_compression": options["bufferCompression"],
"picos_timestamp_precision": options["picosTimestampPrecision"],
}
def _read_rows_options(options: Optional[Dict[str, str]]) -> Dict[str, str]:
"""Convert jobs.query arrowSerializationOptions to the
ArrowSerializationOptions fields that ReadRows takes."""
options = options or {}
return {
"buffer_compression": options.get("bufferCompression", "COMPRESSION_UNSPECIFIED"),
"picos_timestamp_precision": options.get("picosTimestampPrecision", "TIMESTAMP_PRECISION_MICROS"),
}

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.

These options always come from serialization_options(), which sets both keys, so a missing key would be a bug I'd want to see as a KeyError rather than paper over with defaults. RowIterator's constructor is internal to query_and_wait() here.

@ohmayr
ohmayr added this pull request to stack #18648 October 13, 2026 08:17
@ohmayr
ohmayr force-pushed the fix/bigquery-arrow-serialization-options branch from a8b7900 to 6543176 Compare October 13, 2026 10:02
@ohmayr
ohmayr requested a review from tswast October 13, 2026 20:40
@ohmayr
ohmayr force-pushed the fix/bigquery-arrow-serialization-options branch 2 times, most recently from 8e73bb3 to 63d04b5 Compare October 15, 2026 03:22
return {
"bufferCompression": getattr(compression_codec, "value", compression_codec)
or "COMPRESSION_UNSPECIFIED",
"picosTimestampPrecision": "TIMESTAMP_PRECISION_MICROS",

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.

I assume it'll be a separate project to plumb through all of the TIMESTAMP_PRECISION_PICOS features at some point in the future?

query_and_wait() sent compression_codec inside formatOptions, which
only has useInt64Timestamp and timestampOutputFormat, so jobs.query
ignored it, and ReadRows never got it at all. The first page and the
stream each used their own endpoint's defaults, so one result could
mix encodings.

Build one arrowSerializationOptions value for ARROW queries and send
it at the top level of the jobs.query request, where it also joins
the jobs.query allowlist. Options the caller didn't set are sent with
their defaults (COMPRESSION_UNSPECIFIED, TIMESTAMP_PRECISION_MICROS),
so the encoding never depends on server defaults. RowIterator keeps
the value on both the jobs.query and the waited-job paths and sends
it on read_rows(). jobs.insert never gets these fields, since they
come from query_and_wait() arguments rather than QueryJobConfig.

The mypy session now installs the in-repo google-cloud-bigquery-storage
too, so the new read_rows() argument is type-checked.
google-cloud-bigquery-storage 2.43.0 has the read_rows()
arrow_serialization_options argument, so mypy can type-check against
the released package instead of the in-repo copy.
@ohmayr
ohmayr force-pushed the fix/bigquery-arrow-serialization-options branch from 63d04b5 to 48d916f Compare October 16, 2026 01:16

This branch has not been deployed

No deployments
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.

2 participants