Skip to content

Commit 56d1001

Browse files
authored
feat: Sum and Avg aggregation feature (#1067)
* creating sum aggregation * creating avg aggregation * refactoring code to configure alias into an aggregation * Add method in aggregation query builders to accept aggregation in var args form * refactoring equals implementation * changed visibility from public to protected * Made aggregation result capable of returning double values * clirr ignore new method * Made transformer capable of parsing double values * mock webserver simulating sum and avg aggregation response * integration tests for sum and avg * Marking sum and avg methods with @BetaApi annotation * incorporating feedbacks * fixing lint * refactoring dispatcher code * incorporating feedbacks * cleaing up proxy code, as sum/avg now can be run against nightly * remove deprecated annotation * tests for sum and avg aggregations in GQL query * removing the addaggregation variant accepting list of aggregations * fix lint failures * removed BetaApi annotation from sum and avg aggregation * adding doc to AggregationResult#getDouble * adding sum/avg aggreggation test with autogenerated alias * adding a test to run sum and avg aggregation together * testing sum and avg aggregations with transactions * type check before returning the result from aggregation result class
1 parent 4dc4aa5 commit 56d1001

16 files changed

+984
-66
lines changed

google-cloud-datastore/src/main/java/com/google/cloud/datastore/AggregationQuery.java

+13
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import com.google.cloud.datastore.aggregation.Aggregation;
2323
import com.google.cloud.datastore.aggregation.AggregationBuilder;
24+
import java.util.Arrays;
2425
import java.util.HashSet;
2526
import java.util.Set;
2627

@@ -143,6 +144,18 @@ public Builder addAggregation(Aggregation aggregation) {
143144
return this;
144145
}
145146

147+
public Builder addAggregations(AggregationBuilder<?>... aggregationBuilders) {
148+
for (AggregationBuilder<?> builder : aggregationBuilders) {
149+
this.aggregations.add(builder.build());
150+
}
151+
return this;
152+
}
153+
154+
public Builder addAggregations(Aggregation... aggregations) {
155+
this.aggregations.addAll(Arrays.asList(aggregations));
156+
return this;
157+
}
158+
146159
public Builder over(StructuredQuery<?> nestedQuery) {
147160
this.nestedStructuredQuery = nestedQuery;
148161
this.mode = STRUCTURED;

google-cloud-datastore/src/main/java/com/google/cloud/datastore/AggregationResult.java

+49-5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
package com.google.cloud.datastore;
1717

18+
import static com.google.cloud.datastore.ValueType.DOUBLE;
19+
import static com.google.cloud.datastore.ValueType.LONG;
20+
1821
import com.google.common.base.MoreObjects;
1922
import com.google.common.base.MoreObjects.ToStringHelper;
2023
import java.util.Map;
@@ -24,21 +27,62 @@
2427
/** Represents a result of an {@link AggregationQuery} query submission. */
2528
public class AggregationResult {
2629

27-
private final Map<String, LongValue> properties;
30+
private final Map<String, ? extends Value<?>> properties;
2831

29-
public AggregationResult(Map<String, LongValue> properties) {
32+
public AggregationResult(Map<String, ? extends Value<?>> properties) {
3033
this.properties = properties;
3134
}
3235

3336
/**
34-
* Returns a result value for the given alias.
37+
* Returns a result value for the given alias. {@link #getLong(String)} is preferred over this
38+
* method, Use {@link #getLong(String)} wherever possible.
3539
*
3640
* @param alias A custom alias provided in the query or an autogenerated alias in the form of
3741
* 'property_\d'
3842
* @return An aggregation result value for the given alias.
3943
*/
4044
public Long get(String alias) {
41-
return properties.get(alias).get();
45+
return getLong(alias);
46+
}
47+
48+
/**
49+
* Returns a result value for the given alias.
50+
*
51+
* @param alias A custom alias provided in the query or an autogenerated alias in the form of
52+
* 'property_\d'
53+
* @return An aggregation result value for the given alias.
54+
*/
55+
public Long getLong(String alias) {
56+
Value<?> value = properties.get(alias);
57+
switch (value.getType()) {
58+
case DOUBLE:
59+
return ((Double) value.get()).longValue();
60+
case LONG:
61+
return (Long) value.get();
62+
default:
63+
throw new RuntimeException(
64+
String.format("Unsupported type %s received for alias '%s'.", value.getType(), alias));
65+
}
66+
}
67+
68+
/**
69+
* Returns a result value for the given alias.
70+
*
71+
* @param alias A custom alias provided in the query or an autogenerated alias in the form of
72+
* 'property_\d'
73+
* @return An aggregation result value for the given alias.
74+
*/
75+
public Double getDouble(String alias) {
76+
Value<?> value = properties.get(alias);
77+
switch (value.getType()) {
78+
case LONG:
79+
return ((Long) value.get()).doubleValue();
80+
case DOUBLE:
81+
return (Double) value.get();
82+
default:
83+
throw new RuntimeException(
84+
String.format("Unsupported type %s received for alias '%s'.", value.getType(), alias));
85+
}
4286
}
4387

4488
@Override
@@ -61,7 +105,7 @@ public int hashCode() {
61105
@Override
62106
public String toString() {
63107
ToStringHelper toStringHelper = MoreObjects.toStringHelper(this);
64-
for (Entry<String, LongValue> entry : properties.entrySet()) {
108+
for (Entry<String, ? extends Value<?>> entry : properties.entrySet()) {
65109
toStringHelper.add(entry.getKey(), entry.getValue().get());
66110
}
67111
return toStringHelper.toString();

google-cloud-datastore/src/main/java/com/google/cloud/datastore/aggregation/Aggregation.java

+20
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,28 @@ public String getAlias() {
3838
@InternalApi
3939
public abstract AggregationQuery.Aggregation toPb();
4040

41+
@InternalApi
42+
protected AggregationQuery.Aggregation.Builder aggregationBuilder() {
43+
AggregationQuery.Aggregation.Builder aggregationBuilder =
44+
AggregationQuery.Aggregation.newBuilder();
45+
if (this.getAlias() != null) {
46+
aggregationBuilder.setAlias(this.getAlias());
47+
}
48+
return aggregationBuilder;
49+
}
50+
4151
/** Returns a {@link CountAggregation} builder. */
4252
public static CountAggregation.Builder count() {
4353
return new CountAggregation.Builder();
4454
}
55+
56+
/** Returns a {@link SumAggregation} builder. */
57+
public static SumAggregation.Builder sum(String propertyReference) {
58+
return new SumAggregation.Builder().propertyReference(propertyReference);
59+
}
60+
61+
/** Returns a {@link AvgAggregation} builder. */
62+
public static AvgAggregation.Builder avg(String propertyReference) {
63+
return new AvgAggregation.Builder().propertyReference(propertyReference);
64+
}
4565
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.datastore.aggregation;
18+
19+
import static com.google.common.base.Preconditions.checkArgument;
20+
21+
import com.google.api.core.InternalApi;
22+
import com.google.datastore.v1.AggregationQuery;
23+
import com.google.datastore.v1.AggregationQuery.Aggregation.Avg;
24+
import com.google.datastore.v1.PropertyReference;
25+
import java.util.Objects;
26+
27+
/** Represents an {@link Aggregation} which returns average of numerical values. */
28+
public class AvgAggregation extends Aggregation {
29+
30+
private final String propertyReference;
31+
32+
public AvgAggregation(String alias, String propertyReference) {
33+
super(alias);
34+
checkArgument(propertyReference != null, "Property reference can't be null");
35+
this.propertyReference = propertyReference;
36+
}
37+
38+
@InternalApi
39+
@Override
40+
public AggregationQuery.Aggregation toPb() {
41+
PropertyReference reference =
42+
PropertyReference.newBuilder().setName(this.propertyReference).build();
43+
Avg avg = Avg.newBuilder().setProperty(reference).build();
44+
return aggregationBuilder().setAvg(avg).build();
45+
}
46+
47+
@Override
48+
public boolean equals(Object o) {
49+
if (this == o) {
50+
return true;
51+
}
52+
if (o == null || getClass() != o.getClass()) {
53+
return false;
54+
}
55+
AvgAggregation that = (AvgAggregation) o;
56+
return Objects.equals(this.propertyReference, that.propertyReference)
57+
&& Objects.equals(getAlias(), that.getAlias());
58+
}
59+
60+
@Override
61+
public int hashCode() {
62+
return Objects.hash(getAlias(), this.propertyReference);
63+
}
64+
65+
/** A builder class to create and customize a {@link AvgAggregation}. */
66+
public static class Builder implements AggregationBuilder<AvgAggregation> {
67+
68+
private String alias;
69+
private String propertyReference;
70+
71+
public AvgAggregation.Builder propertyReference(String propertyReference) {
72+
this.propertyReference = propertyReference;
73+
return this;
74+
}
75+
76+
public AvgAggregation.Builder as(String alias) {
77+
this.alias = alias;
78+
return this;
79+
}
80+
81+
@Override
82+
public AvgAggregation build() {
83+
return new AvgAggregation(alias, propertyReference);
84+
}
85+
}
86+
}

google-cloud-datastore/src/main/java/com/google/cloud/datastore/aggregation/CountAggregation.java

+2-15
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,7 @@ public CountAggregation(String alias) {
3030

3131
@Override
3232
public AggregationQuery.Aggregation toPb() {
33-
Count.Builder countBuilder = Count.newBuilder();
34-
35-
AggregationQuery.Aggregation.Builder aggregationBuilder =
36-
AggregationQuery.Aggregation.newBuilder().setCount(countBuilder);
37-
if (this.getAlias() != null) {
38-
aggregationBuilder.setAlias(this.getAlias());
39-
}
40-
return aggregationBuilder.build();
33+
return aggregationBuilder().setCount(Count.newBuilder()).build();
4134
}
4235

4336
@Override
@@ -49,13 +42,7 @@ public boolean equals(Object o) {
4942
return false;
5043
}
5144
CountAggregation that = (CountAggregation) o;
52-
boolean bothAliasAreNull = getAlias() == null && that.getAlias() == null;
53-
if (bothAliasAreNull) {
54-
return true;
55-
} else {
56-
boolean bothArePresent = getAlias() != null && that.getAlias() != null;
57-
return bothArePresent && getAlias().equals(that.getAlias());
58-
}
45+
return Objects.equals(getAlias(), that.getAlias());
5946
}
6047

6148
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.datastore.aggregation;
18+
19+
import static com.google.common.base.Preconditions.checkArgument;
20+
21+
import com.google.api.core.InternalApi;
22+
import com.google.datastore.v1.AggregationQuery;
23+
import com.google.datastore.v1.AggregationQuery.Aggregation.Sum;
24+
import com.google.datastore.v1.PropertyReference;
25+
import java.util.Objects;
26+
27+
/** Represents an {@link Aggregation} which returns sum of numerical values. */
28+
public class SumAggregation extends Aggregation {
29+
30+
private final String propertyReference;
31+
32+
public SumAggregation(String alias, String propertyReference) {
33+
super(alias);
34+
checkArgument(propertyReference != null, "Property reference can't be null");
35+
this.propertyReference = propertyReference;
36+
}
37+
38+
@InternalApi
39+
@Override
40+
public AggregationQuery.Aggregation toPb() {
41+
PropertyReference reference =
42+
PropertyReference.newBuilder().setName(this.propertyReference).build();
43+
Sum sum = Sum.newBuilder().setProperty(reference).build();
44+
return aggregationBuilder().setSum(sum).build();
45+
}
46+
47+
@Override
48+
public boolean equals(Object o) {
49+
if (this == o) {
50+
return true;
51+
}
52+
if (o == null || getClass() != o.getClass()) {
53+
return false;
54+
}
55+
SumAggregation that = (SumAggregation) o;
56+
return Objects.equals(this.propertyReference, that.propertyReference)
57+
&& Objects.equals(getAlias(), that.getAlias());
58+
}
59+
60+
@Override
61+
public int hashCode() {
62+
return Objects.hash(getAlias(), this.propertyReference);
63+
}
64+
65+
/** A builder class to create and customize a {@link SumAggregation}. */
66+
public static class Builder implements AggregationBuilder<SumAggregation> {
67+
68+
private String alias;
69+
private String propertyReference;
70+
71+
public SumAggregation.Builder propertyReference(String propertyReference) {
72+
this.propertyReference = propertyReference;
73+
return this;
74+
}
75+
76+
public SumAggregation.Builder as(String alias) {
77+
this.alias = alias;
78+
return this;
79+
}
80+
81+
@Override
82+
public SumAggregation build() {
83+
return new SumAggregation(alias, propertyReference);
84+
}
85+
}
86+
}

google-cloud-datastore/src/main/java/com/google/cloud/datastore/execution/response/AggregationQueryResponseTransformer.java

+4-6
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import com.google.cloud.Timestamp;
2020
import com.google.cloud.datastore.AggregationResult;
2121
import com.google.cloud.datastore.AggregationResults;
22-
import com.google.cloud.datastore.LongValue;
2322
import com.google.datastore.v1.RunAggregationQueryResponse;
2423
import com.google.datastore.v1.Value;
2524
import java.util.AbstractMap.SimpleEntry;
@@ -39,20 +38,19 @@ public AggregationResults transform(RunAggregationQueryResponse response) {
3938
Timestamp readTime = Timestamp.fromProto(response.getBatch().getReadTime());
4039
List<AggregationResult> aggregationResults =
4140
response.getBatch().getAggregationResultsList().stream()
42-
.map(
43-
aggregationResult -> new AggregationResult(resultWithLongValues(aggregationResult)))
41+
.map(aggregationResult -> new AggregationResult(transformValues(aggregationResult)))
4442
.collect(Collectors.toCollection(LinkedList::new));
4543
return new AggregationResults(aggregationResults, readTime);
4644
}
4745

48-
private Map<String, LongValue> resultWithLongValues(
46+
private Map<String, com.google.cloud.datastore.Value<?>> transformValues(
4947
com.google.datastore.v1.AggregationResult aggregationResult) {
5048
return aggregationResult.getAggregatePropertiesMap().entrySet().stream()
5149
.map(
52-
(Function<Entry<String, Value>, Entry<String, LongValue>>)
50+
(Function<Entry<String, Value>, Entry<String, com.google.cloud.datastore.Value<?>>>)
5351
entry ->
5452
new SimpleEntry<>(
55-
entry.getKey(), (LongValue) LongValue.fromPb(entry.getValue())))
53+
entry.getKey(), com.google.cloud.datastore.Value.fromPb(entry.getValue())))
5654
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
5755
}
5856
}

0 commit comments

Comments
 (0)