Skip to content

Commit 29fcb1d

Browse files
committed
Add Distinct
1 parent 8165ce4 commit 29fcb1d

File tree

5 files changed

+88
-20
lines changed

5 files changed

+88
-20
lines changed

google-cloud-firestore/src/main/java/com/google/cloud/firestore/Pipeline.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.google.cloud.firestore.pipeline.stages.Collection;
2020
import com.google.cloud.firestore.pipeline.stages.CollectionGroup;
2121
import com.google.cloud.firestore.pipeline.stages.Database;
22+
import com.google.cloud.firestore.pipeline.stages.Distinct;
2223
import com.google.cloud.firestore.pipeline.stages.Documents;
2324
import com.google.cloud.firestore.pipeline.stages.FindNearest;
2425
import com.google.cloud.firestore.pipeline.stages.GenericStage;
@@ -155,12 +156,12 @@ public Pipeline limit(int limit) {
155156
}
156157

157158
@BetaApi
158-
public Pipeline aggregate(AccumulatorTarget... aggregators) {
159+
public Pipeline aggregate(AccumulatorTarget... accumulators) {
159160
return new Pipeline(
160161
this.db,
161162
ImmutableList.<Stage>builder()
162163
.addAll(stages)
163-
.add(Aggregate.newInstance().withAccumulators(aggregators))
164+
.add(Aggregate.withAccumulators(accumulators))
164165
.build());
165166
}
166167

@@ -170,6 +171,26 @@ public Pipeline aggregate(Aggregate aggregate) {
170171
this.db, ImmutableList.<Stage>builder().addAll(stages).add(aggregate).build());
171172
}
172173

