From 5e031a46468803c6ea2f39e329edb835956d82aa Mon Sep 17 00:00:00 2001 From: Ehsan Nasiri Date: Mon, 24 Jul 2023 19:10:21 -0700 Subject: [PATCH 1/3] WIP: Query profiling APIs. --- .../cloud/firestore/AggregateQuery.java | 40 ++++++++++ .../firestore/AggregateQueryProfileInfo.java | 31 ++++++++ .../com/google/cloud/firestore/Query.java | 40 ++++++++++ .../cloud/firestore/QueryProfileInfo.java | 28 +++++++ .../cloud/firestore/it/ITQueryTest.java | 77 +++++++++++++++++++ 5 files changed, 216 insertions(+) create mode 100644 google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateQueryProfileInfo.java create mode 100644 google-cloud-firestore/src/main/java/com/google/cloud/firestore/QueryProfileInfo.java diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateQuery.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateQuery.java index e99dce925..c3b62185b 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateQuery.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateQuery.java @@ -31,6 +31,8 @@ import com.google.firestore.v1.StructuredAggregationQuery; import com.google.firestore.v1.Value; import com.google.protobuf.ByteString; +import java.util.HashMap; +import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nonnull; @@ -69,6 +71,44 @@ public ApiFuture get() { return get(null); } + /** + * Performs the planning stage of this query, without actually executing the query. Returns an + * ApiFuture that will be resolved with the result of the query planning information. + * + *

Note: the information included in the output of this function is subject to change. + * + * @return An ApiFuture that will be resolved with the results of the query planning information. + */ + @Nonnull + public ApiFuture> plan() { + Map plan = new HashMap<>(); + plan.put("foo", "bar"); + final SettableApiFuture> result = SettableApiFuture.create(); + result.set(plan); + return result; + } + + /** + * Plans and executes this query. Returns an ApiFuture that will be resolved with the planner + * information, statistics from the query execution, and the query results. + * + *

Note: the information included in the output of this function is subject to change. + * + * @return An ApiFuture that will be resolved with the planner information, statistics from the + * query execution, and the query results. + */ + @Nonnull + public ApiFuture profile() { + Map plan = new HashMap<>(); + plan.put("foo", "bar"); + Map stats = new HashMap<>(); + stats.put("cpu", "3ms"); + final SettableApiFuture result = SettableApiFuture.create(); + AggregateQueryProfileInfo mock = new AggregateQueryProfileInfo(plan, stats, null); + result.set(mock); + return result; + } + @Nonnull ApiFuture get(@Nullable final ByteString transactionId) { AggregateQueryResponseDeliverer responseDeliverer = diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateQueryProfileInfo.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateQueryProfileInfo.java new file mode 100644 index 000000000..918cabbc5 --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateQueryProfileInfo.java @@ -0,0 +1,31 @@ +package com.google.cloud.firestore; + +import java.util.Map; +import javax.annotation.Nonnull; + +/** + * An AggregateQueryProfileInfo contains information about planning, execution, and results of an + * aggregate query. + */ +public class AggregateQueryProfileInfo { + /** A Map that contains information about the query plan. Contents are subject to change. */ + @Nonnull public final Map plan; + + /** + * A Map that contains statistics about the execution of the aggregate query. Contents are subject + * to change. + */ + @Nonnull public final Map stats; + + /** The snapshot that contains the results of executing the aggregate query. */ + @Nonnull public final AggregateQuerySnapshot snapshot; + + public AggregateQueryProfileInfo( + @Nonnull Map plan, + @Nonnull Map stats, + @Nonnull AggregateQuerySnapshot snapshot) { + this.plan = plan; + this.stats = stats; + this.snapshot = snapshot; + } +} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java index 6dd52d8fd..2fbff8776 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java @@ -63,8 +63,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; @@ -1702,6 +1704,44 @@ public ApiFuture get() { return get(null); } + /** + * Performs the planning stage of this query, without actually executing the query. Returns an + * ApiFuture that will be resolved with the result of the query planning information. + * + *

Note: the information included in the output of this function is subject to change. + * + * @return An ApiFuture that will be resolved with the results of the query planning information. + */ + @Nonnull + public ApiFuture> plan() { + Map plan = new HashMap<>(); + plan.put("foo", "bar"); + final SettableApiFuture> result = SettableApiFuture.create(); + result.set(plan); + return result; + } + + /** + * Plans and executes this query. Returns an ApiFuture that will be resolved with the planning + * information, statistics from the query execution, and the query results. + * + *

Note: the information included in the output of this function is subject to change. + * + * @return An ApiFuture that will be resolved with the planner information, statistics from the + * query execution, and the query results. + */ + @Nonnull + public ApiFuture profile() { + Map plan = new HashMap<>(); + plan.put("foo", "bar"); + Map stats = new HashMap<>(); + stats.put("cpu", "3ms"); + final SettableApiFuture result = SettableApiFuture.create(); + QueryProfileInfo mock = new QueryProfileInfo(plan, stats, null); + result.set(mock); + return result; + } + /** * Starts listening to this query. * diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/QueryProfileInfo.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/QueryProfileInfo.java new file mode 100644 index 000000000..48f383653 --- /dev/null +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/QueryProfileInfo.java @@ -0,0 +1,28 @@ +package com.google.cloud.firestore; + +import java.util.Map; +import javax.annotation.Nonnull; + +/** A QueryProfile contains information about planning, execution, and results of a query. */ +public class QueryProfileInfo { + /** A Map that contains information about the query plan. Contents are subject to change. */ + @Nonnull public final Map plan; + + /** + * A Map that contains statistics about the execution of the query. Contents are subject to + * change. + */ + @Nonnull public final Map stats; + + /** The snapshot that contains the results of executing the query. */ + @Nonnull public final QuerySnapshot snapshot; + + public QueryProfileInfo( + @Nonnull Map plan, + @Nonnull Map stats, + @Nonnull QuerySnapshot snapshot) { + this.plan = plan; + this.stats = stats; + this.snapshot = snapshot; + } +} diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryTest.java index 9707cb25e..7012dccaf 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryTest.java @@ -22,6 +22,8 @@ import static org.junit.Assume.assumeTrue; import com.google.api.client.util.Preconditions; +import com.google.cloud.firestore.AggregateQuery; +import com.google.cloud.firestore.AggregateQueryProfileInfo; import com.google.cloud.firestore.CollectionReference; import com.google.cloud.firestore.Filter; import com.google.cloud.firestore.Firestore; @@ -29,6 +31,7 @@ import com.google.cloud.firestore.LocalFirestoreHelper; import com.google.cloud.firestore.Query; import com.google.cloud.firestore.Query.Direction; +import com.google.cloud.firestore.QueryProfileInfo; import com.google.cloud.firestore.QuerySnapshot; import java.util.Arrays; import java.util.LinkedHashMap; @@ -415,4 +418,78 @@ public void testOrderByEquality() Query query2 = collection.where(Filter.inArray("a", asList(2, 3))).orderBy("a"); checkQuerySnapshotContainsDocuments(query2, "doc6", "doc3"); } + + @Test + public void testQueryPlan() throws ExecutionException, InterruptedException, TimeoutException { + Map> testDocs = + map( + "doc1", map("a", 1, "b", asList(0)), + "doc2", map("b", asList(1)), + "doc3", map("a", 3, "b", asList(2, 7), "c", 10), + "doc4", map("a", 1, "b", asList(3, 7)), + "doc5", map("a", 1), + "doc6", map("a", 2, "c", 20)); + CollectionReference collection = testCollectionWithDocs(testDocs); + + Query query = collection.where(Filter.equalTo("a", 1)).orderBy("a"); + Map plan = query.plan().get(); + System.out.println(plan); + } + + @Test + public void testQueryProfile() throws ExecutionException, InterruptedException, TimeoutException { + Map> testDocs = + map( + "doc1", map("a", 1, "b", asList(0)), + "doc2", map("b", asList(1)), + "doc3", map("a", 3, "b", asList(2, 7), "c", 10), + "doc4", map("a", 1, "b", asList(3, 7)), + "doc5", map("a", 1), + "doc6", map("a", 2, "c", 20)); + CollectionReference collection = testCollectionWithDocs(testDocs); + + Query query = collection.where(Filter.equalTo("a", 1)).orderBy("a"); + + QueryProfileInfo profile = query.profile().get(); + System.out.println(profile.plan); + System.out.println(profile.stats); + } + + @Test + public void testAggregateQueryPlan() + throws ExecutionException, InterruptedException, TimeoutException { + Map> testDocs = + map( + "doc1", map("a", 1, "b", asList(0)), + "doc2", map("b", asList(1)), + "doc3", map("a", 3, "b", asList(2, 7), "c", 10), + "doc4", map("a", 1, "b", asList(3, 7)), + "doc5", map("a", 1), + "doc6", map("a", 2, "c", 20)); + CollectionReference collection = testCollectionWithDocs(testDocs); + + AggregateQuery query = collection.where(Filter.equalTo("a", 1)).orderBy("a").count(); + Map plan = query.plan().get(); + System.out.println(plan); + } + + @Test + public void testAggregateQueryProfile() + throws ExecutionException, InterruptedException, TimeoutException { + Map> testDocs = + map( + "doc1", map("a", 1, "b", asList(0)), + "doc2", map("b", asList(1)), + "doc3", map("a", 3, "b", asList(2, 7), "c", 10), + "doc4", map("a", 1, "b", asList(3, 7)), + "doc5", map("a", 1), + "doc6", map("a", 2, "c", 20)); + CollectionReference collection = testCollectionWithDocs(testDocs); + + AggregateQuery query = collection.where(Filter.equalTo("a", 1)).orderBy("a").count(); + + AggregateQueryProfileInfo profile = query.profile().get(); + System.out.println(profile.plan); + System.out.println(profile.stats); + } } From 6b7783d19dd6010ab7c4d612c65377a053f344c6 Mon Sep 17 00:00:00 2001 From: Ehsan Nasiri Date: Mon, 31 Jul 2023 15:10:24 -0700 Subject: [PATCH 2/3] Use generic type for QueryProfileInfo. --- .../cloud/firestore/AggregateQuery.java | 8 +++-- .../firestore/AggregateQueryProfileInfo.java | 31 ------------------- .../com/google/cloud/firestore/Query.java | 7 +++-- .../cloud/firestore/QueryProfileInfo.java | 10 +++--- .../cloud/firestore/it/ITQueryTest.java | 17 +++------- 5 files changed, 18 insertions(+), 55 deletions(-) delete mode 100644 google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateQueryProfileInfo.java diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateQuery.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateQuery.java index c3b62185b..1141c09fb 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateQuery.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateQuery.java @@ -98,13 +98,15 @@ public ApiFuture> plan() { * query execution, and the query results. */ @Nonnull - public ApiFuture profile() { + public ApiFuture> profile() { Map plan = new HashMap<>(); plan.put("foo", "bar"); Map stats = new HashMap<>(); stats.put("cpu", "3ms"); - final SettableApiFuture result = SettableApiFuture.create(); - AggregateQueryProfileInfo mock = new AggregateQueryProfileInfo(plan, stats, null); + final SettableApiFuture> result = + SettableApiFuture.create(); + final AggregateQuerySnapshot mockSnapshot = new AggregateQuerySnapshot(this, null, 5); + QueryProfileInfo mock = new QueryProfileInfo(plan, stats, mockSnapshot); result.set(mock); return result; } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateQueryProfileInfo.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateQueryProfileInfo.java deleted file mode 100644 index 918cabbc5..000000000 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/AggregateQueryProfileInfo.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.google.cloud.firestore; - -import java.util.Map; -import javax.annotation.Nonnull; - -/** - * An AggregateQueryProfileInfo contains information about planning, execution, and results of an - * aggregate query. - */ -public class AggregateQueryProfileInfo { - /** A Map that contains information about the query plan. Contents are subject to change. */ - @Nonnull public final Map plan; - - /** - * A Map that contains statistics about the execution of the aggregate query. Contents are subject - * to change. - */ - @Nonnull public final Map stats; - - /** The snapshot that contains the results of executing the aggregate query. */ - @Nonnull public final AggregateQuerySnapshot snapshot; - - public AggregateQueryProfileInfo( - @Nonnull Map plan, - @Nonnull Map stats, - @Nonnull AggregateQuerySnapshot snapshot) { - this.plan = plan; - this.stats = stats; - this.snapshot = snapshot; - } -} diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java index 2fbff8776..4b23b65d3 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/Query.java @@ -1731,13 +1731,14 @@ public ApiFuture> plan() { * query execution, and the query results. */ @Nonnull - public ApiFuture profile() { + public ApiFuture> profile() { Map plan = new HashMap<>(); plan.put("foo", "bar"); Map stats = new HashMap<>(); stats.put("cpu", "3ms"); - final SettableApiFuture result = SettableApiFuture.create(); - QueryProfileInfo mock = new QueryProfileInfo(plan, stats, null); + final SettableApiFuture> result = SettableApiFuture.create(); + QuerySnapshot mockSnapshot = QuerySnapshot.withDocuments(this, null, new ArrayList<>()); + QueryProfileInfo mock = new QueryProfileInfo(plan, stats, mockSnapshot); result.set(mock); return result; } diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/QueryProfileInfo.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/QueryProfileInfo.java index 48f383653..398e67378 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/QueryProfileInfo.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/QueryProfileInfo.java @@ -4,7 +4,7 @@ import javax.annotation.Nonnull; /** A QueryProfile contains information about planning, execution, and results of a query. */ -public class QueryProfileInfo { +public class QueryProfileInfo { /** A Map that contains information about the query plan. Contents are subject to change. */ @Nonnull public final Map plan; @@ -15,12 +15,10 @@ public class QueryProfileInfo { @Nonnull public final Map stats; /** The snapshot that contains the results of executing the query. */ - @Nonnull public final QuerySnapshot snapshot; + @Nonnull public final T snapshot; - public QueryProfileInfo( - @Nonnull Map plan, - @Nonnull Map stats, - @Nonnull QuerySnapshot snapshot) { + QueryProfileInfo( + @Nonnull Map plan, @Nonnull Map stats, @Nonnull T snapshot) { this.plan = plan; this.stats = stats; this.snapshot = snapshot; diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryTest.java index 7012dccaf..56e30dcaf 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryTest.java @@ -22,17 +22,8 @@ import static org.junit.Assume.assumeTrue; import com.google.api.client.util.Preconditions; -import com.google.cloud.firestore.AggregateQuery; -import com.google.cloud.firestore.AggregateQueryProfileInfo; -import com.google.cloud.firestore.CollectionReference; -import com.google.cloud.firestore.Filter; -import com.google.cloud.firestore.Firestore; -import com.google.cloud.firestore.FirestoreOptions; -import com.google.cloud.firestore.LocalFirestoreHelper; -import com.google.cloud.firestore.Query; +import com.google.cloud.firestore.*; import com.google.cloud.firestore.Query.Direction; -import com.google.cloud.firestore.QueryProfileInfo; -import com.google.cloud.firestore.QuerySnapshot; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; @@ -450,9 +441,10 @@ public void testQueryProfile() throws ExecutionException, InterruptedException, Query query = collection.where(Filter.equalTo("a", 1)).orderBy("a"); - QueryProfileInfo profile = query.profile().get(); + QueryProfileInfo profile = query.profile().get(); System.out.println(profile.plan); System.out.println(profile.stats); + System.out.println(profile.snapshot.size()); } @Test @@ -488,8 +480,9 @@ public void testAggregateQueryProfile() AggregateQuery query = collection.where(Filter.equalTo("a", 1)).orderBy("a").count(); - AggregateQueryProfileInfo profile = query.profile().get(); + QueryProfileInfo profile = query.profile().get(); System.out.println(profile.plan); System.out.println(profile.stats); + System.out.println(profile.snapshot.getCount()); } } From 311332a2e82c192129f0e17af27253a30e23d25a Mon Sep 17 00:00:00 2001 From: Ehsan Nasiri Date: Mon, 31 Jul 2023 15:16:02 -0700 Subject: [PATCH 3/3] mark class as final. --- .../main/java/com/google/cloud/firestore/QueryProfileInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/QueryProfileInfo.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/QueryProfileInfo.java index 398e67378..5a735982e 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/QueryProfileInfo.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/QueryProfileInfo.java @@ -4,7 +4,7 @@ import javax.annotation.Nonnull; /** A QueryProfile contains information about planning, execution, and results of a query. */ -public class QueryProfileInfo { +final public class QueryProfileInfo { /** A Map that contains information about the query plan. Contents are subject to change. */ @Nonnull public final Map plan;