Skip to content

feat(bigquery): read Arrow results from explicit destinations - #18654

Open
ohmayr wants to merge 1 commit into
feat/bigquery-arrow-shared-bqstorage-clientfrom
feat/bigquery-arrow-explicit-destination
Open

ohmayr wants to merge 1 commit into
feat/bigquery-arrow-shared-bqstorage-clientfrom
feat/bigquery-arrow-explicit-destination

Conversation

@ohmayr

@ohmayr ohmayr commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Reads Arrow results for queries with an explicit destination table. The job's default stream rejects those jobs, so this reads the table through a read session instead.

  • Opens a single-stream Arrow read session on the destination table, with the same serialization options.
  • Marks the RowIterator from query_and_wait() when the job config sets destination.
  • Adds unit tests and a system test for explicit destinations.

@ohmayr
ohmayr requested review from a team as code owners October 13, 2026 09:05
@ohmayr
ohmayr requested review from tswast and removed request for a team October 13, 2026 09:05

@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 adds support for reading Arrow results from queries with explicit destination tables. It introduces helper functions _open_job_stream and _open_destination_stream to handle streaming from either the default job stream or the destination table. The RowIterator and helper functions are updated to track whether an explicit destination was used. Feedback on the changes includes: 1) Handling cases where row_iterator._billing_project is None in _open_destination_stream to prevent API failures due to invalid project formatting. 2) Raising a clear ValueError when _explicit_destination is true but the destination table is not set, rather than falling back to the default job stream which would fail with a cryptic API error.

Comment on lines +211 to +224
session = bqstorage_client.create_read_session(
parent=f"projects/{row_iterator._billing_project}",
read_session=bigquery_storage.types.stream.ReadSession(
table=row_iterator._table.to_bqstorage(),
data_format=bigquery_storage.types.stream.DataFormat.ARROW,
read_options=bigquery_storage.types.stream.ReadSession.TableReadOptions(
arrow_serialization_options=_read_rows_options(
row_iterator._arrow_serialization_options
)
),
),
max_stream_count=1,
timeout=timeout,
)

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 row_iterator.client is None (which is possible if bqstorage_client is passed directly to download_arrow_from_job), row_iterator._billing_project will be None. This would cause parent to be formatted as projects/None, leading to an API failure. Falling back to row_iterator._project ensures that a valid project is always used.

Suggested change
session = bqstorage_client.create_read_session(
parent=f"projects/{row_iterator._billing_project}",
read_session=bigquery_storage.types.stream.ReadSession(
table=row_iterator._table.to_bqstorage(),
data_format=bigquery_storage.types.stream.DataFormat.ARROW,
read_options=bigquery_storage.types.stream.ReadSession.TableReadOptions(
arrow_serialization_options=_read_rows_options(
row_iterator._arrow_serialization_options
)
),
),
max_stream_count=1,
timeout=timeout,
)
billing_project = row_iterator._billing_project or row_iterator._project
if not billing_project:
raise ValueError("Billing project could not be determined.")
session = bqstorage_client.create_read_session(
parent=f"projects/{billing_project}",
read_session=bigquery_storage.types.stream.ReadSession(
table=row_iterator._table.to_bqstorage(),
data_format=bigquery_storage.types.stream.DataFormat.ARROW,
read_options=bigquery_storage.types.stream.ReadSession.TableReadOptions(
arrow_serialization_options=_read_rows_options(
row_iterator._arrow_serialization_options
)
),
),
max_stream_count=1,
timeout=timeout,
)

Comment on lines +302 to +307
if row_iterator._explicit_destination and row_iterator._table is not None:
reader, pa_schema = _open_destination_stream(
row_iterator, bqstorage_client, timeout, pa_schema
)
else:
reader = _open_job_stream(row_iterator, bqstorage_client, offset, timeout)

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 row_iterator._explicit_destination is True but row_iterator._table is None, the code currently falls back to _open_job_stream. However, the default job stream is guaranteed to reject queries with explicit destination tables, resulting in a cryptic API error. Raising a clear ValueError upfront is much more helpful for debugging.

    if row_iterator._explicit_destination:
        if row_iterator._table is None:
            raise ValueError(
                "The row iterator is marked as having an explicit destination, "
                "but the destination table is not set."
            )
        reader, pa_schema = _open_destination_stream(
            row_iterator, bqstorage_client, timeout, pa_schema
        )
    else:
        reader = _open_job_stream(row_iterator, bqstorage_client, offset, timeout)

@ohmayr
ohmayr added this pull request to stack #18648 October 13, 2026 09:16
@ohmayr
ohmayr force-pushed the feat/bigquery-arrow-explicit-destination branch from b39f7e9 to 31be909 Compare October 13, 2026 10:02
@ohmayr
ohmayr force-pushed the feat/bigquery-arrow-explicit-destination branch 2 times, most recently from 6841e9f to 11c0874 Compare October 15, 2026 03:22
The job's default read stream rejects queries whose job config sets a
destination table ("This API does not support explicitly-specified
destination tables"), so query_and_wait() with ARROW results failed for
those queries.

When the query wrote to an explicit destination, read the table through
create_read_session() instead, with data_format=ARROW and
max_stream_count=1 so rows stay in order. The session's read options
carry the same serialization options as jobs.query. An empty table has
no streams and yields nothing. WRITE_APPEND destinations are already
rejected by validate_query().
@ohmayr
ohmayr force-pushed the feat/bigquery-arrow-explicit-destination branch from 11c0874 to e1965f9 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.

1 participant