Skip to content

fix(bigquery-jdbc): harden Arrow result set for empty batches, unknown row counts and lazy schema - #14941

Open
keshavdandeva wants to merge 1 commit into
jdbc/arrow-support-featurefrom
fix/jdbc-arrow-resultset-hardening
Open

keshavdandeva wants to merge 1 commit into
jdbc/arrow-support-featurefrom
fix/jdbc-arrow-resultset-hardening

Conversation

@keshavdandeva

Copy link
Copy Markdown
Contributor

b/582676539

Hardens BigQueryArrowResultSet and the Read API producer ahead of the Arrow job-stream work (b/581544197). Targets the jdbc/arrow-support-feature branch.

Changes

  • Producer skips Read API responses without rows and fails fast when a response reports rows but carries no record batch.
  • Result set skips zero-row batches instead of returning a phantom row (previously IndexOutOfBoundsException and the following row was lost).
  • Supports an unknown total row count: iterates until the end marker; isLast()/isBeforeFirst() look ahead without skipping rows.
  • Accepts the Arrow schema from the first stream message (schema-only messages are skipped).
  • Fixes isLast() never returning true when Statement.setMaxRows exceeds the result size.
  • isBeforeFirst()/isLast() return false for an empty result, per the JDBC spec.
  • Getters raise SQLException instead of IndexOutOfBoundsException or a stale value when the cursor is not on a row.
  • Record batches are deserialized from the ByteString stream instead of being copied to a heap byte[].

Testing

  • New unit tests in BigQueryArrowResultSetTest and BigQueryStatementTest; testRowCount now exercises a real row limit.

…n row counts and lazy schema

- Producer skips Read API responses without rows and fails fast when a
  response reports rows but carries no record batch.
- Result set skips zero-row batches instead of returning a phantom row.
- Support an unknown total row count: iterate until the end marker;
  isLast()/isBeforeFirst() look ahead without skipping rows.
- Accept the Arrow schema from the first stream message.
- Fix isLast() when Statement.setMaxRows exceeds the result size.
- isBeforeFirst()/isLast() return false for an empty result (JDBC spec).
- Getters raise SQLException instead of IndexOutOfBoundsException or a
  stale value when the cursor is not on a row.
- Deserialize record batches without copying them to a heap byte array.

Bug: b/582676539
@keshavdandeva
keshavdandeva requested review from a team as code owners November 6, 2026 16:28

@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 refactors the BigQuery JDBC driver's Arrow result set processing to support streams with an unknown total row count, skip empty batches, and dynamically forward schemas. It also optimizes memory usage during Arrow batch deserialization by reading directly from the ByteString input stream instead of copying it to a heap byte array. Feedback is provided to wrap the newly introduced InputStream and ReadableByteChannel in a try-with-resources block to prevent potential static analysis warnings regarding unclosed resources.

// set the resultset
long totalRows = (getMaxRows() > 0) ? getMaxRows() : results.getTotalRows();
long totalRows =
(getMaxRows() > 0)

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'd move to helper method; Same applies to JsonResultSet

(maybe worth moving to a standalone PR & merge to main directly)

ArrowRecordBatch currentBatch = response.getArrowRecordBatch();
arrowBatchWrapperBlockingQueue.put(
BigQueryArrowBatchWrapper.of(currentBatch));
enqueueReadRowsResponse(response, arrowBatchWrapperBlockingQueue);

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 is the impact of this change?

/** This class acts as a facade layer and wraps Arrow's VectorSchemaRoot & JsonStringArrayList */
class BigQueryArrowBatchWrapper {
/** Row count used when the producer did not report one. */
static final long UNKNOWN_ROW_COUNT = -1;

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 is a scenario for unknown # of rows?

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