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 2cfc41acf..3854e2f4b 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 @@ -127,6 +127,104 @@ public int hashCode() { } } + 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 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 ArrayUnionFieldValue extends FieldValue { final List elements; @@ -289,6 +387,62 @@ 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 set the field to the numeric minimum of the field's current and the given value. + * + *

If the current field value is not of type 'number', 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 set the field to the numeric minimum of the field's current and the given value. + * + *

If the current field value is not of type 'number', 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 set the field to the numeric maximum of the field's current and the given value. + * + *

If the current field value is not of type 'number', 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 set the field to the numeric maximum of the field's current and the given value. + * + *

If the current field value is not of type 'number', 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 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 b425e5879..d043168b4 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; @@ -530,6 +532,56 @@ public void setWithIncrement() throws Exception { assertCommitEquals(set, commitRequest); } + @Test + public void setWithMinimum() throws Exception { + doReturn(FIELD_TRANSFORM_COMMIT_RESPONSE) + .when(firestoreMock) + .sendRequest( + commitCapture.capture(), + ArgumentMatchers.>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 setWithMaximum() throws Exception { + doReturn(FIELD_TRANSFORM_COMMIT_RESPONSE) + .when(firestoreMock) + .sendRequest( + commitCapture.capture(), + ArgumentMatchers.>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 setWithArrayUnion() throws Exception { doReturn(FIELD_TRANSFORM_COMMIT_RESPONSE) 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 500e35ff9..4396247cb 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 @@ -501,6 +501,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 1712046be..40c1ac5ed 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 @@ -1970,6 +1970,60 @@ public void floatIncrement() throws ExecutionException, InterruptedException { assertEquals(3.3, (Double) docSnap.get("sum"), DOUBLE_EPSILON); } + @Test + public void integerMinimum() throws ExecutionException, InterruptedException { + DocumentReference docRef = randomColl.document(); + docRef.set(Collections.singletonMap("min", 2L)).get(); + docRef.update("min", FieldValue.minimum(1)).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", 2.2)).get(); + docRef.update("min", FieldValue.minimum(1.1)).get(); + DocumentSnapshot docSnap = docRef.get().get(); + assertEquals(1.1, (Double) docSnap.get("min"), DOUBLE_EPSILON); + } + + @Test + public void minimumAgainstNonNumeric() throws ExecutionException, InterruptedException { + DocumentReference docRef = randomColl.document(); + docRef.set(Collections.singletonMap("min", "any string")).get(); + docRef.update("min", FieldValue.minimum(1)).get(); + DocumentSnapshot docSnap = docRef.get().get(); + assertEquals(1L, docSnap.get("min")); + } + + @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 maximumAgainstNonNumeric() throws ExecutionException, InterruptedException { + DocumentReference docRef = randomColl.document(); + docRef.set(Collections.singletonMap("max", "any string")).get(); + docRef.update("max", FieldValue.maximum(2)).get(); + DocumentSnapshot docSnap = docRef.get().get(); + assertEquals(2L, docSnap.get("max")); + } + @Test public void getAllWithObserver() throws Exception { DocumentReference ref1 = randomColl.document("doc1");