From 8409f21830f1282a39c4b7888972011f43d2644a Mon Sep 17 00:00:00 2001 From: Michael Lehenbauer Date: Tue, 8 May 2018 15:33:10 -0700 Subject: Backport array contains / transform improvements from Web. (#1242) --- .../firestore/model/transform_operations.h | 109 ++++++++++++++++++--- 1 file changed, 96 insertions(+), 13 deletions(-) (limited to 'Firestore/core/src') diff --git a/Firestore/core/src/firebase/firestore/model/transform_operations.h b/Firestore/core/src/firebase/firestore/model/transform_operations.h index a1c2de0..abc8d1b 100644 --- a/Firestore/core/src/firebase/firestore/model/transform_operations.h +++ b/Firestore/core/src/firebase/firestore/model/transform_operations.h @@ -20,6 +20,8 @@ #include #include +#include "Firestore/core/include/firebase/firestore/timestamp.h" + #if defined(__OBJC__) #import "Firestore/Source/Model/FSTFieldValue.h" #endif @@ -49,11 +51,28 @@ class TransformOperation { /** Returns the actual type. */ virtual Type type() const = 0; +// TODO(mikelehen): apply methods require FSTFieldValue which is Obj-C only. +#if defined(__OBJC__) + /** + * Computes the local transform result against the provided `previousValue`, + * optionally using the provided localWriteTime. + */ + virtual FSTFieldValue *applyToLocalView( + FSTFieldValue *previousValue, FIRTimestamp *localWriteTime) const = 0; + + /** + * Computes a final transform result after the transform has been acknowledged + * by the server, potentially using the server-provided transformResult. + */ + virtual FSTFieldValue *applyToRemoteDocument( + FSTFieldValue *previousValue, FSTFieldValue *transformResult) const = 0; +#endif + /** Returns whether the two are equal. */ - virtual bool operator==(const TransformOperation& other) const = 0; + virtual bool operator==(const TransformOperation &other) const = 0; /** Returns whether the two are not equal. */ - bool operator!=(const TransformOperation& other) const { + bool operator!=(const TransformOperation &other) const { return !operator==(other); } @@ -74,12 +93,28 @@ class ServerTimestampTransform : public TransformOperation { return Type::ServerTimestamp; } - bool operator==(const TransformOperation& other) const override { +// TODO(mikelehen): apply methods require FSTFieldValue which is Obj-C only. +#if defined(__OBJC__) + FSTFieldValue *applyToLocalView(FSTFieldValue *previousValue, + FIRTimestamp *localWriteTime) const override { + return [FSTServerTimestampValue + serverTimestampValueWithLocalWriteTime:localWriteTime + previousValue:previousValue]; + } + + FSTFieldValue *applyToRemoteDocument( + FSTFieldValue *previousValue, + FSTFieldValue *transformResult) const override { + return transformResult; + } +#endif + + bool operator==(const TransformOperation &other) const override { // All ServerTimestampTransform objects are equal. return other.type() == Type::ServerTimestamp; } - static const ServerTimestampTransform& Get() { + static const ServerTimestampTransform &Get() { static ServerTimestampTransform shared_instance; return shared_instance; } @@ -102,13 +137,14 @@ class ServerTimestampTransform : public TransformOperation { // TODO(mikelehen): ArrayTransform can only be used from Obj-C until we switch // to using FieldValue instead of FSTFieldValue. #if defined(__OBJC__) + /** * Transforms an array via a union or remove operation (for convenience, we use * this class for both Type::ArrayUnion and Type::ArrayRemove). */ class ArrayTransform : public TransformOperation { public: - ArrayTransform(Type type, std::vector elements) + ArrayTransform(Type type, std::vector elements) : type_(type), elements_(std::move(elements)) { } @@ -119,15 +155,29 @@ class ArrayTransform : public TransformOperation { return type_; } - const std::vector& elements() const { + FSTFieldValue *applyToLocalView(FSTFieldValue *previousValue, + FIRTimestamp *localWriteTime) const override { + return apply(previousValue); + } + + FSTFieldValue *applyToRemoteDocument( + FSTFieldValue *previousValue, + FSTFieldValue *transformResult) const override { + // The server just sends null as the transform result for array operations, + // so we have to calculate a result the same as we do for local + // applications. + return apply(previousValue); + } + + const std::vector &elements() const { return elements_; } - bool operator==(const TransformOperation& other) const override { + bool operator==(const TransformOperation &other) const override { if (other.type() != type()) { return false; } - auto array_transform = static_cast(other); + auto array_transform = static_cast(other); if (array_transform.elements_.size() != elements_.size()) { return false; } @@ -145,23 +195,56 @@ class ArrayTransform : public TransformOperation { NSUInteger Hash() const override { NSUInteger result = 37; result = 31 * result + (type() == Type::ArrayUnion ? 1231 : 1237); - for (FSTFieldValue* element : elements_) { + for (FSTFieldValue *element : elements_) { result = 31 * result + [element hash]; } return result; } #endif // defined(__OBJC__) - static const std::vector& Elements( - const TransformOperation& op) { + static const std::vector &Elements( + const TransformOperation &op) { FIREBASE_ASSERT(op.type() == Type::ArrayUnion || op.type() == Type::ArrayRemove); - return static_cast(op).elements(); + return static_cast(op).elements(); } private: Type type_; - std::vector elements_; + std::vector elements_; + + /** + * Inspects the provided value, returning a mutable copy of the internal array + * if it's an FSTArrayValue and an empty mutable array if it's nil or any + * other type of FSTFieldValue. + */ + static NSMutableArray *CoercedFieldValuesArray( + FSTFieldValue *value) { + if ([value isMemberOfClass:[FSTArrayValue class]]) { + return [NSMutableArray + arrayWithArray:reinterpret_cast(value) + .internalValue]; + } else { + // coerce to empty array. + return [NSMutableArray array]; + } + } + + FSTFieldValue *apply(FSTFieldValue *previousValue) const { + NSMutableArray *result = + ArrayTransform::CoercedFieldValuesArray(previousValue); + for (FSTFieldValue *element : elements_) { + if (type_ == Type::ArrayUnion) { + if (![result containsObject:element]) { + [result addObject:element]; + } + } else { + FIREBASE_ASSERT(type_ == Type::ArrayRemove); + [result removeObject:element]; + } + } + return [[FSTArrayValue alloc] initWithValueNoCopy:result]; + } }; #endif -- cgit v1.2.3