From 360e58901c359d7d21da4fff8043894c843427b7 Mon Sep 17 00:00:00 2001 From: Michael Lehenbauer Date: Mon, 16 Apr 2018 16:04:26 -0700 Subject: Implement local and server application of arrayUnion and arrayRemove transforms. (#1101) --- Firestore/Source/Model/FSTMutation.mm | 144 +++++++++++++++++++++++++++++----- 1 file changed, 123 insertions(+), 21 deletions(-) (limited to 'Firestore/Source/Model') diff --git a/Firestore/Source/Model/FSTMutation.mm b/Firestore/Source/Model/FSTMutation.mm index 99d2e51..47e34da 100644 --- a/Firestore/Source/Model/FSTMutation.mm +++ b/Firestore/Source/Model/FSTMutation.mm @@ -36,6 +36,7 @@ #include "Firestore/core/src/firebase/firestore/model/precondition.h" #include "Firestore/core/src/firebase/firestore/model/transform_operations.h" +using firebase::firestore::model::ArrayTransform; using firebase::firestore::model::DocumentKey; using firebase::firestore::model::FieldMask; using firebase::firestore::model::FieldPath; @@ -50,8 +51,8 @@ NS_ASSUME_NONNULL_BEGIN @implementation FSTMutationResult -- (instancetype)initWithVersion:(FSTSnapshotVersion *_Nullable)version - transformResults:(NSArray *_Nullable)transformResults { +- (instancetype)initWithVersion:(nullable FSTSnapshotVersion *)version + transformResults:(nullable NSArray *)transformResults { if (self = [super init]) { _version = version; _transformResults = transformResults; @@ -345,13 +346,18 @@ NS_ASSUME_NONNULL_BEGIN [maybeDoc class]); FSTDocument *doc = (FSTDocument *)maybeDoc; - FSTAssert([doc.key isEqual:self.key], @"Can only patch a document with the same key"); + FSTAssert([doc.key isEqual:self.key], @"Can only transform a document with the same key"); BOOL hasLocalMutations = (mutationResult == nil); - NSArray *transformResults = - mutationResult - ? mutationResult.transformResults - : [self localTransformResultsWithBaseDocument:baseDoc writeTime:localWriteTime]; + NSArray *transformResults; + if (mutationResult) { + transformResults = + [self serverTransformResultsWithBaseDocument:baseDoc + serverTransformResults:mutationResult.transformResults]; + } else { + transformResults = + [self localTransformResultsWithBaseDocument:baseDoc writeTime:localWriteTime]; + } FSTObjectValue *newData = [self transformObject:doc.data transformResults:transformResults]; return [FSTDocument documentWithData:newData key:doc.key @@ -359,6 +365,53 @@ NS_ASSUME_NONNULL_BEGIN hasLocalMutations:hasLocalMutations]; } +/** + * Creates an array of "transform results" (a transform result is a field value representing the + * result of applying a transform) for use after a FSTTransformMutation has been acknowledged by + * the server. + * + * @param baseDocument The document prior to applying this mutation batch. + * @param serverTransformResults The transform results received by the server. + * @return The transform results array. + */ +- (NSArray *) +serverTransformResultsWithBaseDocument:(nullable FSTMaybeDocument *)baseDocument + serverTransformResults:(NSArray *)serverTransformResults { + NSMutableArray *transformResults = [NSMutableArray array]; + FSTAssert(self.fieldTransforms.size() == serverTransformResults.count, + @"server transform result count (%ld) should match field transforms count (%ld)", + serverTransformResults.count, self.fieldTransforms.size()); + + for (NSUInteger i = 0; i < serverTransformResults.count; i++) { + const FieldTransform &fieldTransform = self.fieldTransforms[i]; + FSTFieldValue *previousValue = nil; + if ([baseDocument isMemberOfClass:[FSTDocument class]]) { + previousValue = [((FSTDocument *)baseDocument) fieldForPath:fieldTransform.path()]; + } + + FSTFieldValue *transformResult; + // The server just sends null as the transform result for array union / remove operations, so + // we have to calculate a result the same as we do for local applications. + if (fieldTransform.transformation().type() == TransformOperation::Type::ArrayUnion) { + transformResult = [self + arrayUnionResultWithElements:ArrayTransform::Elements(fieldTransform.transformation()) + previousValue:previousValue]; + + } else if (fieldTransform.transformation().type() == TransformOperation::Type::ArrayRemove) { + transformResult = [self + arrayRemoveResultWithElements:ArrayTransform::Elements(fieldTransform.transformation()) + previousValue:previousValue]; + + } else { + // Just use the server-supplied result. + transformResult = serverTransformResults[i]; + } + + [transformResults addObject:transformResult]; + } + return transformResults; +} + /** * Creates an array of "transform results" (a transform result is a field value representing the * result of applying a transform) for use when applying an FSTTransformMutation locally. @@ -369,27 +422,81 @@ NS_ASSUME_NONNULL_BEGIN * @return The transform results array. */ - (NSArray *)localTransformResultsWithBaseDocument: - (FSTMaybeDocument *_Nullable)baseDocument + (nullable FSTMaybeDocument *)baseDocument writeTime:(FIRTimestamp *)localWriteTime { NSMutableArray *transformResults = [NSMutableArray array]; for (const FieldTransform &fieldTransform : self.fieldTransforms) { + FSTFieldValue *previousValue = nil; + if ([baseDocument isMemberOfClass:[FSTDocument class]]) { + previousValue = [((FSTDocument *)baseDocument) fieldForPath:fieldTransform.path()]; + } + + FSTFieldValue *transformResult; if (fieldTransform.transformation().type() == TransformOperation::Type::ServerTimestamp) { - FSTFieldValue *previousValue = nil; + transformResult = + [FSTServerTimestampValue serverTimestampValueWithLocalWriteTime:localWriteTime + previousValue:previousValue]; + + } else if (fieldTransform.transformation().type() == TransformOperation::Type::ArrayUnion) { + transformResult = [self + arrayUnionResultWithElements:ArrayTransform::Elements(fieldTransform.transformation()) + previousValue:previousValue]; - if ([baseDocument isMemberOfClass:[FSTDocument class]]) { - previousValue = [((FSTDocument *)baseDocument) fieldForPath:fieldTransform.path()]; - } + } else if (fieldTransform.transformation().type() == TransformOperation::Type::ArrayRemove) { + transformResult = [self + arrayRemoveResultWithElements:ArrayTransform::Elements(fieldTransform.transformation()) + previousValue:previousValue]; - [transformResults - addObject:[FSTServerTimestampValue serverTimestampValueWithLocalWriteTime:localWriteTime - previousValue:previousValue]]; } else { FSTFail(@"Encountered unknown transform: %d type", fieldTransform.transformation().type()); } + + [transformResults addObject:transformResult]; } return transformResults; } +/** + * Transforms the provided `previousValue` via the provided `elements`. Used both for local + * application and after server acknowledgement. + */ +- (FSTFieldValue *)arrayUnionResultWithElements:(const std::vector &)elements + previousValue:(FSTFieldValue *)previousValue { + NSMutableArray *result = [self coercedFieldValuesArray:previousValue]; + for (FSTFieldValue *element : elements) { + if (![result containsObject:element]) { + [result addObject:element]; + } + } + return [[FSTArrayValue alloc] initWithValueNoCopy:result]; +} + +/** + * Transforms the provided `previousValue` via the provided `elements`. Used both for local + * application and after server acknowledgement. + */ +- (FSTFieldValue *)arrayRemoveResultWithElements:(const std::vector &)elements + previousValue:(FSTFieldValue *)previousValue { + NSMutableArray *result = [self coercedFieldValuesArray:previousValue]; + for (FSTFieldValue *element : elements) { + [result removeObject:element]; + } + return [[FSTArrayValue alloc] initWithValueNoCopy:result]; +} + +/** + * 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. + */ +- (NSMutableArray *)coercedFieldValuesArray:(nullable FSTFieldValue *)value { + if ([value isMemberOfClass:[FSTArrayValue class]]) { + return [NSMutableArray arrayWithArray:((FSTArrayValue *)value).internalValue]; + } else { + // coerce to empty array. + return [NSMutableArray array]; + } +} + - (FSTObjectValue *)transformObject:(FSTObjectValue *)objectValue transformResults:(NSArray *)transformResults { FSTAssert(transformResults.count == self.fieldTransforms.size(), @@ -397,13 +504,8 @@ NS_ASSUME_NONNULL_BEGIN for (size_t i = 0; i < self.fieldTransforms.size(); i++) { const FieldTransform &fieldTransform = self.fieldTransforms[i]; - const TransformOperation &transform = fieldTransform.transformation(); const FieldPath &fieldPath = fieldTransform.path(); - if (transform.type() == TransformOperation::Type::ServerTimestamp) { - objectValue = [objectValue objectBySettingValue:transformResults[i] forPath:fieldPath]; - } else { - FSTFail(@"Encountered unknown transform: %d type", transform.type()); - } + objectValue = [objectValue objectBySettingValue:transformResults[i] forPath:fieldPath]; } return objectValue; } -- cgit v1.2.3