diff --git a/google-cloud-clients/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FieldValue.java b/google-cloud-clients/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FieldValue.java index 395810a326fa..4214e6a66d7e 100644 --- a/google-cloud-clients/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FieldValue.java +++ b/google-cloud-clients/google-cloud-firestore/src/main/java/com/google/cloud/firestore/FieldValue.java @@ -77,6 +77,55 @@ FieldTransform toProto(FieldPath path) { } }; + static class NumericIncrementFieldValue extends FieldValue { + final Number operand; + + NumericIncrementFieldValue(Number operand) { + this.operand = operand; + } + + @Override + boolean includeInDocumentMask() { + return false; + } + + @Override + boolean includeInDocumentTransform() { + return true; + } + + @Override + String getMethodName() { + return "FieldValue.increment()"; + } + + @Override + FieldTransform toProto(FieldPath path) { + FieldTransform.Builder fieldTransform = FieldTransform.newBuilder(); + fieldTransform.setFieldPath(path.getEncodedPath()); + fieldTransform.setIncrement( + 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; + } + NumericIncrementFieldValue that = (NumericIncrementFieldValue) o; + return Objects.equals(operand, that.operand); + } + + @Override + public int hashCode() { + return Objects.hash(operand); + } + } + static class ArrayUnionFieldValue extends FieldValue { final List elements; @@ -205,11 +254,44 @@ public static FieldValue delete() { } /** - * Returns a special value that can be used with set() or update() that tells the server to union - * the given elements with any array value that already exists on the server. Each specified - * element that doesn't already exist in the array will be added to the end. If the field being - * modified is not already an array it will be overwritten with an array containing exactly the - * specified elements. + * Returns a special value that can be used with set(), create() or update() that tells the server + * to increment the field's current value by the given value. + * + *

If the current field value is an integer, possible integer overflows are resolved to + * Long.MAX_VALUE or Long.MIN_VALUE. If the current field value is a double, both values will be + * interpreted as doubles and the arithmetic will follow IEEE 754 semantics. + * + *

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 increment(long l) { + return new NumericIncrementFieldValue(l); + } + + /** + * Returns a special value that can be used with set(), create() or update() that tells the server + * to increment the field's current value by the given value. + * + *

If the current value is an integer or a double, both the current and the given value will be + * interpreted as doubles and all arithmetic will follow IEEE 754 semantics. Otherwise, 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 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 union the given elements with any array value that already exists on the server. Each + * specified element that doesn't already exist in the array will be added to the end. If the + * field being modified is not already an array it will be overwritten with an array containing + * exactly the specified elements. * * @param elements The elements to union into the array. * @return The FieldValue sentinel for use in a call to set() or update(). @@ -221,10 +303,10 @@ public static FieldValue arrayUnion(@Nonnull Object... elements) { } /** - * Returns a special value that can be used with set() or update() that tells the server to remove - * the given elements from any array value that already exists on the server. All instances of - * each element specified will be removed from the array. If the field being modified is not - * already an array it will be overwritten with an empty array. + * Returns a special value that can be used with set(), create() or update() that tells the server + * to remove the given elements from any array value that already exists on the server. All + * instances of each element specified will be removed from the array. If the field being modified + * is not already an array it will be overwritten with an empty array. * * @param elements The elements to remove from the array. * @return The FieldValue sentinel for use in a call to set() or update(). diff --git a/google-cloud-clients/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UserDataConverter.java b/google-cloud-clients/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UserDataConverter.java index 9bedd8b5d7ce..24c36e36d4c7 100644 --- a/google-cloud-clients/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UserDataConverter.java +++ b/google-cloud-clients/google-cloud-firestore/src/main/java/com/google/cloud/firestore/UserDataConverter.java @@ -90,7 +90,7 @@ private UserDataConverter() {} * @param path path THe field path of the object to encode. * @param sanitizedObject An Object that has been sanitized by CustomClassMapper and only contains * valid types. - * @param options Encoding opions to use for this value. + * @param options Encoding options to use for this value. * @return The Value proto. */ @Nullable diff --git a/google-cloud-clients/google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java b/google-cloud-clients/google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java index a07dab86f326..f441f06efd70 100644 --- a/google-cloud-clients/google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java +++ b/google-cloud-clients/google-cloud-firestore/src/test/java/com/google/cloud/firestore/DocumentReferenceTest.java @@ -44,6 +44,7 @@ import static com.google.cloud.firestore.LocalFirestoreHelper.delete; import static com.google.cloud.firestore.LocalFirestoreHelper.get; 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.object; import static com.google.cloud.firestore.LocalFirestoreHelper.serverTimestamp; @@ -408,6 +409,30 @@ public void mergeWithServerTimestamps() throws Exception { assertCommitEquals(set, commitRequests.get(1)); } + @Test + public void setWithIncrement() throws Exception { + doReturn(FIELD_TRANSFORM_COMMIT_RESPONSE) + .when(firestoreMock) + .sendRequest( + commitCapture.capture(), Matchers.>any()); + + documentReference + .set(map("integer", FieldValue.increment(1), "double", FieldValue.increment(1.1))) + .get(); + + CommitRequest set = + commit( + set(Collections.emptyMap()), + transform( + "integer", + increment(Value.newBuilder().setIntegerValue(1).build()), + "double", + increment(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-clients/google-cloud-firestore/src/test/java/com/google/cloud/firestore/FirestoreTest.java b/google-cloud-clients/google-cloud-firestore/src/test/java/com/google/cloud/firestore/FirestoreTest.java index 42e4cd5e0407..f9e2e91abaa4 100644 --- a/google-cloud-clients/google-cloud-firestore/src/test/java/com/google/cloud/firestore/FirestoreTest.java +++ b/google-cloud-clients/google-cloud-firestore/src/test/java/com/google/cloud/firestore/FirestoreTest.java @@ -157,4 +157,16 @@ public void arrayRemoveEquals() { assertNotEquals(arrayRemove1, arrayRemove3); assertNotEquals(arrayRemove1, arrayUnion); } + + @Test + public void incrementEquals() { + FieldValue increment1 = FieldValue.increment(42); + FieldValue increment2 = FieldValue.increment(42); + FieldValue increment3 = FieldValue.increment(42.0); + FieldValue increment4 = FieldValue.increment(42.0); + assertEquals(increment1, increment2); + assertEquals(increment3, increment4); + assertNotEquals(increment1, increment3); + assertNotEquals(increment2, increment4); + } } diff --git a/google-cloud-clients/google-cloud-firestore/src/test/java/com/google/cloud/firestore/LocalFirestoreHelper.java b/google-cloud-clients/google-cloud-firestore/src/test/java/com/google/cloud/firestore/LocalFirestoreHelper.java index 718064bc5feb..20337f8e6095 100644 --- a/google-cloud-clients/google-cloud-firestore/src/test/java/com/google/cloud/firestore/LocalFirestoreHelper.java +++ b/google-cloud-clients/google-cloud-firestore/src/test/java/com/google/cloud/firestore/LocalFirestoreHelper.java @@ -272,6 +272,10 @@ public static FieldTransform serverTimestamp() { .build(); } + public static FieldTransform increment(Value value) { + return FieldTransform.newBuilder().setIncrement(value).build(); + } + public static FieldTransform arrayUnion(Value... values) { return FieldTransform.newBuilder() .setAppendMissingElements(ArrayValue.newBuilder().addAllValues(Arrays.asList(values))) diff --git a/google-cloud-clients/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITSystemTest.java b/google-cloud-clients/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITSystemTest.java index c4940f6ff38b..ea9771e36602 100644 --- a/google-cloud-clients/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITSystemTest.java +++ b/google-cloud-clients/google-cloud-firestore/src/test/java/com/google/cloud/firestore/it/ITSystemTest.java @@ -71,6 +71,8 @@ public class ITSystemTest { + private static final double DOUBLE_EPSILON = 0.000001; + private final Map SINGLE_FIELD_MAP = LocalFirestoreHelper.SINGLE_FIELD_MAP; private final Map ALL_SUPPORTED_TYPES_MAP = LocalFirestoreHelper.ALL_SUPPORTED_TYPES_MAP; @@ -977,4 +979,22 @@ public void arrayOperators() throws ExecutionException, InterruptedException { assertTrue(containsQuery.get().get().isEmpty()); } + + @Test + public void integerIncrement() throws ExecutionException, InterruptedException { + DocumentReference docRef = randomColl.document(); + docRef.set(Collections.singletonMap("sum", (Object) 1L)).get(); + docRef.update("sum", FieldValue.increment(2)).get(); + DocumentSnapshot docSnap = docRef.get().get(); + assertEquals(3L, docSnap.get("sum")); + } + + @Test + public void floatIncrement() throws ExecutionException, InterruptedException { + DocumentReference docRef = randomColl.document(); + docRef.set(Collections.singletonMap("sum", (Object) 1.1)).get(); + docRef.update("sum", FieldValue.increment(2.2)).get(); + DocumentSnapshot docSnap = docRef.get().get(); + assertEquals(3.3, (Double) docSnap.get("sum"), DOUBLE_EPSILON); + } }