Skip to content

feat(bigquery): expose StatementType and query execution stats on TableResult - #14145

Open
keshavdandeva wants to merge 2 commits into
mainfrom
bq-expose-statement-type
Open

feat(bigquery): expose StatementType and query execution stats on TableResult#14145
keshavdandeva wants to merge 2 commits into
mainfrom
bq-expose-statement-type

Conversation

@keshavdandeva

Copy link
Copy Markdown
Contributor

b/549680449

This PR exposes StatementType and jobless query execution metrics on TableResult by plumbing them from QueryResponse and JobStatistics.QueryStatistics.

This enables downstream consumers (such as the BigQuery JDBC driver) to inspect query statement types and execution statistics directly from TableResult without needing to issue secondary jobs or dry-run queries.

Changes

  • Dependency Update: Bumped google-api-services-bigquery to v2-rev20260731-2.0.0 in google-cloud-jar-parent/pom.xml and java-bigquery/pom.xml.
  • TableResult: Added getters, builder setters, pagination propagation (getNextPage()), toString(), hashCode(), and equals() for:
    • getStatementType() (StatementType)
    • getTotalBytesBilled() (Long)
    • getTotalBytesProcessed() (Long)
    • getTotalSlotMs() (Long)
    • getNumDmlAffectedRows() (Long)
  • BigQueryImpl: Plumbed these fields from QueryResponse when building TableResult in queryRpc().
  • Job: Plumbed StatementType and query metrics from QueryStatistics when creating TableResult in getQueryResults().
  • Tests: Added unit tests in TableResultTest and BigQueryImplTest verifying field retrieval, pagination propagation, builder modifications, and serialization.

@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 updates the BigQuery dependency version and propagates query execution statistics—including statement type, total bytes billed, total bytes processed, total slot milliseconds, and affected DML rows—from query results and job statistics into the TableResult object. The review feedback suggests wrapping the StatementType.valueOf() conversion in a try-catch block to gracefully handle any unrecognized statement types returned by the BigQuery service and prevent runtime exceptions.

@keshavdandeva
keshavdandeva marked this pull request as ready for review August 20, 2026 14:52
@keshavdandeva
keshavdandeva requested review from a team as code owners August 20, 2026 14:52
@lqiu96

lqiu96 commented Aug 20, 2026

Copy link
Copy Markdown
Member

@keshavdandeva FYI, I'm going to upgrade the bigquery apiary version in #14149

Comment on lines +419 to +422
QueryStatistics stats =
job.getStatistics() instanceof QueryStatistics
? (QueryStatistics) job.getStatistics()
: null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

From what I see in the JobStatistic, there are quite a few of impl classes:

  • CopyStatistics
  • JobStatistics
  • Load
  • Query
  • ...

Is there any concern if we just return the whole JobStatistic impl class back? Downstream user (e.g. bigquery-jdbc) can contain the logic to parse to QueryStatistic and pull the relevant fields. This way we don't need to maintain the mapping of select fields for QueryStatistic.

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.

Yeah so, I looked into using JobStatistics / QueryStatistics, and wanted to share a couple of thoughts/questions to get your take:

  • For fast-path/jobless queries, the backend doesn't return a Job or a JobStatistics payload; it only returns these specific execution fields directly on QueryResponse. If we populate a JobStatistics.QueryStatistics object from QueryResponse, most of its fields (queryPlan, timeline, referencedTables, billingTier, etc.) would remain empty/null. Idk if that would be confusing or misleading for callers expecting a full Job statistics object?
  • Since TableResult is only ever produced by query executions (not Copy/Load/Extract), and already exposes direct metadata getters like getSchema(), getQueryId(), and getJobCreationReason(), adding direct getters seemed to align with the existing pattern while keeping it strongly typed and avoiding downstream downcasting.

What do you think? If you feel wrapping these in QueryStatistics on TableResult is still the better direction for the SDK, I'm more than happy to update it

@lqiu96 lqiu96 Aug 20, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For fast-path/jobless queries, the backend doesn't return a Job or a JobStatistics payload; it only returns these specific execution fields directly on QueryResponse. If we populate a JobStatistics.QueryStatistics object from QueryResponse, most of its fields (queryPlan, timeline, referencedTables, billingTier, etc.) would remain empty/null. Idk if that would be confusing or misleading for callers expecting a full Job statistics object?

Can you show me what you mean by specific execution fields directly on QueryResponse? I don't think I see this logic in the PR and I'm not sure I'm wrapping my head around this part.

I also don't think we want to manually populate JobStatistics object. We should try to only propagate server response back to user, no need for us to do any special transformation or logic (just convert to and from PBs and return null if the server gives us null). Do we have documentation that tells users that they would always be expecting a full Job statistics object even for fast path?

Since TableResult is only ever produced by query executions (not Copy/Load/Extract), and already exposes direct metadata getters like getSchema(), getQueryId(), and getJobCreationReason(), adding direct getters seemed to align with the existing pattern while keeping it strongly typed and avoiding downstream downcasting.

IIUC, TableResult is returned from the Query RPC. Anyone can just call the CreateJob RPC with a copy/load/extract operation. Unless there is strict requirement, I think a generic Job interface should return a generic JobStatistic result (responsibility would be on downstream user to figure out the type of jobstatistic and pull out the relevant info)

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.

Yeah, that makes sense, and I'll update TableResult to return JobStatistics

Just to answer your questions on why I initially structured it with individual getters:

  • In BigQueryImpl.java (lines 2099–2106), results is com.google.api.services.bigquery.model.QueryResponse returned by queryRpc. The generated model has these fields directly at the top level (results.getStatementType(), results.getTotalBytesBilled(), etc.) rather than inside a nested statistics object.
  • Because QueryResponse had them as top-level getters, I had initially mapped them 1-to-1 directly onto TableResult (similar to getSchema() and getQueryId()).
  • You're right that there is no documentation requiring full statistics on the fast path, so populating JobStatistics.QueryStatistics with the available metrics from QueryResponse works well.

@keshavdandeva
keshavdandeva requested a review from lqiu96 August 20, 2026 22:17
@keshavdandeva
keshavdandeva force-pushed the bq-expose-statement-type branch from b333559 to 94fbc07 Compare August 20, 2026 22:33
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