diff --git a/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryImpl.java b/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryImpl.java index 2ad09c33d7cb..96bb33e43bc3 100644 --- a/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryImpl.java +++ b/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryImpl.java @@ -2095,6 +2095,8 @@ public com.google.api.services.bigquery.model.QueryResponse call() return job; } + JobStatistics jobStatistics = JobStatistics.QueryStatistics.fromPb(results, schema); + if (results.getPageToken() != null) { JobId jobId = JobId.fromPb(results.getJobReference()); String cursor = results.getPageToken(); @@ -2114,6 +2116,7 @@ public com.google.api.services.bigquery.model.QueryResponse call() .setQueryId(results.getQueryId()) .setJobCreationReason(JobCreationReason.fromPb(results.getJobCreationReason())) .setRowsInPage(results.getRows() != null ? (long) results.getRows().size() : 0L) + .setJobStatistics(jobStatistics) .build(); } // only 1 page of result @@ -2134,6 +2137,7 @@ public com.google.api.services.bigquery.model.QueryResponse call() .setQueryId(results.getQueryId()) .setJobCreationReason(JobCreationReason.fromPb(results.getJobCreationReason())) .setRowsInPage(results.getRows() != null ? (long) results.getRows().size() : 0L) + .setJobStatistics(jobStatistics) .build(); } diff --git a/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Job.java b/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Job.java index 43832f3410b8..fa411ca88ad7 100644 --- a/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Job.java +++ b/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/Job.java @@ -414,6 +414,8 @@ public TableResult getQueryResults(QueryResultsOption... options) : ImmutableList.copyOf(job.getStatus().getExecutionErrors())); } + JobStatistics stats = job.getStatistics(); + // If there are no rows in the result, this may have been a DDL query. // Listing table data might fail, such as with CREATE VIEW queries. // Avoid a tabledata.list API request by returning an empty TableResult. @@ -425,6 +427,7 @@ public TableResult getQueryResults(QueryResultsOption... options) .setTotalRows(0L) .setPageNoSchema(new PageImpl(null, "", null)) .setRowsInPage(0L) + .setJobStatistics(stats) .build(); return emptyTableResult; } @@ -436,7 +439,8 @@ public TableResult getQueryResults(QueryResultsOption... options) TableResult tableResult = bigquery.listTableData( table, response.getSchema(), listOptions.toArray(new TableDataListOption[0])); - TableResult tableResultWithJobId = tableResult.toBuilder().setJobId(job.getJobId()).build(); + TableResult tableResultWithJobId = + tableResult.toBuilder().setJobId(job.getJobId()).setJobStatistics(stats).build(); return tableResultWithJobId; } finally { if (getQueryResults != null) { diff --git a/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/JobStatistics.java b/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/JobStatistics.java index c78378ab53f7..77153be12ae5 100644 --- a/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/JobStatistics.java +++ b/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/JobStatistics.java @@ -24,6 +24,7 @@ import com.google.api.services.bigquery.model.JobStatistics4; import com.google.api.services.bigquery.model.JobStatistics5; import com.google.api.services.bigquery.model.QueryParameter; +import com.google.api.services.bigquery.model.QueryResponse; import com.google.auto.value.AutoValue; import com.google.cloud.StringEnumType; import com.google.cloud.StringEnumValue; @@ -1073,6 +1074,33 @@ static QueryStatistics fromPb( com.google.api.services.bigquery.model.JobStatistics statisticPb) { return new Builder(statisticPb).build(); } + + /** Creates a {@code QueryStatistics} object from a {@link QueryResponse} and schema. */ + static QueryStatistics fromPb(QueryResponse queryResponse, Schema schema) { + if (queryResponse == null) { + return null; + } + Builder builder = newBuilder(); + if (queryResponse.getStatementType() != null) { + builder.setStatementType(queryResponse.getStatementType()); + } + if (queryResponse.getTotalBytesBilled() != null) { + builder.setTotalBytesBilled(queryResponse.getTotalBytesBilled()); + } + if (queryResponse.getTotalBytesProcessed() != null) { + builder.setTotalBytesProcessed(queryResponse.getTotalBytesProcessed()); + } + if (queryResponse.getTotalSlotMs() != null) { + builder.setTotalSlotMs(queryResponse.getTotalSlotMs()); + } + if (queryResponse.getNumDmlAffectedRows() != null) { + builder.setNumDmlAffectedRows(queryResponse.getNumDmlAffectedRows()); + } + if (schema != null) { + builder.setSchema(schema); + } + return builder.build(); + } } /** A Google BigQuery Script statistics. */ diff --git a/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/TableResult.java b/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/TableResult.java index a791628c5b16..a142cc4764d6 100644 --- a/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/TableResult.java +++ b/java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/TableResult.java @@ -51,6 +51,8 @@ public abstract static class Builder { abstract TableResult.Builder setRowsInPage(Long rowsInPage); + public abstract TableResult.Builder setJobStatistics(JobStatistics jobStatistics); + /** Creates a @code TableResult} object. */ public abstract TableResult build(); } @@ -87,6 +89,10 @@ public static Builder newBuilder() { @Nullable public abstract Long getRowsInPage(); + /** Returns the statistics of the job/query that produced these results, if available. */ + @Nullable + public abstract JobStatistics getJobStatistics(); + @Override public boolean hasNextPage() { return getPageNoSchema().hasNextPage(); @@ -109,6 +115,7 @@ public TableResult getNextPage() { .setQueryId(getQueryId()) .setJobCreationReason(getJobCreationReason()) .setRowsInPage(nextRows) + .setJobStatistics(getJobStatistics()) .build(); } return null; @@ -147,13 +154,19 @@ public String toString() { .add("cursor", getNextPageToken()) .add("queryId", getQueryId()) .add("rowsInPage", getRowsInPage()) + .add("jobStatistics", getJobStatistics()) .toString(); } @Override public final int hashCode() { return Objects.hash( - getPageNoSchema(), getSchema(), getTotalRows(), getQueryId(), getRowsInPage()); + getPageNoSchema(), + getSchema(), + getTotalRows(), + getQueryId(), + getRowsInPage(), + getJobStatistics()); } @Override @@ -170,6 +183,7 @@ public final boolean equals(Object obj) { && Objects.equals(getSchema(), response.getSchema()) && getTotalRows() == response.getTotalRows() && Objects.equals(getQueryId(), response.getQueryId()) - && Objects.equals(getRowsInPage(), response.getRowsInPage()); + && Objects.equals(getRowsInPage(), response.getRowsInPage()) + && Objects.equals(getJobStatistics(), response.getJobStatistics()); } } diff --git a/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/BigQueryImplTest.java b/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/BigQueryImplTest.java index 9f2320ab3c35..ef97a4721a5e 100644 --- a/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/BigQueryImplTest.java +++ b/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/BigQueryImplTest.java @@ -65,6 +65,8 @@ import com.google.cloud.bigquery.BigQuery.JobOption; import com.google.cloud.bigquery.BigQuery.QueryResultsOption; import com.google.cloud.bigquery.InsertAllRequest.RowToInsert; +import com.google.cloud.bigquery.JobStatistics.QueryStatistics; +import com.google.cloud.bigquery.JobStatistics.QueryStatistics.StatementType; import com.google.cloud.bigquery.spi.BigQueryRpcFactory; import com.google.cloud.bigquery.spi.v2.BigQueryRpc; import com.google.cloud.bigquery.spi.v2.HttpBigQueryRpc; @@ -2874,7 +2876,11 @@ void testQueryWithTimeoutSetsTimeout() throws InterruptedException, IOException .setPageToken(null) .setRows(ImmutableList.of(TABLE_ROW)) .setSchema(TABLE_SCHEMA.toPb()) + .setStatementType("SELECT") + .setTotalBytesBilled(100L) .setTotalBytesProcessed(42L) + .setTotalSlotMs(50L) + .setNumDmlAffectedRows(0L) .setTotalRows(BigInteger.valueOf(1L)); when(bigqueryRpcMock.queryRpcSkipExceptionTranslation(eq(PROJECT), requestPbCapture.capture())) @@ -2883,6 +2889,15 @@ void testQueryWithTimeoutSetsTimeout() throws InterruptedException, IOException bigquery = options.getService(); Object result = bigquery.queryWithTimeout(QUERY_JOB_CONFIGURATION_FOR_QUERY, null, 1000L); assertTrue(result instanceof TableResult); + TableResult tableResult = (TableResult) result; + assertNotNull(tableResult.getJobStatistics()); + assertTrue(tableResult.getJobStatistics() instanceof QueryStatistics); + QueryStatistics stats = (QueryStatistics) tableResult.getJobStatistics(); + assertEquals(StatementType.SELECT, stats.getStatementType()); + assertEquals((Long) 100L, stats.getTotalBytesBilled()); + assertEquals((Long) 42L, stats.getTotalBytesProcessed()); + assertEquals((Long) 50L, stats.getTotalSlotMs()); + assertEquals((Long) 0L, stats.getNumDmlAffectedRows()); QueryRequest requestPb = requestPbCapture.getValue(); assertEquals((Long) 1000L, requestPb.getTimeoutMs()); } diff --git a/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/TableResultTest.java b/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/TableResultTest.java index 90ae2692f00c..a615fa470c1f 100644 --- a/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/TableResultTest.java +++ b/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/TableResultTest.java @@ -115,4 +115,120 @@ void testSchema() { newFieldValueList("2").withSchema(SCHEMA.getFields())) .inOrder(); } + + @Test + void testJobStatistics() { + JobStatistics.QueryStatistics stats = + JobStatistics.QueryStatistics.newBuilder() + .setStatementType(JobStatistics.QueryStatistics.StatementType.SELECT) + .setTotalBytesBilled(1024L) + .setTotalBytesProcessed(2048L) + .setTotalSlotMs(500L) + .setNumDmlAffectedRows(0L) + .build(); + + TableResult result = + TableResult.newBuilder() + .setSchema(SCHEMA) + .setTotalRows(3L) + .setPageNoSchema(INNER_PAGE_0) + .setRowsInPage(2L) + .setJobStatistics(stats) + .build(); + + assertThat(result.getJobStatistics()).isEqualTo(stats); + assertThat(result.getJobStatistics()).isInstanceOf(JobStatistics.QueryStatistics.class); + JobStatistics.QueryStatistics queryStats = + (JobStatistics.QueryStatistics) result.getJobStatistics(); + assertThat(queryStats.getStatementType()) + .isEqualTo(JobStatistics.QueryStatistics.StatementType.SELECT); + assertThat(queryStats.getTotalBytesBilled()).isEqualTo(1024L); + assertThat(queryStats.getTotalBytesProcessed()).isEqualTo(2048L); + assertThat(queryStats.getTotalSlotMs()).isEqualTo(500L); + assertThat(queryStats.getNumDmlAffectedRows()).isEqualTo(0L); + + TableResult next = result.getNextPage(); + assertThat(next.getJobStatistics()).isEqualTo(stats); + } + + @Test + void testToBuilder() { + JobStatistics.QueryStatistics stats1 = + JobStatistics.QueryStatistics.newBuilder() + .setStatementType(JobStatistics.QueryStatistics.StatementType.INSERT) + .setNumDmlAffectedRows(5L) + .build(); + + TableResult result = + TableResult.newBuilder() + .setSchema(SCHEMA) + .setTotalRows(3L) + .setPageNoSchema(INNER_PAGE_0) + .setRowsInPage(2L) + .setJobStatistics(stats1) + .build(); + + JobStatistics.QueryStatistics stats2 = + JobStatistics.QueryStatistics.newBuilder() + .setStatementType(JobStatistics.QueryStatistics.StatementType.UPDATE) + .setNumDmlAffectedRows(10L) + .build(); + + TableResult modified = result.toBuilder().setJobStatistics(stats2).build(); + + assertThat(modified.getJobStatistics()).isEqualTo(stats2); + } + + @Test + void testEqualsAndHashCode() { + JobStatistics.QueryStatistics stats1 = + JobStatistics.QueryStatistics.newBuilder() + .setStatementType(JobStatistics.QueryStatistics.StatementType.SELECT) + .setTotalBytesBilled(100L) + .build(); + + JobStatistics.QueryStatistics stats2 = + JobStatistics.QueryStatistics.newBuilder() + .setStatementType(JobStatistics.QueryStatistics.StatementType.SELECT) + .setTotalBytesBilled(100L) + .build(); + + JobStatistics.QueryStatistics stats3 = + JobStatistics.QueryStatistics.newBuilder() + .setStatementType(JobStatistics.QueryStatistics.StatementType.DELETE) + .setTotalBytesBilled(100L) + .build(); + + TableResult result1 = + TableResult.newBuilder() + .setSchema(SCHEMA) + .setTotalRows(3L) + .setPageNoSchema(INNER_PAGE_0) + .setRowsInPage(2L) + .setJobStatistics(stats1) + .build(); + + TableResult result2 = + TableResult.newBuilder() + .setSchema(SCHEMA) + .setTotalRows(3L) + .setPageNoSchema(INNER_PAGE_0) + .setRowsInPage(2L) + .setJobStatistics(stats2) + .build(); + + TableResult result3 = + TableResult.newBuilder() + .setSchema(SCHEMA) + .setTotalRows(3L) + .setPageNoSchema(INNER_PAGE_0) + .setRowsInPage(2L) + .setJobStatistics(stats3) + .build(); + + assertThat(result1).isEqualTo(result2); + assertThat(result1.hashCode()).isEqualTo(result2.hashCode()); + assertThat(result1).isNotEqualTo(result3); + assertThat(result1.toString()).contains("jobStatistics"); + } } diff --git a/java-bigquery/pom.xml b/java-bigquery/pom.xml index ee1fcd22dfc8..b480390dd2e7 100644 --- a/java-bigquery/pom.xml +++ b/java-bigquery/pom.xml @@ -55,7 +55,7 @@ UTF-8 github google-cloud-bigquery-parent - v2-rev20251012-2.0.0 + v2-rev20260731-2.0.0