Example of running a query to find all entities of one kind. + * + *
{@code + * String kind = "my_kind"; + * StructuredQuery+ * + * @throws DatastoreException upon failure + */ + @BetaApi + defaultquery = Query.newEntityQueryBuilder() + * .setKind(kind) + * .build(); + * QueryResults results = datastore.run(query, ExplainOptions.newBuilder().setAnalyze(true).build()); + * }
Example of running an {@link AggregationQuery} to find the count of entities of one kind. + * + *
{@link StructuredQuery} example: + * + *
{@code + * EntityQuery selectAllQuery = Query.newEntityQueryBuilder() + * .setKind("Task") + * .build(); + * AggregationQuery aggregationQuery = Query.newAggregationQueryBuilder() + * .addAggregation(count().as("total_count")) + * .over(selectAllQuery) + * .build(); + * AggregationResults aggregationResults = datastore.runAggregation(aggregationQuery, ExplainOptions.newBuilder().setAnalyze(true).build()); + * }+ * + * @throws DatastoreException upon failure + * @return {@link AggregationResults} + */ + @BetaApi + default AggregationResults runAggregation( + AggregationQuery query, ExplainOptions explainOptions, ReadOption... options) { + throw new UnsupportedOperationException("Not implemented."); + } } diff --git a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/DatastoreImpl.java b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/DatastoreImpl.java index a1b337c05..a3bfb3796 100644 --- a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/DatastoreImpl.java +++ b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/DatastoreImpl.java @@ -16,6 +16,7 @@ package com.google.cloud.datastore; +import com.google.api.core.BetaApi; import com.google.api.gax.retrying.RetrySettings; import com.google.cloud.BaseService; import com.google.cloud.ExceptionHandler; @@ -30,6 +31,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; +import com.google.datastore.v1.ExplainOptions; import com.google.datastore.v1.ReadOptions; import com.google.datastore.v1.ReserveIdsRequest; import com.google.datastore.v1.TransactionOptions; @@ -182,28 +184,54 @@ public
> { Q query; ListreadOptions; + ExplainOptions explainOptions; - private QueryConfig(Q query, List readOptions) { + private QueryConfig(Q query, ExplainOptions explainOptions, List readOptions) { this.query = query; + this.explainOptions = explainOptions; this.readOptions = readOptions; } - private QueryConfig(Q query) { - this.query = query; - this.readOptions = Collections.emptyList(); + private QueryConfig(Q query, ExplainOptions explainOptions) { + this(query, explainOptions, Collections.emptyList()); } public Q getQuery() { return query; } + public ExplainOptions getExplainOptions() { + return this.explainOptions; + } + public List getReadOptions() { return readOptions; } - public static > QueryConfigcreate(Q query) { - return new QueryConfig<>(query); + public static> QueryConfigcreate( + Q query, ExplainOptions explainOptions) { + return new QueryConfig<>(query, explainOptions); } public static> QueryConfigcreate( - Q query, ListreadOptions) { - return new QueryConfig<>(query, readOptions); + Q query, ExplainOptions explainOptions, List readOptions) { + return new QueryConfig<>(query, explainOptions, readOptions); } } } diff --git a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/Transaction.java b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/Transaction.java index 69c18d75c..7b6a67a2d 100644 --- a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/Transaction.java +++ b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/Transaction.java @@ -16,6 +16,8 @@ package com.google.cloud.datastore; +import com.google.api.core.BetaApi; +import com.google.cloud.datastore.models.ExplainOptions; import com.google.protobuf.ByteString; import java.util.Iterator; import java.util.List; @@ -176,6 +178,11 @@ interface Response { @Override QueryResults run(Query query); + @BetaApi + default QueryResults run(Query query, ExplainOptions explainOptions) { + throw new UnsupportedOperationException("Not implemented."); + } + /** * Datastore add operation. This method will also allocate id for any entity with an incomplete * key. As opposed to {@link #add(FullEntity)} and {@link #add(FullEntity...)}, this method will diff --git a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/TransactionImpl.java b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/TransactionImpl.java index 3b5e5e4e8..f08a908ec 100644 --- a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/TransactionImpl.java +++ b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/TransactionImpl.java @@ -18,6 +18,8 @@ import static com.google.cloud.datastore.ReadOption.transactionId; +import com.google.api.core.BetaApi; +import com.google.cloud.datastore.models.ExplainOptions; import com.google.common.collect.ImmutableList; import com.google.datastore.v1.ReadOptions; import com.google.datastore.v1.TransactionOptions; @@ -102,7 +104,16 @@ public QueryResults run(Query query) { validateActive(); Optional readOptions = this.readOptionProtoPreparer.prepare(ImmutableList.of(transactionId(transactionId))); - return datastore.run(readOptions, query); + return datastore.run(readOptions, query, null); + } + + @Override + @BetaApi + public QueryResults run(Query query, ExplainOptions explainOptions) { + validateActive(); + Optional readOptions = + this.readOptionProtoPreparer.prepare(ImmutableList.of(transactionId(transactionId))); + return datastore.run(readOptions, query, explainOptions.toPb()); } @Override @@ -110,6 +121,12 @@ public AggregationResults runAggregation(AggregationQuery query) { return datastore.runAggregation(query, transactionId(transactionId)); } + @Override + @BetaApi + public AggregationResults runAggregation(AggregationQuery query, ExplainOptions explainOptions) { + return datastore.runAggregation(query, explainOptions, transactionId(transactionId)); + } + @Override public Transaction.Response commit() { validateActive(); diff --git a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/execution/AggregationQueryExecutor.java b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/execution/AggregationQueryExecutor.java index 5a1fdd2c3..bedcf34e4 100644 --- a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/execution/AggregationQueryExecutor.java +++ b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/execution/AggregationQueryExecutor.java @@ -23,6 +23,7 @@ import com.google.cloud.datastore.ReadOption.QueryConfig; import com.google.cloud.datastore.execution.request.AggregationQueryRequestProtoPreparer; import com.google.cloud.datastore.execution.response.AggregationQueryResponseTransformer; +import com.google.cloud.datastore.models.ExplainOptions; import com.google.cloud.datastore.spi.v1.DatastoreRpc; import com.google.datastore.v1.RunAggregationQueryRequest; import com.google.datastore.v1.RunAggregationQueryResponse; @@ -47,20 +48,24 @@ public AggregationQueryExecutor(DatastoreRpc datastoreRpc, DatastoreOptions data } @Override - public AggregationResults execute(AggregationQuery query, ReadOption... readOptions) { + public AggregationResults execute( + AggregationQuery query, ExplainOptions explainOptions, ReadOption... readOptions) { RunAggregationQueryRequest runAggregationQueryRequest = - getRunAggregationQueryRequest(query, readOptions); + getRunAggregationQueryRequest( + query, explainOptions == null ? null : explainOptions.toPb(), readOptions); RunAggregationQueryResponse runAggregationQueryResponse = this.datastoreRpc.runAggregationQuery(runAggregationQueryRequest); return this.responseTransformer.transform(runAggregationQueryResponse); } private RunAggregationQueryRequest getRunAggregationQueryRequest( - AggregationQuery query, ReadOption... readOptions) { + AggregationQuery query, + com.google.datastore.v1.ExplainOptions explainOptions, + ReadOption... readOptions) { QueryConfig queryConfig = readOptions == null - ? QueryConfig.create(query) - : QueryConfig.create(query, Arrays.asList(readOptions)); + ? QueryConfig.create(query, explainOptions) + : QueryConfig.create(query, explainOptions, Arrays.asList(readOptions)); return this.protoPreparer.prepare(queryConfig); } } diff --git a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/execution/QueryExecutor.java b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/execution/QueryExecutor.java index 856c64a02..60c75e0f8 100644 --- a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/execution/QueryExecutor.java +++ b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/execution/QueryExecutor.java @@ -18,6 +18,7 @@ import com.google.api.core.InternalApi; import com.google.cloud.datastore.Query; import com.google.cloud.datastore.ReadOption; +import com.google.cloud.datastore.models.ExplainOptions; /** * An internal functional interface whose implementation has the responsibility to execute a {@link @@ -34,7 +35,9 @@ public interface QueryExecutor, OUTPUT> { /** * @param query A {@link Query} to execute. + * @param explainOptions {@link com.google.cloud.datastore.models.ExplainOptions}s to be used when + * executing {@link Query}. * @param readOptions Optional {@link ReadOption}s to be used when executing {@link Query}. */ - OUTPUT execute(INPUT query, ReadOption... readOptions); + OUTPUT execute(INPUT query, ExplainOptions explainOptions, ReadOption... readOptions); } diff --git a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/execution/request/AggregationQueryRequestProtoPreparer.java b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/execution/request/AggregationQueryRequestProtoPreparer.java index 475a47b58..a89ebb11a 100644 --- a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/execution/request/AggregationQueryRequestProtoPreparer.java +++ b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/execution/request/AggregationQueryRequestProtoPreparer.java @@ -67,7 +67,9 @@ public RunAggregationQueryRequest prepare(QueryConfig queryCon } else { aggregationQueryRequestBuilder.setAggregationQuery(getAggregationQuery(aggregationQuery)); } - + if (queryConfig.getExplainOptions() != null) { + aggregationQueryRequestBuilder.setExplainOptions(queryConfig.getExplainOptions()); + } Optional readOptionsPb = readOptionProtoPreparer.prepare(readOptions); readOptionsPb.ifPresent(aggregationQueryRequestBuilder::setReadOptions); return aggregationQueryRequestBuilder.build(); diff --git a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/execution/response/AggregationQueryResponseTransformer.java b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/execution/response/AggregationQueryResponseTransformer.java index 8c99fcd41..71f7453cb 100644 --- a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/execution/response/AggregationQueryResponseTransformer.java +++ b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/execution/response/AggregationQueryResponseTransformer.java @@ -19,6 +19,7 @@ import com.google.cloud.Timestamp; import com.google.cloud.datastore.AggregationResult; import com.google.cloud.datastore.AggregationResults; +import com.google.cloud.datastore.models.ExplainMetrics; import com.google.datastore.v1.RunAggregationQueryResponse; import com.google.datastore.v1.Value; import java.util.AbstractMap.SimpleEntry; @@ -40,7 +41,11 @@ public AggregationResults transform(RunAggregationQueryResponse response) { response.getBatch().getAggregationResultsList().stream() .map(aggregationResult -> new AggregationResult(transformValues(aggregationResult))) .collect(Collectors.toCollection(LinkedList::new)); - return new AggregationResults(aggregationResults, readTime); + ExplainMetrics explainMetrics = null; + if (response.hasExplainMetrics()) { + explainMetrics = new ExplainMetrics(response.getExplainMetrics()); + } + return new AggregationResults(aggregationResults, readTime, explainMetrics); } private Map > transformValues( diff --git a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/models/ExecutionStats.java b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/models/ExecutionStats.java new file mode 100644 index 000000000..52184a01a --- /dev/null +++ b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/models/ExecutionStats.java @@ -0,0 +1,84 @@ +/* + * Copyright 2024 Google LLC + * + * 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 + * + * https://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.cloud.datastore.models; + +import com.google.api.core.BetaApi; +import com.google.api.core.InternalApi; +import com.google.cloud.Structs; +import com.google.common.base.Objects; +import java.util.Map; +import org.threeten.bp.Duration; + +/** Model class for {@link com.google.datastore.v1.ExecutionStats} */ +@BetaApi +public class ExecutionStats { + private final long resultsReturned; + private final Duration executionDuration; + private final long readOperations; + private final Map debugStats; + + @InternalApi + public ExecutionStats(com.google.datastore.v1.ExecutionStats proto) { + this.resultsReturned = proto.getResultsReturned(); + this.executionDuration = Duration.ofNanos(proto.getExecutionDuration().getNanos()); + this.readOperations = proto.getReadOperations(); + this.debugStats = Structs.asMap(proto.getDebugStats()); + } + + /** + * Returns the total number of results returned, including documents, projections, aggregation + * results, keys. + */ + public long getResultsReturned() { + return resultsReturned; + } + + /** Returns the debugging statistics from the execution of the query. */ + public Map getDebugStats() { + return debugStats; + } + + /** Returns the total time to execute the query in the backend. */ + public Duration getExecutionDuration() { + return executionDuration; + } + + /** Returns the total billable read operations. */ + public long getReadOperations() { + return readOperations; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ExecutionStats)) { + return false; + } + ExecutionStats that = (ExecutionStats) o; + + return Objects.equal(resultsReturned, that.resultsReturned) + && Objects.equal(executionDuration, that.executionDuration) + && Objects.equal(readOperations, that.readOperations) + && Objects.equal(debugStats, that.debugStats); + } + + @Override + public int hashCode() { + return Objects.hashCode(resultsReturned, executionDuration, readOperations, debugStats); + } +} diff --git a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/models/ExplainMetrics.java b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/models/ExplainMetrics.java new file mode 100644 index 000000000..75fd94d61 --- /dev/null +++ b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/models/ExplainMetrics.java @@ -0,0 +1,63 @@ +/* + * Copyright 2024 Google LLC + * + * 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 + * + * https://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.cloud.datastore.models; + +import com.google.api.core.BetaApi; +import com.google.api.core.InternalApi; +import com.google.common.base.Objects; +import java.util.Optional; + +/** Model class for {@link com.google.datastore.v1.ExplainMetrics}. */ +@BetaApi +public class ExplainMetrics { + private final PlanSummary planSummary; + private ExecutionStats executionStats; + + @InternalApi + public ExplainMetrics(com.google.datastore.v1.ExplainMetrics proto) { + if (proto.hasExecutionStats()) { + this.executionStats = new ExecutionStats(proto.getExecutionStats()); + } + this.planSummary = new PlanSummary(proto.getPlanSummary()); + } + + /** Returns the planning phase information for the query. */ + public PlanSummary getPlanSummary() { + return planSummary; + } + + /** + * Returns the aggregated stats from the execution of the query, if present. Only present when + * 'analyze' is set to true for {@link ExplainOptions}. + */ + public Optional getExecutionStats() { + return Optional.ofNullable(executionStats); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ExplainMetrics)) return false; + ExplainMetrics that = (ExplainMetrics) o; + return Objects.equal(planSummary, that.planSummary) + && Objects.equal(executionStats, that.executionStats); + } + + @Override + public int hashCode() { + return Objects.hashCode(planSummary, executionStats); + } +} diff --git a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/models/ExplainOptions.java b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/models/ExplainOptions.java new file mode 100644 index 000000000..f339a8510 --- /dev/null +++ b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/models/ExplainOptions.java @@ -0,0 +1,86 @@ +/* + * Copyright 2024 Google LLC + * + * 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 + * + * https://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.cloud.datastore.models; + +import com.google.api.core.BetaApi; +import com.google.api.core.InternalApi; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Objects; + +/** + * Model class for {@link com.google.datastore.v1.ExplainOptions}. Contains the explain options for + * the query. Analyze is set to 'false' by default. + */ +@BetaApi +public class ExplainOptions { + private final com.google.datastore.v1.ExplainOptions proto; + + private ExplainOptions(com.google.datastore.v1.ExplainOptions proto) { + this.proto = proto; + } + + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Returns whether analyze is set to true or false. When false (the default), the query will be + * planned, returning only metrics from the planning stages. When true, the query will be planned + * and executed, returning the full query results along with both planning and execution stage + * metrics. + */ + public boolean shouldAnalyze() { + return proto.getAnalyze(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ExplainOptions)) return false; + ExplainOptions that = (ExplainOptions) o; + return proto.equals(that.proto); + } + + @Override + public int hashCode() { + return Objects.hashCode(proto); + } + + @InternalApi + @VisibleForTesting + public com.google.datastore.v1.ExplainOptions toPb() { + return this.proto; + } + + public static class Builder { + private boolean analyze = false; + + /* + * Set 'analyze' to true or false for the explain options. + * When false (the default), the query will be planned, returning only metrics from the planning stages. + * When true, the query will be planned and executed, returning the full query results along with both planning and execution stage metrics. + */ + public Builder setAnalyze(boolean analyze) { + this.analyze = analyze; + return this; + } + + public ExplainOptions build() { + return new ExplainOptions( + com.google.datastore.v1.ExplainOptions.newBuilder().setAnalyze(this.analyze).build()); + } + } +} diff --git a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/models/PlanSummary.java b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/models/PlanSummary.java new file mode 100644 index 000000000..ea9e87bd9 --- /dev/null +++ b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/models/PlanSummary.java @@ -0,0 +1,54 @@ +/* + * Copyright 2024 Google LLC + * + * 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 + * + * https://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.cloud.datastore.models; + +import com.google.api.core.BetaApi; +import com.google.api.core.InternalApi; +import com.google.cloud.Structs; +import com.google.common.base.Objects; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** Model class for {@link com.google.datastore.v1.PlanSummary} */ +@BetaApi +public class PlanSummary { + private final List