diff --git a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FieldValue.java b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FieldValue.java index cef86f922..6e7a8a542 100644 --- a/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FieldValue.java +++ b/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FieldValue.java @@ -126,6 +126,104 @@ public int hashCode() { } } + static class NumericMaximumFieldValue extends FieldValue { + final Number operand; + + NumericMaximumFieldValue(Number operand) { + this.operand = operand; + } + + @Override + boolean includeInDocumentMask() { + return false; + } + + @Override + boolean includeInDocumentTransform() { + return true; + } + + @Override + String getMethodName() { + return "FieldValue.maximum()"; + } + + @Override + FieldTransform toProto(FieldPath path) { + FieldTransform.Builder fieldTransform = FieldTransform.newBuilder(); + fieldTransform.setFieldPath(path.getEncodedPath()); + fieldTransform.setMaximum( + UserDataConverter.encodeValue(path, operand, UserDataConverter.ARGUMENT)); + return fieldTransform.build(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NumericMaximumFieldValue that = (NumericMaximumFieldValue) o; + return Objects.equals(operand, that.operand); + } + + @Override + public int hashCode() { + return Objects.hash(operand); + } + } + + static class NumericMinimumFieldValue extends FieldValue { + final Number operand; + + NumericMinimumFieldValue(Number operand) { + this.operand = operand; + } + + @Override + boolean includeInDocumentMask() { + return false; + } + + @Override + boolean includeInDocumentTransform() { + return true; + } + + @Override + String getMethodName() { + return "FieldValue.minimum()"; + } + + @Override + FieldTransform toProto(FieldPath path) { + FieldTransform.Builder fieldTransform = FieldTransform.newBuilder(); + fieldTransform.setFieldPath(path.getEncodedPath()); + fieldTransform.setMinimum( + UserDataConverter.encodeValue(path, operand, UserDataConverter.ARGUMENT)); + return fieldTransform.build(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NumericMinimumFieldValue that = (NumericMinimumFieldValue) o; + return Objects.equals(operand, that.operand); + } + + @Override + public int hashCode() { + return Objects.hash(operand); + } + } + static class ArrayUnionFieldValue extends FieldValue { final List elements; @@ -288,6 +386,58 @@ public static FieldValue increment(double d) { return new NumericIncrementFieldValue(d); } + /** + * Returns a special value that can be used with set(), create() or update() that tells the server + * to take the maximum of the field's current value and the given value. + * + *

If the current field is not an integer or double, or if the field does not yet exist, the + * transformation will set the field to the given value. + * + * @return The FieldValue sentinel for use in a call to set(), create() or update(). + */ + @Nonnull + public static FieldValue maximum(long l) { return new NumericMaximumFieldValue(l); } + + /** + * Returns a special value that can be used with set(), create() or update() that tells the server + * to take the maximum of the field's current value and the given value. + * + *

If the current field is not an integer or double, or if the field does not yet exist, the + * transformation will set the field to the given value. + * + * @return The FieldValue sentinel for use in a call to set(), create() or update(). + */ + @Nonnull + public static FieldValue maximum(double d) { + return new NumericMaximumFieldValue(d); + } + + /** + * Returns a special value that can be used with set(), create() or update() that tells the server + * to take the minimum of the field's current value and the given value. + * + *

If the current field is not an integer or double, or if the field does not yet exist, the + * transformation will set the field to the given value. + * + * @return The FieldValue sentinel for use in a call to set(), create() or update(). + */ + @Nonnull + public static FieldValue minimum(long l) { return new NumericMinimumFieldValue(l); } + + /** + * Returns a special value that can be used with set(), create() or update() that tells the server + * to take the minimum of the field's current value and the given value. + * + *

If the current field is not an integer or double, or if the field does not yet exist, the + * transformation will set the field to the given value. + * + * @return The FieldValue sentinel for use in a call to set(), create() or update(). + */ + @Nonnull + public static FieldValue minimum(double d) { + return new NumericMinimumFieldValue(d); + } + /** * Returns a special value that can be used with set(), create() or update() that tells the server * to union the given elements with any array value that already exists on the server. Each diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java index aecc8b6be..c10a56aa5 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java @@ -48,6 +48,8 @@ import static com.google.cloud.firestore.LocalFirestoreHelper.getAllResponse; import static com.google.cloud.firestore.LocalFirestoreHelper.increment; import static com.google.cloud.firestore.LocalFirestoreHelper.map; +import static com.google.cloud.firestore.LocalFirestoreHelper.maximum; +import static com.google.cloud.firestore.LocalFirestoreHelper.minimum; import static com.google.cloud.firestore.LocalFirestoreHelper.object; import static com.google.cloud.firestore.LocalFirestoreHelper.serverTimestamp; import static com.google.cloud.firestore.LocalFirestoreHelper.set; @@ -517,6 +519,54 @@ public void setWithIncrement() throws Exception { assertCommitEquals(set, commitRequest); } + @Test + public void setWithMaximum() throws Exception { + doReturn(FIELD_TRANSFORM_COMMIT_RESPONSE) + .when(firestoreMock) + .sendRequest( + commitCapture.capture(), Matchers.>any()); + + documentReference + .set(map("integer", FieldValue.maximum(1), "double", FieldValue.maximum(1.1))) + .get(); + + CommitRequest set = + commit( + set(Collections.emptyMap()), + transform( + "integer", + maximum(Value.newBuilder().setIntegerValue(1).build()), + "double", + maximum(Value.newBuilder().setDoubleValue(1.1).build()))); + + CommitRequest commitRequest = commitCapture.getValue(); + assertCommitEquals(set, commitRequest); + } + + @Test + public void setWithMinimum() throws Exception { + doReturn(FIELD_TRANSFORM_COMMIT_RESPONSE) + .when(firestoreMock) + .sendRequest( + commitCapture.capture(), Matchers.>any()); + + documentReference + .set(map("integer", FieldValue.minimum(1), "double", FieldValue.minimum(1.1))) + .get(); + + CommitRequest set = + commit( + set(Collections.emptyMap()), + transform( + "integer", + minimum(Value.newBuilder().setIntegerValue(1).build()), + "double", + minimum(Value.newBuilder().setDoubleValue(1.1).build()))); + + CommitRequest commitRequest = commitCapture.getValue(); + assertCommitEquals(set, commitRequest); + } + @Test public void setWithArrayUnion() throws Exception { doReturn(FIELD_TRANSFORM_COMMIT_RESPONSE) diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/FirestoreTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/FirestoreTest.java index 9d685022d..4684ed8b1 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/FirestoreTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/FirestoreTest.java @@ -185,6 +185,30 @@ public void incrementEquals() { assertNotEquals(increment2, increment4); } + @Test + public void maximumEquals() { + FieldValue maximum1 = FieldValue.maximum(42); + FieldValue maximum2 = FieldValue.maximum(42); + FieldValue maximum3 = FieldValue.maximum(42.0); + FieldValue maximum4 = FieldValue.maximum(42.0); + assertEquals(maximum1, maximum2); + assertEquals(maximum3, maximum4); + assertNotEquals(maximum1, maximum3); + assertNotEquals(maximum2, maximum4); + } + + @Test + public void minimumEquals() { + FieldValue minimum1 = FieldValue.minimum(42); + FieldValue minimum2 = FieldValue.minimum(42); + FieldValue minimum3 = FieldValue.minimum(42.0); + FieldValue minimum4 = FieldValue.minimum(42.0); + assertEquals(minimum1, minimum2); + assertEquals(minimum3, minimum4); + assertNotEquals(minimum1, minimum3); + assertNotEquals(minimum2, minimum4); + } + @Test public void arrayUnionWithPojo() throws ExecutionException, InterruptedException { doReturn(commitResponse(1, 0)) 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 6aec26e9a..0bd311761 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 @@ -486,6 +486,14 @@ public static FieldTransform increment(Value value) { return FieldTransform.newBuilder().setIncrement(value).build(); } + public static FieldTransform minimum(Value value) { + return FieldTransform.newBuilder().setMinimum(value).build(); + } + + public static FieldTransform maximum(Value value) { + return FieldTransform.newBuilder().setMaximum(value).build(); + } + public static FieldTransform arrayUnion(Value... values) { return FieldTransform.newBuilder() .setAppendMissingElements(ArrayValue.newBuilder().addAllValues(Arrays.asList(values))) diff --git a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITSystemTest.java b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITSystemTest.java index 1ce42901b..3897c13e5 100644 --- a/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITSystemTest.java +++ b/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITSystemTest.java @@ -279,6 +279,24 @@ public void setWithIncrementAndMerge() throws ExecutionException, InterruptedExc assertEquals(3L, docSnap.get("sum")); } + @Test + public void setWithMaximumAndMerge() throws ExecutionException, InterruptedException { + DocumentReference docRef = randomColl.document(); + docRef.set(Collections.singletonMap("max", 1L)).get(); + docRef.set(Collections.singletonMap("max", FieldValue.maximum(2)), SetOptions.merge()).get(); + DocumentSnapshot docSnap = docRef.get().get(); + assertEquals(2L, docSnap.get("max")); + } + + @Test + public void setWithMinimumAndMerge() throws ExecutionException, InterruptedException { + DocumentReference docRef = randomColl.document(); + docRef.set(Collections.singletonMap("min", 1L)).get(); + docRef.set(Collections.singletonMap("min", FieldValue.minimum(2)), SetOptions.merge()).get(); + DocumentSnapshot docSnap = docRef.get().get(); + assertEquals(1L, docSnap.get("min")); + } + @Test public void mergeDocumentWithServerTimestamp() throws Exception { Map originalMap = LocalFirestoreHelper.map("a", "b"); @@ -1392,6 +1410,42 @@ public void floatIncrement() throws ExecutionException, InterruptedException { assertEquals(3.3, (Double) docSnap.get("sum"), DOUBLE_EPSILON); } + @Test + public void integerMaximum() throws ExecutionException, InterruptedException { + DocumentReference docRef = randomColl.document(); + docRef.set(Collections.singletonMap("max", 1L)).get(); + docRef.update("max", FieldValue.maximum(2)).get(); + DocumentSnapshot docSnap = docRef.get().get(); + assertEquals(2L, docSnap.get("max")); + } + + @Test + public void floatMaximum() throws ExecutionException, InterruptedException { + DocumentReference docRef = randomColl.document(); + docRef.set(Collections.singletonMap("max", 1.1)).get(); + docRef.update("max", FieldValue.maximum(2.2)).get(); + DocumentSnapshot docSnap = docRef.get().get(); + assertEquals(2.2, (Double) docSnap.get("max"), DOUBLE_EPSILON); + } + + @Test + public void integerMinimum() throws ExecutionException, InterruptedException { + DocumentReference docRef = randomColl.document(); + docRef.set(Collections.singletonMap("min", 1L)).get(); + docRef.update("min", FieldValue.minimum(2)).get(); + DocumentSnapshot docSnap = docRef.get().get(); + assertEquals(1L, docSnap.get("min")); + } + + @Test + public void floatMinimum() throws ExecutionException, InterruptedException { + DocumentReference docRef = randomColl.document(); + docRef.set(Collections.singletonMap("min", 1.1)).get(); + docRef.update("min", FieldValue.minimum(2.2)).get(); + DocumentSnapshot docSnap = docRef.get().get(); + assertEquals(1.1, (Double) docSnap.get("min"), DOUBLE_EPSILON); + } + @Test public void getAllWithObserver() throws Exception { DocumentReference ref1 = randomColl.document("doc1");