Skip to content
This repository was archived by the owner on May 8, 2026. It is now read-only.

Commit d284bf2

Browse files
feature(firestore): added minimum and maximum FieldValue operations (#2375)
1 parent 60d8e38 commit d284bf2

4 files changed

Lines changed: 268 additions & 0 deletions

File tree

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

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,104 @@ public int hashCode() {
127127
}
128128
}
129129

130+
static class NumericMinimumFieldValue extends FieldValue {
131+
final Number operand;
132+
133+
NumericMinimumFieldValue(Number operand) {
134+
this.operand = operand;
135+
}
136+
137+
@Override
138+
boolean includeInDocumentMask() {
139+
return false;
140+
}
141+
142+
@Override
143+
boolean includeInDocumentTransform() {
144+
return true;
145+
}
146+
147+
@Override
148+
String getMethodName() {
149+
return "FieldValue.minimum()";
150+
}
151+
152+
@Override
153+
FieldTransform toProto(FieldPath path) {
154+
FieldTransform.Builder fieldTransform = FieldTransform.newBuilder();
155+
fieldTransform.setFieldPath(path.getEncodedPath());
156+
fieldTransform.setMinimum(
157+
UserDataConverter.encodeValue(path, operand, UserDataConverter.ARGUMENT));
158+
return fieldTransform.build();
159+
}
160+
161+
@Override
162+
public boolean equals(Object o) {
163+
if (this == o) {
164+
return true;
165+
}
166+
if (o == null || getClass() != o.getClass()) {
167+
return false;
168+
}
169+
NumericMinimumFieldValue that = (NumericMinimumFieldValue) o;
170+
return Objects.equals(operand, that.operand);
171+
}
172+
173+
@Override
174+
public int hashCode() {
175+
return Objects.hash(operand);
176+
}
177+
}
178+
179+
static class NumericMaximumFieldValue extends FieldValue {
180+
final Number operand;
181+
182+
NumericMaximumFieldValue(Number operand) {
183+
this.operand = operand;
184+
}
185+
186+
@Override
187+
boolean includeInDocumentMask() {
188+
return false;
189+
}
190+
191+
@Override
192+
boolean includeInDocumentTransform() {
193+
return true;
194+
}
195+
196+
@Override
197+
String getMethodName() {
198+
return "FieldValue.maximum()";
199+
}
200+
201+
@Override
202+
FieldTransform toProto(FieldPath path) {
203+
FieldTransform.Builder fieldTransform = FieldTransform.newBuilder();
204+
fieldTransform.setFieldPath(path.getEncodedPath());
205+
fieldTransform.setMaximum(
206+
UserDataConverter.encodeValue(path, operand, UserDataConverter.ARGUMENT));
207+
return fieldTransform.build();
208+
}
209+
210+
@Override
211+
public boolean equals(Object o) {
212+
if (this == o) {
213+
return true;
214+
}
215+
if (o == null || getClass() != o.getClass()) {
216+
return false;
217+
}
218+
NumericMaximumFieldValue that = (NumericMaximumFieldValue) o;
219+
return Objects.equals(operand, that.operand);
220+
}
221+
222+
@Override
223+
public int hashCode() {
224+
return Objects.hash(operand);
225+
}
226+
}
227+
130228
static class ArrayUnionFieldValue extends FieldValue {
131229
final List<Object> elements;
132230

@@ -289,6 +387,62 @@ public static FieldValue increment(double d) {
289387
return new NumericIncrementFieldValue(d);
290388
}
291389

390+
/**
391+
* Returns a special value that can be used with set(), create() or update() that tells the server
392+
* to set the field to the numeric minimum of the field's current and the given value.
393+
*
394+
* <p>If the current field value is not of type 'number', or if the field does not yet exist, the
395+
* transformation will set the field to the given value.
396+
*
397+
* @return The FieldValue sentinel for use in a call to set(), create() or update().
398+
*/
399+
@Nonnull
400+
public static FieldValue minimum(long l) {
401+
return new NumericMinimumFieldValue(l);
402+
}
403+
404+
/**
405+
* Returns a special value that can be used with set(), create() or update() that tells the server
406+
* to set the field to the numeric minimum of the field's current and the given value.
407+
*
408+
* <p>If the current field value is not of type 'number', or if the field does not yet exist, the
409+
* transformation will set the field to the given value.
410+
*
411+
* @return The FieldValue sentinel for use in a call to set(), create() or update().
412+
*/
413+
@Nonnull
414+
public static FieldValue minimum(double d) {
415+
return new NumericMinimumFieldValue(d);
416+
}
417+
418+
/**
419+
* Returns a special value that can be used with set(), create() or update() that tells the server
420+
* to set the field to the numeric maximum of the field's current and the given value.
421+
*
422+
* <p>If the current field value is not of type 'number', or if the field does not yet exist, the
423+
* transformation will set the field to the given value.
424+
*
425+
* @return The FieldValue sentinel for use in a call to set(), create() or update().
426+
*/
427+
@Nonnull
428+
public static FieldValue maximum(long l) {
429+
return new NumericMaximumFieldValue(l);
430+
}
431+
432+
/**
433+
* Returns a special value that can be used with set(), create() or update() that tells the server
434+
* to set the field to the numeric maximum of the field's current and the given value.
435+
*
436+
* <p>If the current field value is not of type 'number', or if the field does not yet exist, the
437+
* transformation will set the field to the given value.
438+
*
439+
* @return The FieldValue sentinel for use in a call to set(), create() or update().
440+
*/
441+
@Nonnull
442+
public static FieldValue maximum(double d) {
443+
return new NumericMaximumFieldValue(d);
444+
}
445+
292446
/**
293447
* Returns a special value that can be used with set(), create() or update() that tells the server
294448
* to union the given elements with any array value that already exists on the server. Each

google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
import static com.google.cloud.firestore.LocalFirestoreHelper.getAllResponse;
4949
import static com.google.cloud.firestore.LocalFirestoreHelper.increment;
5050
import static com.google.cloud.firestore.LocalFirestoreHelper.map;
51+
import static com.google.cloud.firestore.LocalFirestoreHelper.maximum;
52+
import static com.google.cloud.firestore.LocalFirestoreHelper.minimum;
5153
import static com.google.cloud.firestore.LocalFirestoreHelper.object;
5254
import static com.google.cloud.firestore.LocalFirestoreHelper.serverTimestamp;
5355
import static com.google.cloud.firestore.LocalFirestoreHelper.set;
@@ -530,6 +532,56 @@ public void setWithIncrement() throws Exception {
530532
assertCommitEquals(set, commitRequest);
531533
}
532534

535+
@Test
536+
public void setWithMinimum() throws Exception {
537+
doReturn(FIELD_TRANSFORM_COMMIT_RESPONSE)
538+
.when(firestoreMock)
539+
.sendRequest(
540+
commitCapture.capture(),
541+
ArgumentMatchers.<UnaryCallable<CommitRequest, CommitResponse>>any());
542+
543+
documentReference
544+
.set(map("integer", FieldValue.minimum(1), "double", FieldValue.minimum(1.1)))
545+
.get();
546+
547+
CommitRequest set =
548+
commit(
549+
set(Collections.emptyMap()),
550+
transform(
551+
"integer",
552+
minimum(Value.newBuilder().setIntegerValue(1).build()),
553+
"double",
554+
minimum(Value.newBuilder().setDoubleValue(1.1).build())));
555+
556+
CommitRequest commitRequest = commitCapture.getValue();
557+
assertCommitEquals(set, commitRequest);
558+
}
559+
560+
@Test
561+
public void setWithMaximum() throws Exception {
562+
doReturn(FIELD_TRANSFORM_COMMIT_RESPONSE)
563+
.when(firestoreMock)
564+
.sendRequest(
565+
commitCapture.capture(),
566+
ArgumentMatchers.<UnaryCallable<CommitRequest, CommitResponse>>any());
567+
568+
documentReference
569+
.set(map("integer", FieldValue.maximum(1), "double", FieldValue.maximum(1.1)))
570+
.get();
571+
572+
CommitRequest set =
573+
commit(
574+
set(Collections.emptyMap()),
575+
transform(
576+
"integer",
577+
maximum(Value.newBuilder().setIntegerValue(1).build()),
578+
"double",
579+
maximum(Value.newBuilder().setDoubleValue(1.1).build())));
580+
581+
CommitRequest commitRequest = commitCapture.getValue();
582+
assertCommitEquals(set, commitRequest);
583+
}
584+
533585
@Test
534586
public void setWithArrayUnion() throws Exception {
535587
doReturn(FIELD_TRANSFORM_COMMIT_RESPONSE)

google-cloud-firestore/src/test/java/com/google/cloud/firestore/LocalFirestoreHelper.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,14 @@ public static FieldTransform increment(Value value) {
501501
return FieldTransform.newBuilder().setIncrement(value).build();
502502
}
503503

504+
public static FieldTransform minimum(Value value) {
505+
return FieldTransform.newBuilder().setMinimum(value).build();
506+
}
507+
508+
public static FieldTransform maximum(Value value) {
509+
return FieldTransform.newBuilder().setMaximum(value).build();
510+
}
511+
504512
public static FieldTransform arrayUnion(Value... values) {
505513
return FieldTransform.newBuilder()
506514
.setAppendMissingElements(ArrayValue.newBuilder().addAllValues(Arrays.asList(values)))

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

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1970,6 +1970,60 @@ public void floatIncrement() throws ExecutionException, InterruptedException {
19701970
assertEquals(3.3, (Double) docSnap.get("sum"), DOUBLE_EPSILON);
19711971
}
19721972

1973+
@Test
1974+
public void integerMinimum() throws ExecutionException, InterruptedException {
1975+
DocumentReference docRef = randomColl.document();
1976+
docRef.set(Collections.singletonMap("min", 2L)).get();
1977+
docRef.update("min", FieldValue.minimum(1)).get();
1978+
DocumentSnapshot docSnap = docRef.get().get();
1979+
assertEquals(1L, docSnap.get("min"));
1980+
}
1981+
1982+
@Test
1983+
public void floatMinimum() throws ExecutionException, InterruptedException {
1984+
DocumentReference docRef = randomColl.document();
1985+
docRef.set(Collections.singletonMap("min", 2.2)).get();
1986+
docRef.update("min", FieldValue.minimum(1.1)).get();
1987+
DocumentSnapshot docSnap = docRef.get().get();
1988+
assertEquals(1.1, (Double) docSnap.get("min"), DOUBLE_EPSILON);
1989+
}
1990+
1991+
@Test
1992+
public void minimumAgainstNonNumeric() throws ExecutionException, InterruptedException {
1993+
DocumentReference docRef = randomColl.document();
1994+
docRef.set(Collections.singletonMap("min", "any string")).get();
1995+
docRef.update("min", FieldValue.minimum(1)).get();
1996+
DocumentSnapshot docSnap = docRef.get().get();
1997+
assertEquals(1L, docSnap.get("min"));
1998+
}
1999+
2000+
@Test
2001+
public void integerMaximum() throws ExecutionException, InterruptedException {
2002+
DocumentReference docRef = randomColl.document();
2003+
docRef.set(Collections.singletonMap("max", 1L)).get();
2004+
docRef.update("max", FieldValue.maximum(2)).get();
2005+
DocumentSnapshot docSnap = docRef.get().get();
2006+
assertEquals(2L, docSnap.get("max"));
2007+
}
2008+
2009+
@Test
2010+
public void floatMaximum() throws ExecutionException, InterruptedException {
2011+
DocumentReference docRef = randomColl.document();
2012+
docRef.set(Collections.singletonMap("max", 1.1)).get();
2013+
docRef.update("max", FieldValue.maximum(2.2)).get();
2014+
DocumentSnapshot docSnap = docRef.get().get();
2015+
assertEquals(2.2, (Double) docSnap.get("max"), DOUBLE_EPSILON);
2016+
}
2017+
2018+
@Test
2019+
public void maximumAgainstNonNumeric() throws ExecutionException, InterruptedException {
2020+
DocumentReference docRef = randomColl.document();
2021+
docRef.set(Collections.singletonMap("max", "any string")).get();
2022+
docRef.update("max", FieldValue.maximum(2)).get();
2023+
DocumentSnapshot docSnap = docRef.get().get();
2024+
assertEquals(2L, docSnap.get("max"));
2025+
}
2026+
19732027
@Test
19742028
public void getAllWithObserver() throws Exception {
19752029
DocumentReference ref1 = randomColl.document("doc1");

0 commit comments

Comments
 (0)