From 5dcc233ef795f5e23e1dbca132590f797e86ce76 Mon Sep 17 00:00:00 2001 From: Ehsan Nasiri Date: Mon, 24 Apr 2023 16:40:59 -0700 Subject: [PATCH 1/6] feat: Add long alias support for aggregations. --- .../cloud/firestore/AggregateQuery.java | 22 ++++++++++--------- .../firestore/it/ITQueryAggregationsTest.java | 19 ++++++++++++++++ 2 files changed, 31 insertions(+), 10 deletions(-) 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 ca5af9ec00..2f34672fcd 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 @@ -36,21 +36,16 @@ /** A query that calculates aggregations over an underlying query. */ @InternalExtensionOnly public class AggregateQuery { - - /** - * The "alias" to specify in the {@link RunAggregationQueryRequest} proto when running a count - * query. The actual value is not meaningful, but will be used to get the count out of the {@link - * RunAggregationQueryResponse}. - */ - private static final String ALIAS_COUNT = "count"; - @Nonnull private final Query query; @Nonnull private List aggregateFieldList; + @Nonnull private Map aliasMap; + AggregateQuery(@Nonnull Query query, @Nonnull List aggregateFields) { this.query = query; this.aggregateFieldList = aggregateFields; + this.aliasMap = new HashMap<>(); } /** Returns the query whose aggregations will be calculated by this object. */ @@ -114,7 +109,9 @@ long getStartTimeNanos() { void deliverResult(@Nonnull Map data, Timestamp readTime) { if (isFutureCompleted.compareAndSet(false, true)) { - future.set(new AggregateQuerySnapshot(AggregateQuery.this, readTime, data)); + Map mappedData = new HashMap<>(); + data.forEach((serverAlias, value) -> mappedData.put(aliasMap.get(serverAlias), value)); + future.set(new AggregateQuerySnapshot(AggregateQuery.this, readTime, mappedData)); } } @@ -204,6 +201,7 @@ RunAggregationQueryRequest toProto(@Nullable final ByteString transactionId) { // We use a Set here to automatically remove duplicates. Set aggregations = new HashSet<>(); + int aggregationNum = 0; for (AggregateField aggregateField : aggregateFieldList) { // If there's a field for this aggregation, build its proto. StructuredQuery.FieldReference field = null; @@ -224,7 +222,11 @@ RunAggregationQueryRequest toProto(@Nullable final ByteString transactionId) { } else { throw new RuntimeException("Unsupported aggregation"); } - aggregation.setAlias(aggregateField.getAlias()); + // Map all client-side aliases to a unique short-form alias. + // This avoids issues with client-side aliases that exceed the 1500-byte string size limit. + String serverAlias = "aggregate_" + aggregationNum++; + aliasMap.put(serverAlias, aggregateField.getAlias()); + aggregation.setAlias(serverAlias); aggregations.add(aggregation.build()); } structuredAggregationQuery.addAllAggregations(aggregations); diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryAggregationsTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryAggregationsTest.java index 9d9b5d3aa4..104ffde85b 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryAggregationsTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryAggregationsTest.java @@ -81,6 +81,25 @@ public void canRunCountUsingAggregationMethod() throws Exception { assertThat(snapshot.getCount()).isEqualTo(2); } + @Test + public void allowsAliasesLongerThan1500Bytes() throws Exception { + // The longest field name allowed is 1500. The alias chosen by the client is _. + // If the field name is + // 1500 bytes, the alias will be longer than 1500, which is the limit for aliases. This is to + // make sure the client + // can handle this corner case correctly. + String longField = ""; + for (int i = 0; i < 150; i++) { + longField += "0123456789"; + } + Map> testDocs = map("a", map(longField, 1), "b", map(longField, 2)); + + CollectionReference collection = testCollectionWithDocs(testDocs); + AggregateQuerySnapshot snapshot = + collection.aggregate(AggregateField.sum(longField)).get().get(); + assertThat(snapshot.get(AggregateField.sum(longField))).isEqualTo(3); + } + @Test public void canGetDuplicateAggregations() throws Exception { CollectionReference collection = testCollectionWithDocs(testDocs1); From fce43644f293e892a148eefe88e20c120f2ad8f1 Mon Sep 17 00:00:00 2001 From: Ehsan Nasiri Date: Thu, 27 Jul 2023 16:58:47 -0700 Subject: [PATCH 2/6] address comments. --- .../firestore/it/ITQueryAggregationsTest.java | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryAggregationsTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryAggregationsTest.java index 104ffde85b..14c62e86bf 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryAggregationsTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryAggregationsTest.java @@ -82,22 +82,24 @@ public void canRunCountUsingAggregationMethod() throws Exception { } @Test - public void allowsAliasesLongerThan1500Bytes() throws Exception { - // The longest field name allowed is 1500. The alias chosen by the client is _. - // If the field name is - // 1500 bytes, the alias will be longer than 1500, which is the limit for aliases. This is to - // make sure the client - // can handle this corner case correctly. - String longField = ""; - for (int i = 0; i < 150; i++) { - longField += "0123456789"; + public void allowsAliasesForLongestFieldNames() throws Exception { + // The longest field name allowed is 1499 characters long. + // Ensure that sum(longestField) and average(longestField) work. + StringBuilder builder = new StringBuilder(1500); + for (int i = 0; i < 1499; i++) { + builder.append("k"); } - Map> testDocs = map("a", map(longField, 1), "b", map(longField, 2)); + String longestField = builder.toString(); + Map> testDocs = + map("a", map(longestField, 2), "b", map(longestField, 4)); CollectionReference collection = testCollectionWithDocs(testDocs); AggregateQuerySnapshot snapshot = - collection.aggregate(AggregateField.sum(longField)).get().get(); - assertThat(snapshot.get(AggregateField.sum(longField))).isEqualTo(3); + collection.aggregate(AggregateField.sum(longestField)).get().get(); + assertThat(snapshot.get(AggregateField.sum(longestField))).isEqualTo(6); + AggregateQuerySnapshot snapshot2 = + collection.aggregate(AggregateField.average(longestField)).get().get(); + assertThat(snapshot2.get(AggregateField.average(longestField))).isEqualTo(3.0); } @Test @@ -293,7 +295,7 @@ public void aggregateQueriesSupportCollectionGroups() throws Exception { } @Test - public void performsAggregationsOnDocumentsWithAllaggregatedFields() throws Exception { + public void performsAggregationsOnDocumentsWithAllAggregatedFields() throws Exception { Map> testDocs = map( "a", From 279a35f05d11fda154e2d8824a95097c5eca209b Mon Sep 17 00:00:00 2001 From: Ehsan Nasiri Date: Thu, 27 Jul 2023 17:24:49 -0700 Subject: [PATCH 3/6] Better method name and replace hardcoded "count" with "aggregate_0". --- .../cloud/firestore/LocalFirestoreHelper.java | 35 +++++++-------- .../cloud/firestore/QueryCountTest.java | 45 +++++++++---------- .../cloud/firestore/TransactionTest.java | 7 ++- 3 files changed, 42 insertions(+), 45 deletions(-) diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/LocalFirestoreHelper.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/LocalFirestoreHelper.java index 6aec26e9a1..35a19f5df3 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/LocalFirestoreHelper.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/LocalFirestoreHelper.java @@ -341,24 +341,24 @@ public static Answer queryResponseWithDone( } } - public static Answer aggregationQueryResponse() { - return aggregationQueryResponse(42); + public static Answer countQueryResponse() { + return countQueryResponse(42); } - public static Answer aggregationQueryResponse(int count) { - return aggregationQueryResponse(count, null); + public static Answer countQueryResponse(int count) { + return countQueryResponse(count, null); } - public static Answer aggregationQueryResponse( + public static Answer countQueryResponse( int count, @Nullable Timestamp readTime) { return streamingResponse( new RunAggregationQueryResponse[] { - createRunAggregationQueryResponse(count, readTime), + createCountQueryResponse(count, readTime), }, /*throwable=*/ null); } - public static Answer aggregationQueryResponse(Throwable throwable) { + public static Answer countQueryResponse(Throwable throwable) { return streamingResponse(new RunAggregationQueryResponse[] {}, throwable); } @@ -366,8 +366,7 @@ public static Answer aggregationQueryResponses( int count1, int count2) { return streamingResponse( new RunAggregationQueryResponse[] { - createRunAggregationQueryResponse(count1, null), - createRunAggregationQueryResponse(count2, null), + createCountQueryResponse(count1, null), createCountQueryResponse(count2, null), }, /*throwable=*/ null); } @@ -376,17 +375,17 @@ public static Answer aggregationQueryResponses( int count1, Throwable throwable) { return streamingResponse( new RunAggregationQueryResponse[] { - createRunAggregationQueryResponse(count1, null), + createCountQueryResponse(count1, null), }, throwable); } - private static RunAggregationQueryResponse createRunAggregationQueryResponse( + private static RunAggregationQueryResponse createCountQueryResponse( int count, @Nullable Timestamp timestamp) { RunAggregationQueryResponse.Builder builder = RunAggregationQueryResponse.newBuilder(); builder.setResult( AggregationResult.newBuilder() - .putAggregateFields("count", Value.newBuilder().setIntegerValue(count).build()) + .putAggregateFields("aggregate_0", Value.newBuilder().setIntegerValue(count).build()) .build()); if (timestamp != null) { builder.setReadTime(timestamp.toProto()); @@ -751,11 +750,11 @@ public static RunQueryRequest query( return request.build(); } - public static RunAggregationQueryRequest aggregationQuery() { - return aggregationQuery((String) null); + public static RunAggregationQueryRequest countQuery() { + return countQuery((String) null); } - public static RunAggregationQueryRequest aggregationQuery(@Nullable String transactionId) { + public static RunAggregationQueryRequest countQuery(@Nullable String transactionId) { RunQueryRequest runQueryRequest = query(TRANSACTION_ID, false); RunAggregationQueryRequest.Builder request = @@ -766,7 +765,7 @@ public static RunAggregationQueryRequest aggregationQuery(@Nullable String trans .setStructuredQuery(runQueryRequest.getStructuredQuery()) .addAggregations( Aggregation.newBuilder() - .setAlias("count") + .setAlias("aggregate_0") .setCount(Aggregation.Count.getDefaultInstance()))); if (transactionId != null) { @@ -776,7 +775,7 @@ public static RunAggregationQueryRequest aggregationQuery(@Nullable String trans return request.build(); } - public static RunAggregationQueryRequest aggregationQuery(RunQueryRequest runQueryRequest) { + public static RunAggregationQueryRequest countQuery(RunQueryRequest runQueryRequest) { return RunAggregationQueryRequest.newBuilder() .setParent(runQueryRequest.getParent()) .setStructuredAggregationQuery( @@ -784,7 +783,7 @@ public static RunAggregationQueryRequest aggregationQuery(RunQueryRequest runQue .setStructuredQuery(runQueryRequest.getStructuredQuery()) .addAggregations( Aggregation.newBuilder() - .setAlias("count") + .setAlias("aggregate_0") .setCount(Aggregation.Count.getDefaultInstance()))) .build(); } diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/QueryCountTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/QueryCountTest.java index ea9dad975f..00dafb7788 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/QueryCountTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/QueryCountTest.java @@ -17,9 +17,8 @@ package com.google.cloud.firestore; import static com.google.cloud.firestore.LocalFirestoreHelper.COLLECTION_ID; -import static com.google.cloud.firestore.LocalFirestoreHelper.aggregationQuery; -import static com.google.cloud.firestore.LocalFirestoreHelper.aggregationQueryResponse; import static com.google.cloud.firestore.LocalFirestoreHelper.aggregationQueryResponses; +import static com.google.cloud.firestore.LocalFirestoreHelper.countQueryResponse; import static com.google.cloud.firestore.LocalFirestoreHelper.endAt; import static com.google.cloud.firestore.LocalFirestoreHelper.limit; import static com.google.cloud.firestore.LocalFirestoreHelper.order; @@ -78,7 +77,7 @@ public void before() { @Test public void countShouldBeZeroForEmptyCollection() throws Exception { - doAnswer(aggregationQueryResponse(0)) + doAnswer(LocalFirestoreHelper.countQueryResponse(0)) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -92,7 +91,7 @@ public void countShouldBeZeroForEmptyCollection() throws Exception { @Test public void countShouldBe99ForCollectionWith99Documents() throws Exception { - doAnswer(aggregationQueryResponse(99)) + doAnswer(LocalFirestoreHelper.countQueryResponse(99)) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -106,7 +105,7 @@ public void countShouldBe99ForCollectionWith99Documents() throws Exception { @Test public void countShouldMakeCorrectRequestForACollection() throws Exception { - doAnswer(aggregationQueryResponse(0)) + doAnswer(LocalFirestoreHelper.countQueryResponse(0)) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -116,12 +115,12 @@ public void countShouldMakeCorrectRequestForACollection() throws Exception { CollectionReference collection = firestoreMock.collection(COLLECTION_ID); collection.count().get(); - assertThat(runAggregationQuery.getValue()).isEqualTo(aggregationQuery()); + assertThat(runAggregationQuery.getValue()).isEqualTo(LocalFirestoreHelper.countQuery()); } @Test public void countShouldMakeCorrectRequestForAComplexQuery() throws Exception { - doAnswer(aggregationQueryResponse(0)) + doAnswer(LocalFirestoreHelper.countQueryResponse(0)) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -132,7 +131,7 @@ public void countShouldMakeCorrectRequestForAComplexQuery() throws Exception { assertThat(runAggregationQuery.getValue()) .isEqualTo( - aggregationQuery( + LocalFirestoreHelper.countQuery( query( limit(42), order("foo", StructuredQuery.Direction.DESCENDING), @@ -142,7 +141,7 @@ public void countShouldMakeCorrectRequestForAComplexQuery() throws Exception { @Test public void shouldReturnReadTimeFromResponse() throws Exception { - doAnswer(aggregationQueryResponse(99, Timestamp.ofTimeSecondsAndNanos(123, 456))) + doAnswer(LocalFirestoreHelper.countQueryResponse(99, Timestamp.ofTimeSecondsAndNanos(123, 456))) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -185,7 +184,7 @@ public void shouldIgnoreExtraErrors() throws Exception { @Test public void shouldPropagateErrors() throws Exception { Exception exception = new Exception(); - doAnswer(aggregationQueryResponse(exception)) + doAnswer(countQueryResponse(exception)) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -205,7 +204,7 @@ public void aggregateQueryGetQueryShouldReturnCorrectValue() throws Exception { @Test public void aggregateQuerySnapshotGetQueryShouldReturnCorrectValue() throws Exception { - doAnswer(aggregationQueryResponse()) + doAnswer(LocalFirestoreHelper.countQueryResponse()) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -220,8 +219,8 @@ public void aggregateQuerySnapshotGetQueryShouldReturnCorrectValue() throws Exce @Test public void shouldNotRetryIfExceptionIsNotFirestoreException() { - doAnswer(aggregationQueryResponse(new NotFirestoreException())) - .doAnswer(aggregationQueryResponse()) + doAnswer(countQueryResponse(new NotFirestoreException())) + .doAnswer(LocalFirestoreHelper.countQueryResponse()) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -235,8 +234,8 @@ public void shouldNotRetryIfExceptionIsNotFirestoreException() { @Test public void shouldRetryIfExceptionIsFirestoreExceptionWithRetryableStatus() throws Exception { - doAnswer(aggregationQueryResponse(new FirestoreException("reason", Status.INTERNAL))) - .doAnswer(aggregationQueryResponse(42)) + doAnswer(countQueryResponse(new FirestoreException("reason", Status.INTERNAL))) + .doAnswer(LocalFirestoreHelper.countQueryResponse(42)) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -252,8 +251,8 @@ public void shouldRetryIfExceptionIsFirestoreExceptionWithRetryableStatus() thro @Test public void shouldNotRetryIfExceptionIsFirestoreExceptionWithNonRetryableStatus() { doReturn(Duration.ZERO).when(firestoreMock).getTotalRequestTimeout(); - doAnswer(aggregationQueryResponse(new FirestoreException("reason", Status.INVALID_ARGUMENT))) - .doAnswer(aggregationQueryResponse()) + doAnswer(countQueryResponse(new FirestoreException("reason", Status.INVALID_ARGUMENT))) + .doAnswer(LocalFirestoreHelper.countQueryResponse()) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -270,8 +269,8 @@ public void shouldNotRetryIfExceptionIsFirestoreExceptionWithNonRetryableStatus( shouldRetryIfExceptionIsFirestoreExceptionWithRetryableStatusWithInfiniteTimeoutWindow() throws Exception { doReturn(Duration.ZERO).when(firestoreMock).getTotalRequestTimeout(); - doAnswer(aggregationQueryResponse(new FirestoreException("reason", Status.INTERNAL))) - .doAnswer(aggregationQueryResponse(42)) + doAnswer(countQueryResponse(new FirestoreException("reason", Status.INTERNAL))) + .doAnswer(LocalFirestoreHelper.countQueryResponse(42)) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -288,8 +287,8 @@ public void shouldNotRetryIfExceptionIsFirestoreExceptionWithNonRetryableStatus( public void shouldRetryIfExceptionIsFirestoreExceptionWithRetryableStatusWithinTimeoutWindow() throws Exception { doReturn(Duration.ofDays(999)).when(firestoreMock).getTotalRequestTimeout(); - doAnswer(aggregationQueryResponse(new FirestoreException("reason", Status.INTERNAL))) - .doAnswer(aggregationQueryResponse(42)) + doAnswer(countQueryResponse(new FirestoreException("reason", Status.INTERNAL))) + .doAnswer(LocalFirestoreHelper.countQueryResponse(42)) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -313,8 +312,8 @@ public void shouldRetryIfExceptionIsFirestoreExceptionWithRetryableStatusWithinT .when(clockMock) .nanoTime(); doReturn(Duration.ofSeconds(5)).when(firestoreMock).getTotalRequestTimeout(); - doAnswer(aggregationQueryResponse(new FirestoreException("reason", Status.INTERNAL))) - .doAnswer(aggregationQueryResponse(42)) + doAnswer(countQueryResponse(new FirestoreException("reason", Status.INTERNAL))) + .doAnswer(LocalFirestoreHelper.countQueryResponse(42)) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/TransactionTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/TransactionTest.java index 7f9200222b..dd791cef50 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/TransactionTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/TransactionTest.java @@ -19,12 +19,11 @@ import static com.google.cloud.firestore.LocalFirestoreHelper.IMMEDIATE_RETRY_SETTINGS; import static com.google.cloud.firestore.LocalFirestoreHelper.SINGLE_FIELD_PROTO; import static com.google.cloud.firestore.LocalFirestoreHelper.TRANSACTION_ID; -import static com.google.cloud.firestore.LocalFirestoreHelper.aggregationQuery; -import static com.google.cloud.firestore.LocalFirestoreHelper.aggregationQueryResponse; import static com.google.cloud.firestore.LocalFirestoreHelper.begin; import static com.google.cloud.firestore.LocalFirestoreHelper.beginResponse; import static com.google.cloud.firestore.LocalFirestoreHelper.commit; import static com.google.cloud.firestore.LocalFirestoreHelper.commitResponse; +import static com.google.cloud.firestore.LocalFirestoreHelper.countQuery; import static com.google.cloud.firestore.LocalFirestoreHelper.create; import static com.google.cloud.firestore.LocalFirestoreHelper.delete; import static com.google.cloud.firestore.LocalFirestoreHelper.get; @@ -678,7 +677,7 @@ public void getAggregateQuery() throws Exception { .when(firestoreMock) .sendRequest(requestCapture.capture(), Matchers.>any()); - doAnswer(aggregationQueryResponse(42)) + doAnswer(LocalFirestoreHelper.countQueryResponse(42)) .when(firestoreMock) .streamRequest(requestCapture.capture(), streamObserverCapture.capture(), Matchers.any()); @@ -691,7 +690,7 @@ public void getAggregateQuery() throws Exception { assertEquals(3, requests.size()); assertEquals(begin(), requests.get(0)); - assertEquals(aggregationQuery(TRANSACTION_ID), requests.get(1)); + assertEquals(countQuery(TRANSACTION_ID), requests.get(1)); assertEquals(commit(TRANSACTION_ID), requests.get(2)); } From daeebf9ca89a07ff661c5ab46ba20eb896a34586 Mon Sep 17 00:00:00 2001 From: Ehsan Nasiri Date: Thu, 27 Jul 2023 18:28:38 -0700 Subject: [PATCH 4/6] Remove duplicate aggregations. --- .../com/google/cloud/firestore/AggregateQuery.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) 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 2f34672fcd..067ec0185a 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 @@ -199,10 +199,18 @@ RunAggregationQueryRequest toProto(@Nullable final ByteString transactionId) { request.getStructuredAggregationQueryBuilder(); structuredAggregationQuery.setStructuredQuery(runQueryRequest.getStructuredQuery()); - // We use a Set here to automatically remove duplicates. - Set aggregations = new HashSet<>(); + // We use this set to remove duplicate aggregates. e.g. `aggregate(sum("foo"), sum("foo"))` + HashSet uniqueAggregates = new HashSet<>(); + List aggregations = new ArrayList<>(); int aggregationNum = 0; for (AggregateField aggregateField : aggregateFieldList) { + // `getAlias()` provides a unique representation of an AggregateField. + boolean isNewAggregateField = uniqueAggregates.add(aggregateField.getAlias()); + if (!isNewAggregateField) { + // This is a duplicate AggregateField. We don't need to include it in the request. + continue; + } + // If there's a field for this aggregation, build its proto. StructuredQuery.FieldReference field = null; if (!aggregateField.getFieldPath().isEmpty()) { From a5febd3933a796e6bc2b826b82726954ede9c329 Mon Sep 17 00:00:00 2001 From: Ehsan Nasiri Date: Thu, 27 Jul 2023 18:34:06 -0700 Subject: [PATCH 5/6] add static import. --- .../cloud/firestore/QueryCountTest.java | 24 +++++++++---------- .../cloud/firestore/TransactionTest.java | 22 ++--------------- 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/QueryCountTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/QueryCountTest.java index 00dafb7788..cf0c228626 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/QueryCountTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/QueryCountTest.java @@ -77,7 +77,7 @@ public void before() { @Test public void countShouldBeZeroForEmptyCollection() throws Exception { - doAnswer(LocalFirestoreHelper.countQueryResponse(0)) + doAnswer(countQueryResponse(0)) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -91,7 +91,7 @@ public void countShouldBeZeroForEmptyCollection() throws Exception { @Test public void countShouldBe99ForCollectionWith99Documents() throws Exception { - doAnswer(LocalFirestoreHelper.countQueryResponse(99)) + doAnswer(countQueryResponse(99)) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -105,7 +105,7 @@ public void countShouldBe99ForCollectionWith99Documents() throws Exception { @Test public void countShouldMakeCorrectRequestForACollection() throws Exception { - doAnswer(LocalFirestoreHelper.countQueryResponse(0)) + doAnswer(countQueryResponse(0)) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -120,7 +120,7 @@ public void countShouldMakeCorrectRequestForACollection() throws Exception { @Test public void countShouldMakeCorrectRequestForAComplexQuery() throws Exception { - doAnswer(LocalFirestoreHelper.countQueryResponse(0)) + doAnswer(countQueryResponse(0)) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -141,7 +141,7 @@ public void countShouldMakeCorrectRequestForAComplexQuery() throws Exception { @Test public void shouldReturnReadTimeFromResponse() throws Exception { - doAnswer(LocalFirestoreHelper.countQueryResponse(99, Timestamp.ofTimeSecondsAndNanos(123, 456))) + doAnswer(countQueryResponse(99, Timestamp.ofTimeSecondsAndNanos(123, 456))) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -204,7 +204,7 @@ public void aggregateQueryGetQueryShouldReturnCorrectValue() throws Exception { @Test public void aggregateQuerySnapshotGetQueryShouldReturnCorrectValue() throws Exception { - doAnswer(LocalFirestoreHelper.countQueryResponse()) + doAnswer(countQueryResponse()) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -220,7 +220,7 @@ public void aggregateQuerySnapshotGetQueryShouldReturnCorrectValue() throws Exce @Test public void shouldNotRetryIfExceptionIsNotFirestoreException() { doAnswer(countQueryResponse(new NotFirestoreException())) - .doAnswer(LocalFirestoreHelper.countQueryResponse()) + .doAnswer(countQueryResponse()) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -235,7 +235,7 @@ public void shouldNotRetryIfExceptionIsNotFirestoreException() { @Test public void shouldRetryIfExceptionIsFirestoreExceptionWithRetryableStatus() throws Exception { doAnswer(countQueryResponse(new FirestoreException("reason", Status.INTERNAL))) - .doAnswer(LocalFirestoreHelper.countQueryResponse(42)) + .doAnswer(countQueryResponse(42)) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -252,7 +252,7 @@ public void shouldRetryIfExceptionIsFirestoreExceptionWithRetryableStatus() thro public void shouldNotRetryIfExceptionIsFirestoreExceptionWithNonRetryableStatus() { doReturn(Duration.ZERO).when(firestoreMock).getTotalRequestTimeout(); doAnswer(countQueryResponse(new FirestoreException("reason", Status.INVALID_ARGUMENT))) - .doAnswer(LocalFirestoreHelper.countQueryResponse()) + .doAnswer(countQueryResponse()) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -270,7 +270,7 @@ public void shouldNotRetryIfExceptionIsFirestoreExceptionWithNonRetryableStatus( throws Exception { doReturn(Duration.ZERO).when(firestoreMock).getTotalRequestTimeout(); doAnswer(countQueryResponse(new FirestoreException("reason", Status.INTERNAL))) - .doAnswer(LocalFirestoreHelper.countQueryResponse(42)) + .doAnswer(countQueryResponse(42)) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -288,7 +288,7 @@ public void shouldRetryIfExceptionIsFirestoreExceptionWithRetryableStatusWithinT throws Exception { doReturn(Duration.ofDays(999)).when(firestoreMock).getTotalRequestTimeout(); doAnswer(countQueryResponse(new FirestoreException("reason", Status.INTERNAL))) - .doAnswer(LocalFirestoreHelper.countQueryResponse(42)) + .doAnswer(countQueryResponse(42)) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), @@ -313,7 +313,7 @@ public void shouldRetryIfExceptionIsFirestoreExceptionWithRetryableStatusWithinT .nanoTime(); doReturn(Duration.ofSeconds(5)).when(firestoreMock).getTotalRequestTimeout(); doAnswer(countQueryResponse(new FirestoreException("reason", Status.INTERNAL))) - .doAnswer(LocalFirestoreHelper.countQueryResponse(42)) + .doAnswer(countQueryResponse(42)) .when(firestoreMock) .streamRequest( runAggregationQuery.capture(), diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/TransactionTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/TransactionTest.java index dd791cef50..b30556efa7 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/TransactionTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/TransactionTest.java @@ -16,25 +16,7 @@ package com.google.cloud.firestore; -import static com.google.cloud.firestore.LocalFirestoreHelper.IMMEDIATE_RETRY_SETTINGS; -import static com.google.cloud.firestore.LocalFirestoreHelper.SINGLE_FIELD_PROTO; -import static com.google.cloud.firestore.LocalFirestoreHelper.TRANSACTION_ID; -import static com.google.cloud.firestore.LocalFirestoreHelper.begin; -import static com.google.cloud.firestore.LocalFirestoreHelper.beginResponse; -import static com.google.cloud.firestore.LocalFirestoreHelper.commit; -import static com.google.cloud.firestore.LocalFirestoreHelper.commitResponse; -import static com.google.cloud.firestore.LocalFirestoreHelper.countQuery; -import static com.google.cloud.firestore.LocalFirestoreHelper.create; -import static com.google.cloud.firestore.LocalFirestoreHelper.delete; -import static com.google.cloud.firestore.LocalFirestoreHelper.get; -import static com.google.cloud.firestore.LocalFirestoreHelper.getAll; -import static com.google.cloud.firestore.LocalFirestoreHelper.getAllResponse; -import static com.google.cloud.firestore.LocalFirestoreHelper.query; -import static com.google.cloud.firestore.LocalFirestoreHelper.queryResponse; -import static com.google.cloud.firestore.LocalFirestoreHelper.rollback; -import static com.google.cloud.firestore.LocalFirestoreHelper.rollbackResponse; -import static com.google.cloud.firestore.LocalFirestoreHelper.set; -import static com.google.cloud.firestore.LocalFirestoreHelper.update; +import static com.google.cloud.firestore.LocalFirestoreHelper.*; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -677,7 +659,7 @@ public void getAggregateQuery() throws Exception { .when(firestoreMock) .sendRequest(requestCapture.capture(), Matchers.>any()); - doAnswer(LocalFirestoreHelper.countQueryResponse(42)) + doAnswer(countQueryResponse(42)) .when(firestoreMock) .streamRequest(requestCapture.capture(), streamObserverCapture.capture(), Matchers.any()); From ebff5cbd08998fe75997195fad68a6a5b38f14ae Mon Sep 17 00:00:00 2001 From: Ehsan Nasiri Date: Fri, 28 Jul 2023 16:23:26 -0700 Subject: [PATCH 6/6] Fix tests. All tests pass. --- .../firestore/it/ITQueryAggregationsTest.java | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryAggregationsTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryAggregationsTest.java index 14c62e86bf..7c24f74186 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryAggregationsTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITQueryAggregationsTest.java @@ -10,6 +10,7 @@ import static java.util.Arrays.asList; import static org.junit.Assert.assertThrows; import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; import com.google.cloud.firestore.*; import com.google.common.base.Preconditions; @@ -131,7 +132,7 @@ public void aggregateErrorMessageIfIndexIsMissing() throws Exception { assertThat(executionException) .hasCauseThat() .hasMessageThat() - .containsMatch("/index.*https:\\/\\/console\\.firebase\\.google\\.com/"); + .containsMatch("index.*https:\\/\\/console\\.firebase\\.google\\.com"); } @Test @@ -216,8 +217,9 @@ public void getCorrectTypeForAverageNull() throws Exception { @Test public void canPerformMaxAggregations() throws Exception { - // TODO: Update this test once aggregate de-duplication is implemented and more aggregation - // types are available. + assumeTrue( + "Skip this test when running against prod because it requires composite index creation.", + isRunningAgainstFirestoreEmulator(firestore)); CollectionReference collection = testCollectionWithDocs(testDocs1); AggregateField f1 = sum("pages"); AggregateField f2 = average("pages"); @@ -255,6 +257,9 @@ public void cannotPerformMoreThanMaxAggregations() throws Exception { @Test public void aggregateQueriesSupportCollectionGroups() throws Exception { + assumeTrue( + "Skip this test when running against prod because it requires composite index creation.", + isRunningAgainstFirestoreEmulator(firestore)); String collectionGroupId = "myColGroupId" + autoId(); Map data = map("x", 2); // Setting documents at the following paths: @@ -296,6 +301,9 @@ public void aggregateQueriesSupportCollectionGroups() throws Exception { @Test public void performsAggregationsOnDocumentsWithAllAggregatedFields() throws Exception { + assumeTrue( + "Skip this test when running against prod because it requires composite index creation.", + isRunningAgainstFirestoreEmulator(firestore)); Map> testDocs = map( "a", @@ -320,6 +328,9 @@ public void performsAggregationsOnDocumentsWithAllAggregatedFields() throws Exce @Test public void performsAggregationsWhenNaNExistsForSomeFieldValues() throws Exception { + assumeTrue( + "Skip this test when running against prod because it requires composite index creation.", + isRunningAgainstFirestoreEmulator(firestore)); Map> testDocs = map( "a", @@ -378,6 +389,9 @@ public void throwsAnErrorWhenGettingTheResultOfAnUnrequestedAggregation() throws @Test public void performsAggregationWhenUsingInOperator() throws Exception { + assumeTrue( + "Skip this test when running against prod because it requires composite index creation.", + isRunningAgainstFirestoreEmulator(firestore)); Map> testDocs = map( "a", @@ -415,6 +429,9 @@ public void performsAggregationWhenUsingInOperator() throws Exception { @Test public void performsAggregationWhenUsingArrayContainsAnyOperator() throws Exception { + assumeTrue( + "Skip this test when running against prod because it requires composite index creation.", + isRunningAgainstFirestoreEmulator(firestore)); Map> testDocs = map( "a", @@ -470,6 +487,9 @@ public void performsAggregationWhenUsingArrayContainsAnyOperator() throws Except @Test public void performsAggregationsOnNestedMapValues() throws Exception { + assumeTrue( + "Skip this test when running against prod because it requires composite index creation.", + isRunningAgainstFirestoreEmulator(firestore)); Map> testDocs = map( "a", @@ -530,8 +550,8 @@ public void performsSumOfIntsAndFloatsThatResultsInInt() throws Exception { CollectionReference collection = testCollectionWithDocs(testDocs); AggregateQuerySnapshot snapshot = collection.aggregate(sum("rating")).get().get(); Object sum = snapshot.get(sum("rating")); - assertThat(sum instanceof Long).isTrue(); - assertThat(sum).isEqualTo(13); + assertThat(sum instanceof Double).isTrue(); + assertThat(sum).isEqualTo(13.0); } @Test @@ -619,8 +639,8 @@ public void performsSumThatIsValidButCouldOverflowDuringAggregation() throws Exc CollectionReference collection = testCollectionWithDocs(testDocs); AggregateQuerySnapshot snapshot = collection.aggregate(sum("rating")).get().get(); Object sum = snapshot.get(sum("rating")); - assertThat(sum instanceof Long).isTrue(); - assertThat(sum).isEqualTo(0); + assertThat(sum instanceof Double).isTrue(); + assertThat(sum).isAnyOf(0, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY); } @Test