Example of running a query to find all entities of one kind. + * + *
{@code
+ * String kind = "my_kind";
+ * StructuredQuery query = Query.newEntityQueryBuilder()
+ * .setKind(kind)
+ * .build();
+ * QueryResults results = datastore.run(query, QueryMode.EXPLAIN_ANALYZE);
+ * ResultSetStats resultSetStats = results.getResultSetStats();
+ * }
+ *
+ * @throws DatastoreException upon failure
+ */
+ @BetaApi
+ default 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, QueryMode.EXPLAIN_ANALYZE);
+ * ResultSetStats aggregationStats = aggregationResults.getResultSetStats();
+ * }
+ *
+ * @throws DatastoreException upon failure
+ * @return {@link AggregationResults}
+ */
+ @BetaApi
+ default AggregationResults runAggregation(
+ AggregationQuery query, QueryProfile.QueryMode queryMode, 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..795fe4215 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;
@@ -23,6 +24,7 @@
import com.google.cloud.RetryHelper.RetryHelperException;
import com.google.cloud.ServiceOptions;
import com.google.cloud.datastore.execution.AggregationQueryExecutor;
+import com.google.cloud.datastore.models.QueryProfile;
import com.google.cloud.datastore.spi.v1.DatastoreRpc;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
@@ -30,6 +32,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
+import com.google.datastore.v1.QueryMode;
import com.google.datastore.v1.ReadOptions;
import com.google.datastore.v1.ReserveIdsRequest;
import com.google.datastore.v1.TransactionOptions;
@@ -182,28 +185,50 @@ public > { Q query; ListreadOptions; + QueryMode queryMode; - private QueryConfig(Q query, List readOptions) { + private QueryConfig(Q query, QueryMode queryMode, List readOptions) { this.query = query; + this.queryMode = queryMode; this.readOptions = readOptions; } - private QueryConfig(Q query) { + private QueryConfig(Q query, QueryMode queryMode) { this.query = query; this.readOptions = Collections.emptyList(); + this.queryMode = queryMode; } public Q getQuery() { return query; } + public QueryMode getQueryMode() { + return this.queryMode; + } + public List getReadOptions() { return readOptions; } - public static > QueryConfigcreate(Q query) { - return new QueryConfig<>(query); + public static> QueryConfigcreate(Q query, QueryMode queryMode) { + return new QueryConfig<>(query, queryMode); } public static> QueryConfigcreate( - Q query, ListreadOptions) { - return new QueryConfig<>(query, readOptions); + Q query, QueryMode queryMode, List readOptions) { + return new QueryConfig<>(query, queryMode, 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..c7b08a4d0 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.QueryProfile.QueryMode; 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, QueryMode queryMode) { + 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..3301b0c0d 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,7 +18,10 @@ import static com.google.cloud.datastore.ReadOption.transactionId; +import com.google.api.core.BetaApi; +import com.google.cloud.datastore.models.QueryProfile; import com.google.common.collect.ImmutableList; +import com.google.datastore.v1.QueryMode; import com.google.datastore.v1.ReadOptions; import com.google.datastore.v1.TransactionOptions; import com.google.protobuf.ByteString; @@ -102,7 +105,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, QueryMode.NORMAL); + } + + @Override + @BetaApi + public QueryResults run(Query query, QueryProfile.QueryMode queryMode) { + validateActive(); + Optional readOptions = + this.readOptionProtoPreparer.prepare(ImmutableList.of(transactionId(transactionId))); + return datastore.run(readOptions, query, queryMode.toPb()); } @Override @@ -110,6 +122,12 @@ public AggregationResults runAggregation(AggregationQuery query) { return datastore.runAggregation(query, transactionId(transactionId)); } + @Override + public AggregationResults runAggregation( + AggregationQuery query, QueryProfile.QueryMode queryMode) { + return datastore.runAggregation(query, queryMode, 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..d02b4d842 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,7 +23,9 @@ 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.QueryProfile; import com.google.cloud.datastore.spi.v1.DatastoreRpc; +import com.google.datastore.v1.QueryMode; import com.google.datastore.v1.RunAggregationQueryRequest; import com.google.datastore.v1.RunAggregationQueryResponse; import java.util.Arrays; @@ -47,20 +49,21 @@ public AggregationQueryExecutor(DatastoreRpc datastoreRpc, DatastoreOptions data } @Override - public AggregationResults execute(AggregationQuery query, ReadOption... readOptions) { + public AggregationResults execute( + AggregationQuery query, QueryProfile.QueryMode queryMode, ReadOption... readOptions) { RunAggregationQueryRequest runAggregationQueryRequest = - getRunAggregationQueryRequest(query, readOptions); + getRunAggregationQueryRequest(query, queryMode.toPb(), readOptions); RunAggregationQueryResponse runAggregationQueryResponse = this.datastoreRpc.runAggregationQuery(runAggregationQueryRequest); return this.responseTransformer.transform(runAggregationQueryResponse); } private RunAggregationQueryRequest getRunAggregationQueryRequest( - AggregationQuery query, ReadOption... readOptions) { + AggregationQuery query, QueryMode queryMode, ReadOption... readOptions) { QueryConfig queryConfig = readOptions == null - ? QueryConfig.create(query) - : QueryConfig.create(query, Arrays.asList(readOptions)); + ? QueryConfig.create(query, queryMode) + : QueryConfig.create(query, queryMode, 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..3d5f6b15b 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.QueryProfile; /** * 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 queryMode {@link com.google.cloud.datastore.models.QueryProfile.QueryMode}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, QueryProfile.QueryMode queryMode, 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..2d85682f4 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,7 @@ public RunAggregationQueryRequest prepare(QueryConfig queryCon } else { aggregationQueryRequestBuilder.setAggregationQuery(getAggregationQuery(aggregationQuery)); } - + aggregationQueryRequestBuilder.setMode(queryConfig.getQueryMode()); 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..1506d5456 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.ResultSetStats; 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); + ResultSetStats stats = null; + if (response.getStats().hasQueryStats() || response.getStats().hasQueryPlan()) { + stats = new ResultSetStats(response.getStats()); + } + return new AggregationResults(aggregationResults, readTime, stats); } private Map > transformValues( diff --git a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/models/QueryPlan.java b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/models/QueryPlan.java new file mode 100644 index 000000000..6ec98069d --- /dev/null +++ b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/models/QueryPlan.java @@ -0,0 +1,57 @@ +/* + * Copyright 2023 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.InternalApi; +import com.google.cloud.Structs; +import com.google.common.base.Objects; +import java.util.Map; + +/* + * Class to model the returned plan for the query. This includes plan info - a map containing phase information for the query. + */ +public class QueryPlan { + private final Map planInfo; + + @InternalApi + public QueryPlan(com.google.datastore.v1.QueryPlan proto) { + this.planInfo = Structs.asMap(proto.getPlanInfo()); + } + + /* + * Returns the plan info as a map. + */ + public Map getPlanInfo() { + return this.planInfo; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof QueryPlan)) { + return false; + } + QueryPlan queryPlan = (QueryPlan) o; + return Objects.equal(planInfo, queryPlan.planInfo); + } + + @Override + public int hashCode() { + return Objects.hashCode(planInfo); + } +} diff --git a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/models/QueryProfile.java b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/models/QueryProfile.java new file mode 100644 index 000000000..4c9262f29 --- /dev/null +++ b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/models/QueryProfile.java @@ -0,0 +1,59 @@ +/* + * Copyright 2023 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.InternalApi; +import com.google.common.annotations.VisibleForTesting; + +public class QueryProfile { + + /* + * Model enums for query mode. + */ + public enum QueryMode { + /* + * The default mode. Only the query results are returned. + */ + NORMAL(com.google.datastore.v1.QueryMode.NORMAL), + + /* + * This mode returns only the query plan, without any results or execution statistics information. + */ + EXPLAIN(com.google.datastore.v1.QueryMode.PLAN), + + /* + * This mode returns both the query plan and the execution statistics along with the results. + */ + EXPLAIN_ANALYZE(com.google.datastore.v1.QueryMode.PROFILE), + + /* + * Unrecognized enum specified; please update your client. + */ + UNRECOGZNIED(com.google.datastore.v1.QueryMode.UNRECOGNIZED); + + private final com.google.datastore.v1.QueryMode proto; + + QueryMode(com.google.datastore.v1.QueryMode proto) { + this.proto = proto; + } + + @InternalApi + @VisibleForTesting + public com.google.datastore.v1.QueryMode toPb() { + return this.proto; + } + } +} diff --git a/google-cloud-datastore/src/main/java/com/google/cloud/datastore/models/ResultSetStats.java b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/models/ResultSetStats.java new file mode 100644 index 000000000..ea21e5635 --- /dev/null +++ b/google-cloud-datastore/src/main/java/com/google/cloud/datastore/models/ResultSetStats.java @@ -0,0 +1,77 @@ +/* + * Copyright 2023 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.InternalApi; +import com.google.cloud.Structs; +import com.google.common.base.Objects; +import java.util.Map; +import java.util.Optional; + +/* + * Model class containing the planning and execution stats for the query. If QueryMode.EXPLAIN_ANALYZE was set, + * QueryPlan and QueryStats will be present. If QueryMode.EXPLAIN was set, QueryPlan will be present and QueryStats will return null. + */ +public class ResultSetStats { + + private QueryPlan queryPlan = null; + private Map queryStats = null; + + @InternalApi + public ResultSetStats(com.google.datastore.v1.ResultSetStats proto) { + if (proto.hasQueryPlan()) { + this.queryPlan = new QueryPlan(proto.getQueryPlan()); + } + if (proto.hasQueryStats()) { + this.queryStats = Structs.asMap(proto.getQueryStats()); + } + } + + /* + * Returns the plan for the query. + */ + public QueryPlan getQueryPlan() { + return this.queryPlan; + } + + /* + * Returns the stats for the query if EXPLAIN_ANALYZE was set. Otherwise, returns null. + */ + public Optional