174+
@BetaApi
175+
public Pipeline distinct(String... fields) {
176+
return new Pipeline(
177+
this.db,
178+
ImmutableList.<Stage>builder()
179+
.addAll(stages)
180+
.add(new Distinct(PipelineUtils.fieldNamesToMap(fields)))
181+
.build());
182+
}
183+
184+
@BetaApi
185+
public Pipeline distinct(Selectable... selectables) {
186+
return new Pipeline(
187+
this.db,
188+
ImmutableList.<Stage>builder()
189+
.addAll(stages)
190+
.add(new Distinct(PipelineUtils.selectablesToMap(selectables)))
191+
.build());
192+
}
193+
173194
@BetaApi
174195
public Pipeline findNearest(
175196
String fieldName, double[] vector, FindNearest.FindNearestOptions options) {

google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/Aggregate.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,6 @@ public final class Aggregate implements Stage {
1919
private final Map<String, Expr> groups;
2020
private final Map<String, Accumulator> accumulators;
2121

22-
@BetaApi
23-
public static Aggregate newInstance() {
24-
return new Aggregate(Collections.emptyMap(), Collections.emptyMap());
25-
}
26-
2722
@BetaApi
2823
public Aggregate withGroups(String... fields) {
2924
return new Aggregate(PipelineUtils.fieldNamesToMap(fields), this.accumulators);
@@ -35,10 +30,15 @@ public Aggregate withGroups(Selectable... selectables) {
3530
}
3631

3732
@BetaApi
38-
public Aggregate withAccumulators(AccumulatorTarget... aggregators) {
33+
public static Aggregate withAccumulators(AccumulatorTarget... accumulators) {
34+
if (accumulators.length == 0) {
35+
throw new IllegalArgumentException(
36+
"Must specify at least one accumulator for aggregate() stage. There is a distinct() stage if only distinct group values are needed.");
37+
}
38+
3939
return new Aggregate(
40-
this.groups,
41-
Arrays.stream(aggregators)
40+
Collections.emptyMap(),
41+
Arrays.stream(accumulators)
4242
.collect(
4343
Collectors.toMap(
4444
AccumulatorTarget::getFieldName, AccumulatorTarget::getAccumulator)));
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.google.cloud.firestore.pipeline.stages;
2+
3+
import com.google.api.core.BetaApi;
4+
import com.google.api.core.InternalApi;
5+
import com.google.cloud.firestore.pipeline.expressions.Expr;
6+
import java.util.Map;
7+
8+
@BetaApi
9+
public final class Distinct implements Stage {
10+
11+
private static final String name = "distinct";
12+
private final Map<String, Expr> groups;
13+
14+
@InternalApi
15+
public Distinct(Map<String, Expr> groups) {
16+
this.groups = groups;
17+
}
18+
19+
@InternalApi
20+
Map<String, Expr> getGroups() {
21+
return groups;
22+
}
23+
24+
@Override
25+
public String getName() {
26+
return name;
27+
}
28+
}

google-cloud-firestore/src/main/java/com/google/cloud/firestore/pipeline/stages/StageUtils.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ public static com.google.firestore.v1.Pipeline.Stage toStageProto(Stage stage) {
8686
.addArgs(encodeValue(aggregateStage.getGroups()))
8787
.addArgs(encodeValue(aggregateStage.getAccumulators()))
8888
.build();
89+
} else if (stage instanceof Distinct) {
90+
Distinct distinctStage = (Distinct) stage;
91+
return com.google.firestore.v1.Pipeline.Stage.newBuilder()
92+
.setName(distinctStage.getName())
93+
.addArgs(encodeValue(distinctStage.getGroups()))
94+
.build();
8995
} else if (stage instanceof FindNearest) {
9096
FindNearest findNearestStage = (FindNearest) stage;
9197
return com.google.firestore.v1.Pipeline.Stage.newBuilder()

google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITPipelineTest.java

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import static com.google.cloud.firestore.pipeline.expressions.Function.strConcat;
4545
import static com.google.cloud.firestore.pipeline.expressions.Function.subtract;
4646
import static com.google.common.truth.Truth.assertThat;
47+
import static org.junit.Assert.assertThrows;
4748

4849
import com.google.cloud.firestore.CollectionReference;
4950
import com.google.cloud.firestore.LocalFirestoreHelper;
@@ -278,16 +279,29 @@ public void testAggregates() throws Exception {
278279
}
279280

280281
@Test
281-
public void testGroupBys() throws Exception {
282+
public void testGroupBysWithoutAccumulators() throws Exception {
283+
assertThrows(
284+
IllegalArgumentException.class,
285+
() -> {
286+
collection
287+
.pipeline()
288+
.where(lt("published", 1900))
289+
.aggregate(Aggregate.withAccumulators().withGroups("genre"));
290+
});
291+
}
292+
293+
@Test
294+
public void testDistinct() throws Exception {
282295
List<PipelineResult> results =
283296
collection
284297
.pipeline()
285298
.where(lt("published", 1900))
286-
.aggregate(Aggregate.newInstance().withGroups("genre"))
299+
.distinct(Field.of("genre").toLowercase().as("lower_genre"))
287300
.execute()
288301
.get();
289302
assertThat(data(results))
290-
.containsExactly(map("genre", "Romance"), map("genre", "Psychological Thriller"));
303+
.containsExactly(
304+
map("lower_genre", "romance"), map("lower_genre", "psychological thriller"));
291305
}
292306

293307
@Test
@@ -297,9 +311,7 @@ public void testGroupBysAndAggregate() throws Exception {
297311
.pipeline()
298312
.where(lt("published", 1984))
299313
.aggregate(
300-
Aggregate.newInstance()
301-
.withGroups("genre")
302-
.withAccumulators(avg("rating").as("avg_rating")))
314+
Aggregate.withAccumulators(avg("rating").as("avg_rating")).withGroups("genre"))
303315
.where(gt("avg_rating", 4.3))
304316
.execute()
305317
.get();
@@ -531,20 +543,21 @@ public void testStrConcat() throws Exception {
531543
@Test
532544
public void testStartsWith() throws Exception {
533545
List<PipelineResult> results =
534-
collection.pipeline()
546+
collection
547+
.pipeline()
535548
.where(startsWith("title", "The"))
536549
.select("title")
537550
.sort(Field.of("title").ascending())
538-
.execute().get();
551+
.execute()
552+
.get();
539553

540554
assertThat(data(results))
541555
.isEqualTo(
542556
Lists.newArrayList(
543557
map("title", "The Great Gatsby"),
544558
map("title", "The Handmaid's Tale"),
545559
map("title", "The Hitchhiker's Guide to the Galaxy"),
546-
map("title", "The Lord of the Rings")
547-
));
560+
map("title", "The Lord of the Rings")));
548561
}
549562

550563
@Test

0 commit comments

Comments
 (0)