From 20d48415ac9814a52b0f158d4f7f4ee6b6ff1645 Mon Sep 17 00:00:00 2001 From: Marco Ziccardi Date: Fri, 11 Dec 2015 23:37:57 +0100 Subject: [PATCH 1/4] Add query request and query response classes and tests --- .../google/gcloud/bigquery/QueryRequest.java | 272 ++++++++++++++++++ .../google/gcloud/bigquery/QueryResponse.java | 233 +++++++++++++++ .../gcloud/bigquery/QueryRequestTest.java | 94 ++++++ .../gcloud/bigquery/QueryResponseTest.java | 113 ++++++++ 4 files changed, 712 insertions(+) create mode 100644 gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryRequest.java create mode 100644 gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResponse.java create mode 100644 gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryRequestTest.java create mode 100644 gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryResponseTest.java diff --git a/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryRequest.java b/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryRequest.java new file mode 100644 index 000000000000..717fe78e6fc2 --- /dev/null +++ b/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryRequest.java @@ -0,0 +1,272 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.bigquery; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.MoreObjects; + +import java.io.Serializable; +import java.util.Objects; + +/** + * Google Cloud BigQuery Query Request. This class can be used to run a BigQuery SQL query and + * return results if the query completes within a specified timeout. The query results are saved to + * a temporary table that is deleted approximately 24 hours after the query is run. Query is run + * through a BigQuery Job whose identity can be accessed via {@link QueryResponse#job()}. + * + * @see Query + */ +public class QueryRequest implements Serializable { + + private static final long serialVersionUID = -8727328332415880852L; + + private final String query; + private final Long maxResults; + private final DatasetId defaultDataset; + private final Long maxWaitTime; + private final Boolean dryRun; + private final Boolean useQueryCache; + + public static final class Builder { + + private String query; + private Long maxResults; + private DatasetId defaultDataset; + private Long maxWaitTime; + private Boolean dryRun; + private Boolean useQueryCache; + + private Builder() {} + + /** + * Sets the BigQuery query to be executed. + */ + public Builder query(String query) { + this.query = query; + return this; + } + + /** + * Sets the maximum number of rows of data to return per page of results. Setting this flag to a + * small value such as 1000 and then paging through results might improve reliability when the + * query result set is large. In addition to this limit, responses are also limited to 10 MB. + * By default, there is no maximum row count, and only the byte limit applies. + */ + public Builder maxResults(Long maxResults) { + this.maxResults = maxResults; + return this; + } + + /** + * Sets the default dataset to assume for any unqualified table names in the query. + */ + public Builder defaultDataset(DatasetId defaultDataset) { + this.defaultDataset = defaultDataset; + return this; + } + + /** + * Sets how long to wait for the query to complete, in milliseconds, before the request times + * out and returns. Note that this is only a timeout for the request, not the query. If the + * query takes longer to run than the timeout value, the call returns without any results and + * with the {@link QueryResponse#jobComplete()} set to {@code false}. You can call + * {@link BigQuery#getQueryResults(JobId, BigQuery.QueryResultsOption...)} to wait for the query + * to complete and read the results. The default value is 10000 milliseconds (10 seconds). + */ + public Builder maxWaitTime(Long maxWaitTime) { + this.maxWaitTime = maxWaitTime; + return this; + } + + /** + * Sets whether the query has to be dry run or not. If set, the query is not executed: if the + * query is valid statistics are returned on how many bytes would be processed, if the query is + * invalid an error is returned. + */ + public Builder dryRun(Boolean dryRun) { + this.dryRun = dryRun; + return this; + } + + /** + * Sets whether to look for the result in the query cache. The query cache is a best-effort + * cache that will be flushed whenever tables in the query are modified. + * + * @see Query Caching + */ + public Builder useQueryCache(Boolean useQueryCache) { + this.useQueryCache = useQueryCache; + return this; + } + + public QueryRequest build() { + return new QueryRequest(this); + } + } + + private QueryRequest(Builder builder) { + query = checkNotNull(builder.query); + maxResults = builder.maxResults; + defaultDataset = builder.defaultDataset; + maxWaitTime = builder.maxWaitTime; + dryRun = builder.dryRun; + useQueryCache = builder.useQueryCache; + } + + /** + * Sets the BigQuery query to be executed. + */ + public String query() { + return query; + } + + /** + * Returns the maximum number of rows of data to return per page of results. + */ + public Long maxResults() { + return maxResults; + } + + /** + * Returns the default dataset to assume for any unqualified table names in the query. + */ + public DatasetId defaultDataset() { + return defaultDataset; + } + + /** + * Returns how long to wait for the query to complete, in milliseconds, before the request times + * out and returns. Note that this is only a timeout for the request, not the query. If the + * query takes longer to run than the timeout value, the call returns without any results and + * with the {@link QueryResponse#jobComplete()} set to {@code false}. You can call + * {@link BigQuery#getQueryResults(JobId, BigQuery.QueryResultsOption...)} to wait for the query + * to complete and read the results. The default value is 10000 milliseconds (10 seconds). + */ + public Long maxWaitTime() { + return maxWaitTime; + } + + /** + * Returns whether the query has to be dry run or not. If set, the query is not executed: if the + * query is valid statistics are returned on how many bytes would be processed, if the query is + * invalid an error is returned. + */ + public Boolean dryRun() { + return dryRun; + } + + /** + * Returns whether to look for the result in the query cache. The query cache is a best-effort + * cache that will be flushed whenever tables in the query are modified. + * + * @see Query Caching + */ + public Boolean useQueryCache() { + return useQueryCache; + } + + /** + * Returns a builder for the {@code QueryRequest} object. + */ + public Builder toBuilder() { + return new Builder() + .query(query) + .maxResults(maxResults) + .defaultDataset(defaultDataset) + .maxWaitTime(maxWaitTime) + .dryRun(dryRun) + .useQueryCache(useQueryCache); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("query", query) + .add("maxResults", maxResults) + .add("defaultDataset", defaultDataset) + .add("maxWaitTime", maxWaitTime) + .add("dryRun", dryRun) + .add("useQueryCache", useQueryCache) + .toString(); + } + + @Override + public int hashCode() { + return Objects.hash(query, maxResults, defaultDataset, maxWaitTime, dryRun, useQueryCache); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof QueryRequest && Objects.equals(toPb(), ((QueryRequest) obj).toPb()); + } + + com.google.api.services.bigquery.model.QueryRequest toPb() { + com.google.api.services.bigquery.model.QueryRequest queryRequestPb = + new com.google.api.services.bigquery.model.QueryRequest().setQuery(query); + if (maxResults != null) { + queryRequestPb.setMaxResults(maxResults); + } + if (defaultDataset != null) { + queryRequestPb.setDefaultDataset(defaultDataset.toPb()); + } + if (maxWaitTime != null) { + queryRequestPb.setTimeoutMs(maxWaitTime); + } + if (dryRun != null) { + queryRequestPb.setDryRun(dryRun); + } + if (useQueryCache != null) { + queryRequestPb.setUseQueryCache(useQueryCache); + } + return queryRequestPb; + } + + /** + * Creates a builder for a {@code QueryRequest} given the BigQuery SQL query to be executed. + */ + public static Builder builder(String query) { + return new Builder().query(query); + } + + /** + * Creates a {@code QueryRequest} object given the BigQuery SQL query to be executed. + */ + public static QueryRequest of(String query) { + return new Builder().query(query).build(); + } + + static QueryRequest fromPb(com.google.api.services.bigquery.model.QueryRequest queryRequestPb) { + Builder builder = builder(queryRequestPb.getQuery()); + if (queryRequestPb.getMaxResults() != null) { + builder.maxResults(queryRequestPb.getMaxResults()); + } + if (queryRequestPb.getDefaultDataset() != null) { + builder.defaultDataset(DatasetId.fromPb(queryRequestPb.getDefaultDataset())); + } + if (queryRequestPb.getTimeoutMs() != null) { + builder.maxWaitTime(queryRequestPb.getTimeoutMs()); + } + if (queryRequestPb.getDryRun() != null) { + builder.dryRun(queryRequestPb.getDryRun()); + } + if (queryRequestPb.getUseQueryCache() != null) { + builder.useQueryCache(queryRequestPb.getUseQueryCache()); + } + return builder.build(); + } +} diff --git a/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResponse.java b/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResponse.java new file mode 100644 index 000000000000..add54583f033 --- /dev/null +++ b/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResponse.java @@ -0,0 +1,233 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.bigquery; + +import com.google.common.base.MoreObjects; +import com.google.gcloud.Page; + +import java.io.Serializable; +import java.util.List; +import java.util.Objects; + +/** + * Google Cloud BigQuery Query Response. This class contains the results of a Query Job or of a + * Query Request. + */ +public class QueryResponse implements Serializable { + + private static final long serialVersionUID = 3549226764825005655L; + + private final String etag; + private final Schema schema; + private final JobId job; + private final Long totalRows; + private final Page> rows; + private final Long totalBytesProcessed; + private final Boolean jobComplete; + private final List errors; + private final Boolean cacheHit; + + static final class Builder { + + private String etag; + private Schema schema; + private JobId job; + private Long totalRows; + private Page> rows; + private Long totalBytesProcessed; + private Boolean jobComplete; + private List errors; + private Boolean cacheHit; + + private Builder() {} + + Builder etag(String etag) { + this.etag = etag; + return this; + } + + Builder schema(Schema schema) { + this.schema = schema; + return this; + } + + Builder job(JobId job) { + this.job = job; + return this; + } + + Builder totalRows(Long totalRows) { + this.totalRows = totalRows; + return this; + } + + Builder rows(Page> rows) { + this.rows = rows; + return this; + } + + Builder totalBytesProcessed(Long totalBytesProcessed) { + this.totalBytesProcessed = totalBytesProcessed; + return this; + } + + Builder jobComplete(Boolean jobComplete) { + this.jobComplete = jobComplete; + return this; + } + + Builder errors(List errors) { + this.errors = errors; + return this; + } + + Builder cacheHit(Boolean cacheHit) { + this.cacheHit = cacheHit; + return this; + } + + QueryResponse build() { + return new QueryResponse(this); + } + } + + private QueryResponse(Builder builder) { + this.etag = builder.etag; + this.schema = builder.schema; + this.job = builder.job; + this.totalRows = builder.totalRows; + this.rows = builder.rows; + this.totalBytesProcessed = builder.totalBytesProcessed; + this.jobComplete = builder.jobComplete; + this.errors = builder.errors; + this.cacheHit = builder.cacheHit; + } + + /** + * Returns the hash of the {@code QueryResponse} resource or {@code null} if not set. + */ + public String etag() { + return etag; + } + + /** + * Returns the schema of the results when the query completed successfully. Returns {@code null} + * otherwise. + */ + public Schema schema() { + return schema; + } + + /** + * Returns the identity of the BigQuery Job that was created to run the query. This field will be + * present even if the original request timed out. + */ + public JobId job() { + return job; + } + + /** + * Returns the total number of rows in the complete query result set, which can be more than the + * number of rows in the first page of results returned by {@link #rows()}. Returns {@code null} + * if the query did not complete successfully. + */ + public Long totalRows() { + return totalRows; + } + + /** + * Returns the query result as a paginated list of rows, if the query completed successfully. + * Returns {@code null} otherwise. + */ + public Page> rows() { + return rows; + } + + /** + * Returns the total number of bytes processed for the query. + */ + public Long totalBytesProcessed() { + return totalBytesProcessed; + } + + /** + * Returns whether the job running the query has completed or not. If {@link #rows()} and + * {@link #totalRows()} are present, this method will always return {@code true}. If this method + * returns {@code false}, {@link #totalRows()} will not be available. + */ + public Boolean jobComplete() { + return jobComplete; + } + + /** + * Returns errors and warnings encountered during the running of the job, if any. Errors here do + * not necessarily mean that the job has completed or was unsuccessful. + */ + public List errors() { + return errors; + } + + /** + * Returns whether the query result was fetched from the query cache. + * + * @see Query Caching + */ + public Boolean cacheHit() { + return cacheHit; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("job", job) + .add("jobComplete", jobComplete) + .add("totalRows", totalRows) + .add("schema", schema) + .add("totalBytesProcessed", totalBytesProcessed) + .add("errors", errors) + .add("cacheHit", cacheHit) + .toString(); + } + + @Override + public int hashCode() { + return Objects.hash(job); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + QueryResponse response = (QueryResponse) obj; + return Objects.equals(schema, response.schema) + && Objects.equals(job, response.job) + && Objects.equals(totalRows, response.totalRows) + && Objects.equals(rows, response.rows) + && Objects.equals(totalBytesProcessed, response.totalBytesProcessed) + && Objects.equals(jobComplete, response.jobComplete) + && Objects.equals(errors, response.errors) + && Objects.equals(cacheHit, response.cacheHit); + } + + static Builder builder() { + return new Builder(); + } +} \ No newline at end of file diff --git a/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryRequestTest.java b/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryRequestTest.java new file mode 100644 index 000000000000..f2ff83cc6009 --- /dev/null +++ b/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryRequestTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.bigquery; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.junit.Test; + +public class QueryRequestTest { + + private static final String QUERY = "BigQuery SQL"; + private static final DatasetId DATASET_ID = DatasetId.of("project", "dataset"); + private static final Boolean USE_QUERY_CACHE = true; + private static final Boolean DRY_RUN = false; + private static final Long MAX_RESULTS = 42L; + private static final Long MAX_WAIT_TIME = 42000L; + private static final QueryRequest QUERY_REQUEST = QueryRequest.builder(QUERY) + .useQueryCache(USE_QUERY_CACHE) + .defaultDataset(DATASET_ID) + .dryRun(DRY_RUN) + .maxResults(MAX_RESULTS) + .maxWaitTime(MAX_WAIT_TIME) + .build(); + + @Test + public void testToBuilder() { + compareQueryRequest(QUERY_REQUEST, QUERY_REQUEST.toBuilder().build()); + QueryRequest queryRequest = QUERY_REQUEST.toBuilder() + .query("New BigQuery SQL") + .build(); + assertEquals("New BigQuery SQL", queryRequest.query()); + queryRequest = queryRequest.toBuilder().query(QUERY).build(); + compareQueryRequest(QUERY_REQUEST, queryRequest); + } + + @Test + public void testToBuilderIncomplete() { + QueryRequest queryRequest = QueryRequest.of(QUERY); + compareQueryRequest(queryRequest, queryRequest.toBuilder().build()); + } + + @Test + public void testBuilder() { + assertEquals(QUERY, QUERY_REQUEST.query()); + assertEquals(USE_QUERY_CACHE, QUERY_REQUEST.useQueryCache()); + assertEquals(DATASET_ID, QUERY_REQUEST.defaultDataset()); + assertEquals(DRY_RUN, QUERY_REQUEST.dryRun()); + assertEquals(MAX_RESULTS, QUERY_REQUEST.maxResults()); + assertEquals(MAX_WAIT_TIME, QUERY_REQUEST.maxWaitTime()); + } + + @Test + public void testOf() { + QueryRequest request = QueryRequest.of(QUERY); + assertEquals(QUERY, request.query()); + assertNull(request.useQueryCache()); + assertNull(request.defaultDataset()); + assertNull(request.dryRun()); + assertNull(request.maxResults()); + assertNull(request.maxWaitTime()); + } + + @Test + public void testToPbAndFromPb() { + compareQueryRequest(QUERY_REQUEST, QueryRequest.fromPb(QUERY_REQUEST.toPb())); + QueryRequest queryRequest = QueryRequest.of(QUERY); + compareQueryRequest(queryRequest, QueryRequest.fromPb(queryRequest.toPb())); + } + + private void compareQueryRequest(QueryRequest expected, QueryRequest value) { + assertEquals(expected, value); + assertEquals(expected.query(), value.query()); + assertEquals(expected.useQueryCache(), value.useQueryCache()); + assertEquals(expected.defaultDataset(), value.defaultDataset()); + assertEquals(expected.dryRun(), value.dryRun()); + assertEquals(expected.maxResults(), value.maxResults()); + assertEquals(expected.maxWaitTime(), value.maxWaitTime()); + } +} diff --git a/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryResponseTest.java b/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryResponseTest.java new file mode 100644 index 000000000000..f6f29e7b1312 --- /dev/null +++ b/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryResponseTest.java @@ -0,0 +1,113 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.bigquery; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import com.google.common.collect.ImmutableList; +import com.google.gcloud.Page; +import com.google.gcloud.PageImpl; + +import org.junit.Test; + +import java.util.List; + +public class QueryResponseTest { + + private static final String ETAG = "etag"; + private static final Field FIELD_SCHEMA1 = + Field.builder("StringField", Field.Type.string()) + .mode(Field.Mode.NULLABLE) + .description("FieldDescription1") + .build(); + private static final Schema SCHEMA = Schema.of(FIELD_SCHEMA1); + private static final JobId JOB_ID = JobId.of("project", "job"); + private static final Long TOTAL_ROWS = 42L; + private static final PageImpl.NextPageFetcher> FETCHER = + new PageImpl.NextPageFetcher>() { + @Override + public Page> nextPage() { + return null; + } + }; + private static final Page> ROWS = + new PageImpl>(FETCHER, "cursor", ImmutableList.>of()); + private static final Long TOTAL_BYTES_PROCESSED = 4200L; + private static final Boolean JOB_COMPLETE = true; + private static final List ERRORS = ImmutableList.of( + new BigQueryError("reason1", "location1", "message1", "debugInfo1"), + new BigQueryError("reason2", "location2", "message2", "debugInfo2") + ); + private static final Boolean CACHE_HIT = false; + private static final QueryResponse QUERY_RESPONSE = QueryResponse.builder() + .etag(ETAG) + .schema(SCHEMA) + .job(JOB_ID) + .totalRows(TOTAL_ROWS) + .rows(ROWS) + .totalBytesProcessed(TOTAL_BYTES_PROCESSED) + .jobComplete(JOB_COMPLETE) + .errors(ERRORS) + .cacheHit(CACHE_HIT) + .build(); + + @Test + public void testBuilder() { + assertEquals(ETAG, QUERY_RESPONSE.etag()); + assertEquals(SCHEMA, QUERY_RESPONSE.schema()); + assertEquals(JOB_ID, QUERY_RESPONSE.job()); + assertEquals(TOTAL_ROWS, QUERY_RESPONSE.totalRows()); + assertEquals(ROWS, QUERY_RESPONSE.rows()); + assertEquals(TOTAL_BYTES_PROCESSED, QUERY_RESPONSE.totalBytesProcessed()); + assertEquals(JOB_COMPLETE, QUERY_RESPONSE.jobComplete()); + assertEquals(ERRORS, QUERY_RESPONSE.errors()); + assertEquals(CACHE_HIT, QUERY_RESPONSE.cacheHit()); + } + + @Test + public void testBuilderIncomplete() { + QueryResponse queryResponse = QueryResponse.builder().jobComplete(false).build(); + assertNull(queryResponse.etag()); + assertNull(queryResponse.schema()); + assertNull(queryResponse.job()); + assertNull(queryResponse.totalRows()); + assertNull(queryResponse.rows()); + assertNull(queryResponse.totalBytesProcessed()); + assertEquals(false, queryResponse.jobComplete()); + assertNull(queryResponse.errors()); + assertNull(queryResponse.cacheHit()); + } + + @Test + public void testEquals() { + compareQueryResponse(QUERY_RESPONSE, QUERY_RESPONSE); + } + + private void compareQueryResponse(QueryResponse expected, QueryResponse value) { + assertEquals(expected, value); + assertEquals(expected.etag(), value.etag()); + assertEquals(expected.schema(), value.schema()); + assertEquals(expected.job(), value.job()); + assertEquals(expected.totalRows(), value.totalRows()); + assertEquals(expected.rows(), value.rows()); + assertEquals(expected.totalBytesProcessed(), value.totalBytesProcessed()); + assertEquals(expected.jobComplete(), value.jobComplete()); + assertEquals(expected.errors(), value.errors()); + assertEquals(expected.cacheHit(), value.cacheHit()); + } +} From 9756dff839d558145eaf927826d0b399af49f193 Mon Sep 17 00:00:00 2001 From: Marco Ziccardi Date: Mon, 14 Dec 2015 11:34:29 +0100 Subject: [PATCH 2/4] Refactor QueryRequest and QueryResponse - Add better javadoc and snippet to QueryResponse - Use primitive boolean for jobComplete - Add test for NPE in QueryRequestTests - Add QueryRequest and QueryResponse to SerializationTest --- .../google/gcloud/bigquery/QueryRequest.java | 33 ++++++----- .../google/gcloud/bigquery/QueryResponse.java | 58 ++++++++++++------- .../gcloud/bigquery/QueryRequestTest.java | 9 +++ .../gcloud/bigquery/QueryResponseTest.java | 12 ++-- .../gcloud/bigquery/SerializationTest.java | 15 ++++- 5 files changed, 85 insertions(+), 42 deletions(-) diff --git a/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryRequest.java b/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryRequest.java index 717fe78e6fc2..83d6ed4164b4 100644 --- a/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryRequest.java +++ b/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryRequest.java @@ -26,10 +26,11 @@ /** * Google Cloud BigQuery Query Request. This class can be used to run a BigQuery SQL query and * return results if the query completes within a specified timeout. The query results are saved to - * a temporary table that is deleted approximately 24 hours after the query is run. Query is run + * a temporary table that is deleted approximately 24 hours after the query is run. The query is run * through a BigQuery Job whose identity can be accessed via {@link QueryResponse#job()}. * * @see Query + * @see Query Reference */ public class QueryRequest implements Serializable { @@ -57,7 +58,7 @@ private Builder() {} * Sets the BigQuery query to be executed. */ public Builder query(String query) { - this.query = query; + this.query = checkNotNull(query); return this; } @@ -84,9 +85,8 @@ public Builder defaultDataset(DatasetId defaultDataset) { * Sets how long to wait for the query to complete, in milliseconds, before the request times * out and returns. Note that this is only a timeout for the request, not the query. If the * query takes longer to run than the timeout value, the call returns without any results and - * with the {@link QueryResponse#jobComplete()} set to {@code false}. You can call - * {@link BigQuery#getQueryResults(JobId, BigQuery.QueryResultsOption...)} to wait for the query - * to complete and read the results. The default value is 10000 milliseconds (10 seconds). + * with the {@link QueryResponse#jobComplete()} set to {@code false}. If not set, a wait time of + * 10000 milliseconds (10 seconds) is used. */ public Builder maxWaitTime(Long maxWaitTime) { this.maxWaitTime = maxWaitTime; @@ -94,9 +94,9 @@ public Builder maxWaitTime(Long maxWaitTime) { } /** - * Sets whether the query has to be dry run or not. If set, the query is not executed: if the - * query is valid statistics are returned on how many bytes would be processed, if the query is - * invalid an error is returned. + * Sets whether the query has to be dry run or not. If set, the query is not executed. If the + * query is valid statistics are returned on how many bytes would be processed. If the query is + * invalid an error is returned. If not set the query is executed. */ public Builder dryRun(Boolean dryRun) { this.dryRun = dryRun; @@ -105,7 +105,8 @@ public Builder dryRun(Boolean dryRun) { /** * Sets whether to look for the result in the query cache. The query cache is a best-effort - * cache that will be flushed whenever tables in the query are modified. + * cache that will be flushed whenever tables in the query are modified. If not specified the + * query cache is used. * * @see Query Caching */ @@ -120,7 +121,7 @@ public QueryRequest build() { } private QueryRequest(Builder builder) { - query = checkNotNull(builder.query); + query = builder.query; maxResults = builder.maxResults; defaultDataset = builder.defaultDataset; maxWaitTime = builder.maxWaitTime; @@ -155,16 +156,17 @@ public DatasetId defaultDataset() { * query takes longer to run than the timeout value, the call returns without any results and * with the {@link QueryResponse#jobComplete()} set to {@code false}. You can call * {@link BigQuery#getQueryResults(JobId, BigQuery.QueryResultsOption...)} to wait for the query - * to complete and read the results. The default value is 10000 milliseconds (10 seconds). + * to complete and read the results. If not set, a wait time of 10000 milliseconds (10 seconds) + * is used. */ public Long maxWaitTime() { return maxWaitTime; } /** - * Returns whether the query has to be dry run or not. If set, the query is not executed: if the - * query is valid statistics are returned on how many bytes would be processed, if the query is - * invalid an error is returned. + * Returns whether the query has to be dry run or not. If set, the query is not executed. If the + * query is valid statistics are returned on how many bytes would be processed. If the query is + * invalid an error is returned. If not set the query is executed. */ public Boolean dryRun() { return dryRun; @@ -172,7 +174,8 @@ public Boolean dryRun() { /** * Returns whether to look for the result in the query cache. The query cache is a best-effort - * cache that will be flushed whenever tables in the query are modified. + * cache that will be flushed whenever tables in the query are modified. If not specified the + * query cache is used. * * @see Query Caching */ diff --git a/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResponse.java b/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResponse.java index add54583f033..2c9257388b12 100644 --- a/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResponse.java +++ b/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResponse.java @@ -26,6 +26,21 @@ /** * Google Cloud BigQuery Query Response. This class contains the results of a Query Job or of a * Query Request. + * + *

Example usage of a query response: + *

    {@code
+ *    QueryResponse response = bigquery.query(request);
+ *    while (!response.jobComplete()) {
+ *      response = bigquery.getQueryResults(response.job());
+ *      Thread.sleep(1000);
+ *    }
+ *    List executionErrors = response.executionErrors();
+ *    Page> rows = response.rows();
+ * }
+ * + * @see Get Query + * Results + * @see Query */ public class QueryResponse implements Serializable { @@ -37,8 +52,8 @@ public class QueryResponse implements Serializable { private final Long totalRows; private final Page> rows; private final Long totalBytesProcessed; - private final Boolean jobComplete; - private final List errors; + private final boolean jobComplete; + private final List executionErrors; private final Boolean cacheHit; static final class Builder { @@ -49,8 +64,8 @@ static final class Builder { private Long totalRows; private Page> rows; private Long totalBytesProcessed; - private Boolean jobComplete; - private List errors; + private boolean jobComplete; + private List executionErrors; private Boolean cacheHit; private Builder() {} @@ -85,13 +100,13 @@ Builder totalBytesProcessed(Long totalBytesProcessed) { return this; } - Builder jobComplete(Boolean jobComplete) { + Builder jobComplete(boolean jobComplete) { this.jobComplete = jobComplete; return this; } - Builder errors(List errors) { - this.errors = errors; + Builder executionErrors(List executionErrors) { + this.executionErrors = executionErrors; return this; } @@ -113,7 +128,7 @@ private QueryResponse(Builder builder) { this.rows = builder.rows; this.totalBytesProcessed = builder.totalBytesProcessed; this.jobComplete = builder.jobComplete; - this.errors = builder.errors; + this.executionErrors = builder.executionErrors; this.cacheHit = builder.cacheHit; } @@ -125,7 +140,7 @@ public String etag() { } /** - * Returns the schema of the results when the query completed successfully. Returns {@code null} + * Returns the schema of the results if the query completed successfully. Returns {@code null} * otherwise. */ public Schema schema() { @@ -158,7 +173,9 @@ public Page> rows() { } /** - * Returns the total number of bytes processed for the query. + * Returns the total number of bytes processed for the query. If this query was a dry run, this is + * the number of bytes that would be processed if the query were run. Returns {@code null} + * if the query did not complete. */ public Long totalBytesProcessed() { return totalBytesProcessed; @@ -166,10 +183,11 @@ public Long totalBytesProcessed() { /** * Returns whether the job running the query has completed or not. If {@link #rows()} and - * {@link #totalRows()} are present, this method will always return {@code true}. If this method - * returns {@code false}, {@link #totalRows()} will not be available. + * {@link #totalRows()} are not {@code null}, this method will always return {@code true}. If this + * method returns {@code false} {@link #totalRows()} and {@link #rows()} return {@code null}. This + * method can be used to check if query execution completed and results are available. */ - public Boolean jobComplete() { + public boolean jobComplete() { return jobComplete; } @@ -177,8 +195,8 @@ public Boolean jobComplete() { * Returns errors and warnings encountered during the running of the job, if any. Errors here do * not necessarily mean that the job has completed or was unsuccessful. */ - public List errors() { - return errors; + public List executionErrors() { + return executionErrors; } /** @@ -198,7 +216,7 @@ public String toString() { .add("totalRows", totalRows) .add("schema", schema) .add("totalBytesProcessed", totalBytesProcessed) - .add("errors", errors) + .add("executionErrors", executionErrors) .add("cacheHit", cacheHit) .toString(); } @@ -217,17 +235,17 @@ public boolean equals(Object obj) { return false; } QueryResponse response = (QueryResponse) obj; - return Objects.equals(schema, response.schema) + return jobComplete == response.jobComplete + && Objects.equals(schema, response.schema) && Objects.equals(job, response.job) && Objects.equals(totalRows, response.totalRows) && Objects.equals(rows, response.rows) && Objects.equals(totalBytesProcessed, response.totalBytesProcessed) - && Objects.equals(jobComplete, response.jobComplete) - && Objects.equals(errors, response.errors) + && Objects.equals(executionErrors, response.executionErrors) && Objects.equals(cacheHit, response.cacheHit); } static Builder builder() { return new Builder(); } -} \ No newline at end of file +} diff --git a/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryRequestTest.java b/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryRequestTest.java index f2ff83cc6009..276e4f6792b3 100644 --- a/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryRequestTest.java +++ b/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryRequestTest.java @@ -19,7 +19,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; public class QueryRequestTest { @@ -37,6 +39,9 @@ public class QueryRequestTest { .maxWaitTime(MAX_WAIT_TIME) .build(); + @Rule + public ExpectedException thrown = ExpectedException.none(); + @Test public void testToBuilder() { compareQueryRequest(QUERY_REQUEST, QUERY_REQUEST.toBuilder().build()); @@ -62,6 +67,8 @@ public void testBuilder() { assertEquals(DRY_RUN, QUERY_REQUEST.dryRun()); assertEquals(MAX_RESULTS, QUERY_REQUEST.maxResults()); assertEquals(MAX_WAIT_TIME, QUERY_REQUEST.maxWaitTime()); + thrown.expect(NullPointerException.class); + QueryRequest.builder(null); } @Test @@ -73,6 +80,8 @@ public void testOf() { assertNull(request.dryRun()); assertNull(request.maxResults()); assertNull(request.maxWaitTime()); + thrown.expect(NullPointerException.class); + QueryRequest.of(null); } @Test diff --git a/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryResponseTest.java b/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryResponseTest.java index f6f29e7b1312..2c00e81a9f63 100644 --- a/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryResponseTest.java +++ b/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryResponseTest.java @@ -46,7 +46,7 @@ public Page> nextPage() { } }; private static final Page> ROWS = - new PageImpl>(FETCHER, "cursor", ImmutableList.>of()); + new PageImpl<>(FETCHER, "cursor", ImmutableList.>of()); private static final Long TOTAL_BYTES_PROCESSED = 4200L; private static final Boolean JOB_COMPLETE = true; private static final List ERRORS = ImmutableList.of( @@ -62,7 +62,7 @@ public Page> nextPage() { .rows(ROWS) .totalBytesProcessed(TOTAL_BYTES_PROCESSED) .jobComplete(JOB_COMPLETE) - .errors(ERRORS) + .executionErrors(ERRORS) .cacheHit(CACHE_HIT) .build(); @@ -73,9 +73,9 @@ public void testBuilder() { assertEquals(JOB_ID, QUERY_RESPONSE.job()); assertEquals(TOTAL_ROWS, QUERY_RESPONSE.totalRows()); assertEquals(ROWS, QUERY_RESPONSE.rows()); - assertEquals(TOTAL_BYTES_PROCESSED, QUERY_RESPONSE.totalBytesProcessed()); + assertEquals(TOTAL_BYTES_PROCESSED, (Long) QUERY_RESPONSE.totalBytesProcessed()); assertEquals(JOB_COMPLETE, QUERY_RESPONSE.jobComplete()); - assertEquals(ERRORS, QUERY_RESPONSE.errors()); + assertEquals(ERRORS, QUERY_RESPONSE.executionErrors()); assertEquals(CACHE_HIT, QUERY_RESPONSE.cacheHit()); } @@ -89,7 +89,7 @@ public void testBuilderIncomplete() { assertNull(queryResponse.rows()); assertNull(queryResponse.totalBytesProcessed()); assertEquals(false, queryResponse.jobComplete()); - assertNull(queryResponse.errors()); + assertNull(queryResponse.executionErrors()); assertNull(queryResponse.cacheHit()); } @@ -107,7 +107,7 @@ private void compareQueryResponse(QueryResponse expected, QueryResponse value) { assertEquals(expected.rows(), value.rows()); assertEquals(expected.totalBytesProcessed(), value.totalBytesProcessed()); assertEquals(expected.jobComplete(), value.jobComplete()); - assertEquals(expected.errors(), value.errors()); + assertEquals(expected.executionErrors(), value.executionErrors()); assertEquals(expected.cacheHit(), value.cacheHit()); } } diff --git a/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/SerializationTest.java b/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/SerializationTest.java index 67addce39d6d..20092395565a 100644 --- a/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/SerializationTest.java +++ b/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/SerializationTest.java @@ -186,6 +186,19 @@ public class SerializationTest { private static final InsertAllResponse INSERT_ALL_RESPONSE = new InsertAllResponse(ERRORS_MAP); private static final FieldValue FIELD_VALUE = new FieldValue(FieldValue.Attribute.PRIMITIVE, "value"); + private static final QueryRequest QUERY_REQUEST = QueryRequest.builder("query") + .useQueryCache(true) + .defaultDataset(DATASET_ID) + .dryRun(false) + .maxResults(42L) + .maxWaitTime(10L) + .build(); + private static final QueryResponse QUERY_RESPONSE = QueryResponse.builder() + .etag(ETAG) + .schema(TABLE_SCHEMA) + .job(JOB_ID) + .totalRows(1L) + .build(); @Test public void testServiceOptions() throws Exception { @@ -212,7 +225,7 @@ public void testModelAndRequests() throws Exception { TABLE_SCHEMA, TABLE_INFO, VIEW_INFO, EXTERNAL_TABLE_INFO, INLINE_FUNCTION, URI_FUNCTION, JOB_STATISTICS, EXTRACT_STATISTICS, LOAD_STATISTICS, QUERY_STATISTICS, BIGQUERY_ERROR, JOB_STATUS, JOB_ID, COPY_JOB, EXTRACT_JOB, LOAD_JOB, QUERY_JOB, INSERT_ALL_REQUEST, - INSERT_ALL_RESPONSE, FIELD_VALUE}; + INSERT_ALL_RESPONSE, FIELD_VALUE, QUERY_REQUEST, QUERY_RESPONSE}; for (Serializable obj : objects) { Object copy = serializeAndDeserialize(obj); assertEquals(obj, obj); From e16c027164ce0e9efb186fcc6f9798b52007cf3c Mon Sep 17 00:00:00 2001 From: Marco Ziccardi Date: Mon, 14 Dec 2015 22:25:53 +0100 Subject: [PATCH 3/4] Add QueryResult class and tests --- .../google/gcloud/bigquery/QueryResponse.java | 126 +++---------- .../google/gcloud/bigquery/QueryResult.java | 176 ++++++++++++++++++ .../gcloud/bigquery/QueryResponseTest.java | 46 ++--- .../gcloud/bigquery/QueryResultTest.java | 91 +++++++++ .../gcloud/bigquery/SerializationTest.java | 12 +- 5 files changed, 324 insertions(+), 127 deletions(-) create mode 100644 gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResult.java create mode 100644 gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryResultTest.java diff --git a/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResponse.java b/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResponse.java index 2c9257388b12..787047a7a06d 100644 --- a/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResponse.java +++ b/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResponse.java @@ -17,7 +17,6 @@ package com.google.gcloud.bigquery; import com.google.common.base.MoreObjects; -import com.google.gcloud.Page; import java.io.Serializable; import java.util.List; @@ -35,7 +34,12 @@ * Thread.sleep(1000); * } * List executionErrors = response.executionErrors(); - * Page> rows = response.rows(); + * QueryResult result = response.result(); + * Iterator> rowIterator = result.iterateAll(); + * while(rowIterator.hasNext()) { + * List row = rowIterator.next(); + * // do something with row + * } * } * * @see Get Query @@ -46,37 +50,29 @@ public class QueryResponse implements Serializable { private static final long serialVersionUID = 3549226764825005655L; + private final QueryResult result; private final String etag; - private final Schema schema; private final JobId job; - private final Long totalRows; - private final Page> rows; - private final Long totalBytesProcessed; private final boolean jobComplete; private final List executionErrors; - private final Boolean cacheHit; static final class Builder { + private QueryResult result; private String etag; - private Schema schema; private JobId job; - private Long totalRows; - private Page> rows; - private Long totalBytesProcessed; private boolean jobComplete; private List executionErrors; - private Boolean cacheHit; private Builder() {} - Builder etag(String etag) { - this.etag = etag; + Builder result(QueryResult result) { + this.result = result; return this; } - Builder schema(Schema schema) { - this.schema = schema; + Builder etag(String etag) { + this.etag = etag; return this; } @@ -85,21 +81,6 @@ Builder job(JobId job) { return this; } - Builder totalRows(Long totalRows) { - this.totalRows = totalRows; - return this; - } - - Builder rows(Page> rows) { - this.rows = rows; - return this; - } - - Builder totalBytesProcessed(Long totalBytesProcessed) { - this.totalBytesProcessed = totalBytesProcessed; - return this; - } - Builder jobComplete(boolean jobComplete) { this.jobComplete = jobComplete; return this; @@ -110,41 +91,32 @@ Builder executionErrors(List executionErrors) { return this; } - Builder cacheHit(Boolean cacheHit) { - this.cacheHit = cacheHit; - return this; - } - QueryResponse build() { return new QueryResponse(this); } } private QueryResponse(Builder builder) { + this.result = builder.result; this.etag = builder.etag; - this.schema = builder.schema; this.job = builder.job; - this.totalRows = builder.totalRows; - this.rows = builder.rows; - this.totalBytesProcessed = builder.totalBytesProcessed; this.jobComplete = builder.jobComplete; this.executionErrors = builder.executionErrors; - this.cacheHit = builder.cacheHit; } /** - * Returns the hash of the {@code QueryResponse} resource or {@code null} if not set. + * Returns the result of the query. Returns {@code null} if {@link #jobComplete()} is {@code + * false}. */ - public String etag() { - return etag; + public QueryResult result() { + return result; } /** - * Returns the schema of the results if the query completed successfully. Returns {@code null} - * otherwise. + * Returns the hash of the {@code QueryResponse} resource or {@code null} if not set. */ - public Schema schema() { - return schema; + public String etag() { + return etag; } /** @@ -156,36 +128,10 @@ public JobId job() { } /** - * Returns the total number of rows in the complete query result set, which can be more than the - * number of rows in the first page of results returned by {@link #rows()}. Returns {@code null} - * if the query did not complete successfully. - */ - public Long totalRows() { - return totalRows; - } - - /** - * Returns the query result as a paginated list of rows, if the query completed successfully. - * Returns {@code null} otherwise. - */ - public Page> rows() { - return rows; - } - - /** - * Returns the total number of bytes processed for the query. If this query was a dry run, this is - * the number of bytes that would be processed if the query were run. Returns {@code null} - * if the query did not complete. - */ - public Long totalBytesProcessed() { - return totalBytesProcessed; - } - - /** - * Returns whether the job running the query has completed or not. If {@link #rows()} and - * {@link #totalRows()} are not {@code null}, this method will always return {@code true}. If this - * method returns {@code false} {@link #totalRows()} and {@link #rows()} return {@code null}. This - * method can be used to check if query execution completed and results are available. + * Returns whether the job running the query has completed or not. If {@link #result()} is not + * {@code null}, this method will always return {@code true}. If this method returns {@code false} + * {@link #result()} returns {@code null}. This method can be used to check if query execution + * completed and results are available. */ public boolean jobComplete() { return jobComplete; @@ -199,25 +145,14 @@ public List executionErrors() { return executionErrors; } - /** - * Returns whether the query result was fetched from the query cache. - * - * @see Query Caching - */ - public Boolean cacheHit() { - return cacheHit; - } - @Override public String toString() { return MoreObjects.toStringHelper(this) + .add("result", result) + .add("etag", etag) .add("job", job) .add("jobComplete", jobComplete) - .add("totalRows", totalRows) - .add("schema", schema) - .add("totalBytesProcessed", totalBytesProcessed) .add("executionErrors", executionErrors) - .add("cacheHit", cacheHit) .toString(); } @@ -236,13 +171,10 @@ public boolean equals(Object obj) { } QueryResponse response = (QueryResponse) obj; return jobComplete == response.jobComplete - && Objects.equals(schema, response.schema) + && Objects.equals(etag, response.etag) + && Objects.equals(result, response.result) && Objects.equals(job, response.job) - && Objects.equals(totalRows, response.totalRows) - && Objects.equals(rows, response.rows) - && Objects.equals(totalBytesProcessed, response.totalBytesProcessed) - && Objects.equals(executionErrors, response.executionErrors) - && Objects.equals(cacheHit, response.cacheHit); + && Objects.equals(executionErrors, response.executionErrors); } static Builder builder() { diff --git a/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResult.java b/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResult.java new file mode 100644 index 000000000000..16942d249912 --- /dev/null +++ b/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResult.java @@ -0,0 +1,176 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.bigquery; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; +import com.google.gcloud.PageImpl; + +import java.util.List; +import java.util.Objects; + +public class QueryResult extends PageImpl> { + + private static final long serialVersionUID = -4831062717210349818L; + + private final boolean cacheHit; + private final Schema schema; + private final long totalRows; + private final long totalBytesProcessed; + + interface QueryResultsPageFetcher extends PageImpl.NextPageFetcher> { + @Override + QueryResult nextPage(); + } + + static final class Builder { + + private QueryResultsPageFetcher pageFetcher; + private String cursor; + private Iterable> results; + private boolean cacheHit; + private Schema schema; + private long totalRows; + private long totalBytesProcessed; + + private Builder() {} + + Builder cacheHit(boolean cacheHit) { + this.cacheHit = cacheHit; + return this; + } + + Builder schema(Schema schema) { + this.schema = schema; + return this; + } + + Builder totalBytesProcessed(long totalBytesProcessed) { + this.totalBytesProcessed = totalBytesProcessed; + return this; + } + + Builder totalRows(long totalRows) { + this.totalRows = totalRows; + return this; + } + + Builder pageFetcher(QueryResultsPageFetcher pageFetcher) { + this.pageFetcher = pageFetcher; + return this; + }; + + Builder cursor(String cursor) { + this.cursor = cursor; + return this; + }; + + Builder results(Iterable> results) { + this.results = results; + return this; + }; + + QueryResult build() { + return new QueryResult(this); + } + } + + private QueryResult(Builder builder) { + super(builder.pageFetcher, builder.cursor, builder.results != null ? builder.results + : ImmutableList.>of()); + this.cacheHit = builder.cacheHit; + this.schema = builder.schema; + this.totalBytesProcessed = builder.totalBytesProcessed; + this.totalRows = builder.totalRows; + } + + /** + * Returns whether the query result was fetched from the query cache. + * + * @see Query Caching + */ + public boolean cacheHit() { + return cacheHit; + } + + /** + * Returns the schema of the results. + */ + public Schema schema() { + return schema; + } + + /** + * Returns the total number of bytes processed for the query. If this query was a dry run, this is + * the number of bytes that would be processed if the query were run. + */ + public long totalBytesProcessed() { + return totalBytesProcessed; + } + + /** + * Returns the total number of rows in the complete query result set, which can be more than the + * number of rows in the first page of results returned by {@link #values()}. Returns {@code 0} + * if the query was a dry run. + */ + public long totalRows() { + return totalRows; + } + + @Override + public QueryResult nextPage() { + return (QueryResult) super.nextPage(); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("rows", values()) + .add("cacheHit", cacheHit) + .add("schema", schema) + .add("totalBytesProcessed", totalBytesProcessed) + .add("totalRows", totalRows) + .add("cursor", nextPageCursor()) + .toString(); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), cacheHit, schema, totalBytesProcessed, totalRows); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + QueryResult response = (QueryResult) obj; + return Objects.equals(nextPageCursor(), response.nextPageCursor()) + && Objects.equals(values(), response.values()) + && Objects.equals(schema, response.schema) + && totalRows == response.totalRows + && totalBytesProcessed == response.totalBytesProcessed + && cacheHit == response.cacheHit; + } + + static Builder builder() { + return new Builder(); + } +} diff --git a/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryResponseTest.java b/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryResponseTest.java index 2c00e81a9f63..cccf4d12b714 100644 --- a/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryResponseTest.java +++ b/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryResponseTest.java @@ -17,11 +17,10 @@ package com.google.gcloud.bigquery; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import com.google.common.collect.ImmutableList; -import com.google.gcloud.Page; -import com.google.gcloud.PageImpl; import org.junit.Test; @@ -38,15 +37,13 @@ public class QueryResponseTest { private static final Schema SCHEMA = Schema.of(FIELD_SCHEMA1); private static final JobId JOB_ID = JobId.of("project", "job"); private static final Long TOTAL_ROWS = 42L; - private static final PageImpl.NextPageFetcher> FETCHER = - new PageImpl.NextPageFetcher>() { + private static final QueryResult.QueryResultsPageFetcher FETCHER = + new QueryResult.QueryResultsPageFetcher() { @Override - public Page> nextPage() { + public QueryResult nextPage() { return null; } }; - private static final Page> ROWS = - new PageImpl<>(FETCHER, "cursor", ImmutableList.>of()); private static final Long TOTAL_BYTES_PROCESSED = 4200L; private static final Boolean JOB_COMPLETE = true; private static final List ERRORS = ImmutableList.of( @@ -54,43 +51,40 @@ public Page> nextPage() { new BigQueryError("reason2", "location2", "message2", "debugInfo2") ); private static final Boolean CACHE_HIT = false; - private static final QueryResponse QUERY_RESPONSE = QueryResponse.builder() - .etag(ETAG) + private static final QueryResult QUERY_RESULT = QueryResult.builder() .schema(SCHEMA) - .job(JOB_ID) .totalRows(TOTAL_ROWS) - .rows(ROWS) .totalBytesProcessed(TOTAL_BYTES_PROCESSED) + .cursor("cursor") + .pageFetcher(FETCHER) + .results(ImmutableList.>of()) + .cacheHit(CACHE_HIT) + .build(); + private static final QueryResponse QUERY_RESPONSE = QueryResponse.builder() + .etag(ETAG) + .job(JOB_ID) .jobComplete(JOB_COMPLETE) .executionErrors(ERRORS) - .cacheHit(CACHE_HIT) + .result(QUERY_RESULT) .build(); @Test public void testBuilder() { assertEquals(ETAG, QUERY_RESPONSE.etag()); - assertEquals(SCHEMA, QUERY_RESPONSE.schema()); + assertEquals(QUERY_RESULT, QUERY_RESPONSE.result()); assertEquals(JOB_ID, QUERY_RESPONSE.job()); - assertEquals(TOTAL_ROWS, QUERY_RESPONSE.totalRows()); - assertEquals(ROWS, QUERY_RESPONSE.rows()); - assertEquals(TOTAL_BYTES_PROCESSED, (Long) QUERY_RESPONSE.totalBytesProcessed()); assertEquals(JOB_COMPLETE, QUERY_RESPONSE.jobComplete()); assertEquals(ERRORS, QUERY_RESPONSE.executionErrors()); - assertEquals(CACHE_HIT, QUERY_RESPONSE.cacheHit()); } @Test public void testBuilderIncomplete() { QueryResponse queryResponse = QueryResponse.builder().jobComplete(false).build(); assertNull(queryResponse.etag()); - assertNull(queryResponse.schema()); + assertNull(queryResponse.result()); assertNull(queryResponse.job()); - assertNull(queryResponse.totalRows()); - assertNull(queryResponse.rows()); - assertNull(queryResponse.totalBytesProcessed()); - assertEquals(false, queryResponse.jobComplete()); + assertFalse(queryResponse.jobComplete()); assertNull(queryResponse.executionErrors()); - assertNull(queryResponse.cacheHit()); } @Test @@ -101,13 +95,9 @@ public void testEquals() { private void compareQueryResponse(QueryResponse expected, QueryResponse value) { assertEquals(expected, value); assertEquals(expected.etag(), value.etag()); - assertEquals(expected.schema(), value.schema()); + assertEquals(expected.result(), value.result()); assertEquals(expected.job(), value.job()); - assertEquals(expected.totalRows(), value.totalRows()); - assertEquals(expected.rows(), value.rows()); - assertEquals(expected.totalBytesProcessed(), value.totalBytesProcessed()); assertEquals(expected.jobComplete(), value.jobComplete()); assertEquals(expected.executionErrors(), value.executionErrors()); - assertEquals(expected.cacheHit(), value.cacheHit()); } } diff --git a/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryResultTest.java b/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryResultTest.java new file mode 100644 index 000000000000..db2432753b52 --- /dev/null +++ b/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryResultTest.java @@ -0,0 +1,91 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gcloud.bigquery; + +import com.google.common.collect.ImmutableList; + +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class QueryResultTest { + + private static final String CURSOR = "cursor"; + private static final Field FIELD_SCHEMA1 = + Field.builder("StringField", Field.Type.string()) + .mode(Field.Mode.NULLABLE) + .description("FieldDescription1") + .build(); + private static final Schema SCHEMA = Schema.of(FIELD_SCHEMA1); + private static final long TOTAL_ROWS = 42L; + private static final QueryResult.QueryResultsPageFetcher FETCHER = + new QueryResult.QueryResultsPageFetcher() { + @Override + public QueryResult nextPage() { + return null; + } + }; + private static final long TOTAL_BYTES_PROCESSED = 4200L; + private static final boolean CACHE_HIT = false; + private static final QueryResult QUERY_RESULT = QueryResult.builder() + .schema(SCHEMA) + .totalRows(TOTAL_ROWS) + .totalBytesProcessed(TOTAL_BYTES_PROCESSED) + .cursor(CURSOR) + .pageFetcher(FETCHER) + .results(ImmutableList.>of()) + .cacheHit(CACHE_HIT) + .build(); + private static final QueryResult QUERY_RESULT_INCOMPLETE = QueryResult.builder() + .totalBytesProcessed(TOTAL_BYTES_PROCESSED) + .build(); + + @Test + public void testBuilder() { + assertEquals(SCHEMA, QUERY_RESULT.schema()); + assertEquals(TOTAL_ROWS, QUERY_RESULT.totalRows()); + assertEquals(TOTAL_BYTES_PROCESSED, QUERY_RESULT.totalBytesProcessed()); + assertEquals(CACHE_HIT, QUERY_RESULT.cacheHit()); + assertEquals(CURSOR, QUERY_RESULT.nextPageCursor()); + assertEquals(null, QUERY_RESULT.nextPage()); + assertEquals(null, QUERY_RESULT_INCOMPLETE.schema()); + assertEquals(0L, QUERY_RESULT_INCOMPLETE.totalRows()); + assertEquals(TOTAL_BYTES_PROCESSED, QUERY_RESULT_INCOMPLETE.totalBytesProcessed()); + assertEquals(false, QUERY_RESULT_INCOMPLETE.cacheHit()); + assertEquals(null, QUERY_RESULT_INCOMPLETE.nextPageCursor()); + assertEquals(null, QUERY_RESULT_INCOMPLETE.nextPage()); + } + + @Test + public void testEquals() { + compareQueryResult(QUERY_RESULT, QUERY_RESULT); + compareQueryResult(QUERY_RESULT_INCOMPLETE, QUERY_RESULT_INCOMPLETE); + } + + private void compareQueryResult(QueryResult expected, QueryResult value) { + assertEquals(expected, value); + assertEquals(expected.nextPage(), value.nextPage()); + assertEquals(expected.nextPageCursor(), value.nextPageCursor()); + assertEquals(expected.values(), value.values()); + assertEquals(expected.schema(), value.schema()); + assertEquals(expected.totalRows(), value.totalRows()); + assertEquals(expected.totalBytesProcessed(), value.totalBytesProcessed()); + assertEquals(expected.cacheHit(), value.cacheHit()); + } +} diff --git a/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/SerializationTest.java b/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/SerializationTest.java index 20092395565a..b14d4f2920c0 100644 --- a/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/SerializationTest.java +++ b/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/SerializationTest.java @@ -193,11 +193,19 @@ public class SerializationTest { .maxResults(42L) .maxWaitTime(10L) .build(); + private static final QueryResult QUERY_RESULT = QueryResult.builder() + .schema(TABLE_SCHEMA) + .totalRows(1L) + .totalBytesProcessed(42L) + .cursor("cursor") + .pageFetcher(null) + .results(ImmutableList.>of()) + .build(); private static final QueryResponse QUERY_RESPONSE = QueryResponse.builder() .etag(ETAG) - .schema(TABLE_SCHEMA) .job(JOB_ID) - .totalRows(1L) + .jobComplete(true) + .result(QUERY_RESULT) .build(); @Test From c6e53c8cb58550bbfa769ec9666b4f77e945950a Mon Sep 17 00:00:00 2001 From: Marco Ziccardi Date: Mon, 14 Dec 2015 23:48:34 +0100 Subject: [PATCH 4/4] Rename QueryResponse.job to jobId, better javadoc, no errors as empty list, add hasErrors --- .../google/gcloud/bigquery/QueryRequest.java | 2 +- .../google/gcloud/bigquery/QueryResponse.java | 36 ++++++++++++------- .../google/gcloud/bigquery/QueryResult.java | 8 ++--- .../gcloud/bigquery/QueryResponseTest.java | 14 +++++--- .../gcloud/bigquery/SerializationTest.java | 2 +- 5 files changed, 39 insertions(+), 23 deletions(-) diff --git a/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryRequest.java b/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryRequest.java index 83d6ed4164b4..d64994e69c76 100644 --- a/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryRequest.java +++ b/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryRequest.java @@ -27,7 +27,7 @@ * Google Cloud BigQuery Query Request. This class can be used to run a BigQuery SQL query and * return results if the query completes within a specified timeout. The query results are saved to * a temporary table that is deleted approximately 24 hours after the query is run. The query is run - * through a BigQuery Job whose identity can be accessed via {@link QueryResponse#job()}. + * through a BigQuery Job whose identity can be accessed via {@link QueryResponse#jobId()}. * * @see Query * @see Query Reference diff --git a/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResponse.java b/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResponse.java index 787047a7a06d..b7bd5dc0efd0 100644 --- a/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResponse.java +++ b/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResponse.java @@ -17,6 +17,7 @@ package com.google.gcloud.bigquery; import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; import java.io.Serializable; import java.util.List; @@ -30,10 +31,11 @@ *
    {@code
  *    QueryResponse response = bigquery.query(request);
  *    while (!response.jobComplete()) {
- *      response = bigquery.getQueryResults(response.job());
+ *      response = bigquery.getQueryResults(response.jobId());
  *      Thread.sleep(1000);
  *    }
  *    List executionErrors = response.executionErrors();
+ *    // look for errors in executionErrors
  *    QueryResult result = response.result();
  *    Iterator> rowIterator = result.iterateAll();
  *    while(rowIterator.hasNext()) {
@@ -52,7 +54,7 @@ public class QueryResponse implements Serializable {
 
   private final QueryResult result;
   private final String etag;
-  private final JobId job;
+  private final JobId jobId;
   private final boolean jobComplete;
   private final List executionErrors;
 
@@ -60,7 +62,7 @@ static final class Builder {
 
     private QueryResult result;
     private String etag;
-    private JobId job;
+    private JobId jobId;
     private boolean jobComplete;
     private List executionErrors;
 
@@ -76,8 +78,8 @@ Builder etag(String etag) {
       return this;
     }
 
-    Builder job(JobId job) {
-      this.job = job;
+    Builder jobId(JobId jobId) {
+      this.jobId = jobId;
       return this;
     }
 
@@ -99,9 +101,10 @@ QueryResponse build() {
   private QueryResponse(Builder builder) {
     this.result = builder.result;
     this.etag = builder.etag;
-    this.job = builder.job;
+    this.jobId = builder.jobId;
     this.jobComplete = builder.jobComplete;
-    this.executionErrors = builder.executionErrors;
+    this.executionErrors = builder.executionErrors != null ? builder.executionErrors
+      : ImmutableList.of();
   }
 
   /**
@@ -123,8 +126,8 @@ public String etag() {
    * Returns the identity of the BigQuery Job that was created to run the query. This field will be
    * present even if the original request timed out.
    */
-  public JobId job() {
-    return job;
+  public JobId jobId() {
+    return jobId;
   }
 
   /**
@@ -137,6 +140,15 @@ public boolean jobComplete() {
     return jobComplete;
   }
 
+  /**
+   * Returns whether errors and warnings occurred during the execution of the job. If this method
+   * returns {@code true} it does not necessarily mean that the job has completed or was
+   * unsuccessful.
+   */
+  public boolean hasErrors() {
+    return !executionErrors.isEmpty();
+  }
+
   /**
    * Returns errors and warnings encountered during the running of the job, if any. Errors here do
    * not necessarily mean that the job has completed or was unsuccessful.
@@ -150,7 +162,7 @@ public String toString() {
     return MoreObjects.toStringHelper(this)
         .add("result", result)
         .add("etag", etag)
-        .add("job", job)
+        .add("jobId", jobId)
         .add("jobComplete", jobComplete)
         .add("executionErrors", executionErrors)
         .toString();
@@ -158,7 +170,7 @@ public String toString() {
 
   @Override
   public int hashCode() {
-    return Objects.hash(job);
+    return Objects.hash(jobId);
   }
 
   @Override
@@ -173,7 +185,7 @@ public boolean equals(Object obj) {
     return jobComplete == response.jobComplete
         && Objects.equals(etag, response.etag)
         && Objects.equals(result, response.result)
-        && Objects.equals(job, response.job)
+        && Objects.equals(jobId, response.jobId)
         && Objects.equals(executionErrors, response.executionErrors);
   }
 
diff --git a/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResult.java b/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResult.java
index 16942d249912..692abab937a9 100644
--- a/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResult.java
+++ b/gcloud-java-bigquery/src/main/java/com/google/gcloud/bigquery/QueryResult.java
@@ -72,17 +72,17 @@ Builder totalRows(long totalRows) {
     Builder pageFetcher(QueryResultsPageFetcher pageFetcher) {
       this.pageFetcher = pageFetcher;
       return this;
-    };
+    }
 
     Builder cursor(String cursor) {
       this.cursor = cursor;
       return this;
-    };
+    }
 
     Builder results(Iterable> results) {
       this.results = results;
       return this;
-    };
+    }
 
     QueryResult build() {
       return new QueryResult(this);
@@ -108,7 +108,7 @@ public boolean cacheHit() {
   }
 
   /**
-   * Returns the schema of the results.
+   * Returns the schema of the results. This is present only when the query completes successfully.
    */
   public Schema schema() {
     return schema;
diff --git a/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryResponseTest.java b/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryResponseTest.java
index cccf4d12b714..3ecae9b76e18 100644
--- a/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryResponseTest.java
+++ b/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/QueryResponseTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import com.google.common.collect.ImmutableList;
 
@@ -62,7 +63,7 @@ public QueryResult nextPage() {
       .build();
   private static final QueryResponse QUERY_RESPONSE = QueryResponse.builder()
       .etag(ETAG)
-      .job(JOB_ID)
+      .jobId(JOB_ID)
       .jobComplete(JOB_COMPLETE)
       .executionErrors(ERRORS)
       .result(QUERY_RESULT)
@@ -72,9 +73,10 @@ public QueryResult nextPage() {
   public void testBuilder() {
     assertEquals(ETAG, QUERY_RESPONSE.etag());
     assertEquals(QUERY_RESULT, QUERY_RESPONSE.result());
-    assertEquals(JOB_ID, QUERY_RESPONSE.job());
+    assertEquals(JOB_ID, QUERY_RESPONSE.jobId());
     assertEquals(JOB_COMPLETE, QUERY_RESPONSE.jobComplete());
     assertEquals(ERRORS, QUERY_RESPONSE.executionErrors());
+    assertTrue(QUERY_RESPONSE.hasErrors());
   }
 
   @Test
@@ -82,9 +84,10 @@ public void testBuilderIncomplete() {
     QueryResponse queryResponse = QueryResponse.builder().jobComplete(false).build();
     assertNull(queryResponse.etag());
     assertNull(queryResponse.result());
-    assertNull(queryResponse.job());
+    assertNull(queryResponse.jobId());
     assertFalse(queryResponse.jobComplete());
-    assertNull(queryResponse.executionErrors());
+    assertEquals(ImmutableList.of(), queryResponse.executionErrors());
+    assertFalse(queryResponse.hasErrors());
   }
 
   @Test
@@ -96,8 +99,9 @@ private void compareQueryResponse(QueryResponse expected, QueryResponse value) {
     assertEquals(expected, value);
     assertEquals(expected.etag(), value.etag());
     assertEquals(expected.result(), value.result());
-    assertEquals(expected.job(), value.job());
+    assertEquals(expected.jobId(), value.jobId());
     assertEquals(expected.jobComplete(), value.jobComplete());
     assertEquals(expected.executionErrors(), value.executionErrors());
+    assertEquals(expected.hasErrors(), value.hasErrors());
   }
 }
diff --git a/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/SerializationTest.java b/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/SerializationTest.java
index b14d4f2920c0..3576769f4007 100644
--- a/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/SerializationTest.java
+++ b/gcloud-java-bigquery/src/test/java/com/google/gcloud/bigquery/SerializationTest.java
@@ -203,7 +203,7 @@ public class SerializationTest {
       .build();
   private static final QueryResponse QUERY_RESPONSE = QueryResponse.builder()
       .etag(ETAG)
-      .job(JOB_ID)
+      .jobId(JOB_ID)
       .jobComplete(true)
       .result(QUERY_RESULT)
       .build();