| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499 |
- /*
- * Copyright 2017 Google
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- #import "Firestore/Source/Model/FSTMutation.h"
- #include <memory>
- #include <string>
- #include <utility>
- #include <vector>
- #import "FIRTimestamp.h"
- #import "Firestore/Source/Model/FSTDocument.h"
- #import "Firestore/Source/Model/FSTFieldValue.h"
- #import "Firestore/Source/Util/FSTClasses.h"
- #include "Firestore/core/src/firebase/firestore/model/document_key.h"
- #include "Firestore/core/src/firebase/firestore/model/field_mask.h"
- #include "Firestore/core/src/firebase/firestore/model/field_path.h"
- #include "Firestore/core/src/firebase/firestore/model/field_transform.h"
- #include "Firestore/core/src/firebase/firestore/model/precondition.h"
- #include "Firestore/core/src/firebase/firestore/model/transform_operations.h"
- #include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
- #include "absl/types/optional.h"
- using firebase::firestore::model::ArrayTransform;
- using firebase::firestore::model::DocumentKey;
- using firebase::firestore::model::FieldMask;
- using firebase::firestore::model::FieldPath;
- using firebase::firestore::model::FieldTransform;
- using firebase::firestore::model::Precondition;
- using firebase::firestore::model::ServerTimestampTransform;
- using firebase::firestore::model::SnapshotVersion;
- using firebase::firestore::model::TransformOperation;
- NS_ASSUME_NONNULL_BEGIN
- #pragma mark - FSTMutationResult
- @implementation FSTMutationResult {
- absl::optional<SnapshotVersion> _version;
- }
- - (instancetype)initWithVersion:(absl::optional<SnapshotVersion>)version
- transformResults:(nullable NSArray<FSTFieldValue *> *)transformResults {
- if (self = [super init]) {
- _version = std::move(version);
- _transformResults = transformResults;
- }
- return self;
- }
- - (const absl::optional<SnapshotVersion> &)version {
- return _version;
- }
- @end
- #pragma mark - FSTMutation
- @implementation FSTMutation {
- DocumentKey _key;
- Precondition _precondition;
- }
- - (instancetype)initWithKey:(DocumentKey)key precondition:(Precondition)precondition {
- if (self = [super init]) {
- _key = std::move(key);
- _precondition = std::move(precondition);
- }
- return self;
- }
- - (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
- baseDocument:(nullable FSTMaybeDocument *)baseDoc
- localWriteTime:(FIRTimestamp *)localWriteTime
- mutationResult:(nullable FSTMutationResult *)mutationResult {
- @throw FSTAbstractMethodException(); // NOLINT
- }
- - (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
- baseDocument:(nullable FSTMaybeDocument *)baseDoc
- localWriteTime:(nullable FIRTimestamp *)localWriteTime {
- return
- [self applyTo:maybeDoc baseDocument:baseDoc localWriteTime:localWriteTime mutationResult:nil];
- }
- - (const DocumentKey &)key {
- return _key;
- }
- - (const firebase::firestore::model::Precondition &)precondition {
- return _precondition;
- }
- @end
- #pragma mark - FSTSetMutation
- @implementation FSTSetMutation
- - (instancetype)initWithKey:(DocumentKey)key
- value:(FSTObjectValue *)value
- precondition:(Precondition)precondition {
- if (self = [super initWithKey:std::move(key) precondition:std::move(precondition)]) {
- _value = value;
- }
- return self;
- }
- - (NSString *)description {
- return [NSString stringWithFormat:@"<FSTSetMutation key=%s value=%@ precondition=%@>",
- self.key.ToString().c_str(), self.value,
- self.precondition.description()];
- }
- - (BOOL)isEqual:(id)other {
- if (other == self) {
- return YES;
- }
- if (![other isKindOfClass:[FSTSetMutation class]]) {
- return NO;
- }
- FSTSetMutation *otherMutation = (FSTSetMutation *)other;
- return [self.key isEqual:otherMutation.key] && [self.value isEqual:otherMutation.value] &&
- self.precondition == otherMutation.precondition;
- }
- - (NSUInteger)hash {
- NSUInteger result = [self.key hash];
- result = 31 * result + self.precondition.Hash();
- result = 31 * result + [self.value hash];
- return result;
- }
- - (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
- baseDocument:(nullable FSTMaybeDocument *)baseDoc
- localWriteTime:(FIRTimestamp *)localWriteTime
- mutationResult:(nullable FSTMutationResult *)mutationResult {
- if (mutationResult) {
- HARD_ASSERT(!mutationResult.transformResults, "Transform results received by FSTSetMutation.");
- }
- if (!self.precondition.IsValidFor(maybeDoc)) {
- return maybeDoc;
- }
- BOOL hasLocalMutations = (mutationResult == nil);
- if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) {
- // If the document didn't exist before, create it.
- return [FSTDocument documentWithData:self.value
- key:self.key
- version:SnapshotVersion::None()
- hasLocalMutations:hasLocalMutations];
- }
- HARD_ASSERT([maybeDoc isMemberOfClass:[FSTDocument class]], "Unknown MaybeDocument type %s",
- [maybeDoc class]);
- FSTDocument *doc = (FSTDocument *)maybeDoc;
- HARD_ASSERT([doc.key isEqual:self.key], "Can only set a document with the same key");
- return [FSTDocument documentWithData:self.value
- key:doc.key
- version:doc.version
- hasLocalMutations:hasLocalMutations];
- }
- @end
- #pragma mark - FSTPatchMutation
- @implementation FSTPatchMutation {
- FieldMask _fieldMask;
- }
- - (instancetype)initWithKey:(DocumentKey)key
- fieldMask:(FieldMask)fieldMask
- value:(FSTObjectValue *)value
- precondition:(Precondition)precondition {
- self = [super initWithKey:std::move(key) precondition:std::move(precondition)];
- if (self) {
- _fieldMask = std::move(fieldMask);
- _value = value;
- }
- return self;
- }
- - (const firebase::firestore::model::FieldMask &)fieldMask {
- return _fieldMask;
- }
- - (BOOL)isEqual:(id)other {
- if (other == self) {
- return YES;
- }
- if (![other isKindOfClass:[FSTPatchMutation class]]) {
- return NO;
- }
- FSTPatchMutation *otherMutation = (FSTPatchMutation *)other;
- return [self.key isEqual:otherMutation.key] && self.fieldMask == otherMutation.fieldMask &&
- [self.value isEqual:otherMutation.value] &&
- self.precondition == otherMutation.precondition;
- }
- - (NSUInteger)hash {
- NSUInteger result = [self.key hash];
- result = 31 * result + self.precondition.Hash();
- result = 31 * result + self.fieldMask.Hash();
- result = 31 * result + [self.value hash];
- return result;
- }
- - (NSString *)description {
- return [NSString stringWithFormat:@"<FSTPatchMutation key=%s mask=%s value=%@ precondition=%@>",
- self.key.ToString().c_str(), self.fieldMask.ToString().c_str(),
- self.value, self.precondition.description()];
- }
- - (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
- baseDocument:(nullable FSTMaybeDocument *)baseDoc
- localWriteTime:(FIRTimestamp *)localWriteTime
- mutationResult:(nullable FSTMutationResult *)mutationResult {
- if (mutationResult) {
- HARD_ASSERT(!mutationResult.transformResults,
- "Transform results received by FSTPatchMutation.");
- }
- if (!self.precondition.IsValidFor(maybeDoc)) {
- return maybeDoc;
- }
- BOOL hasLocalMutations = (mutationResult == nil);
- if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) {
- // Precondition applied, so create the document if necessary
- const DocumentKey &key = maybeDoc ? maybeDoc.key : self.key;
- SnapshotVersion version = maybeDoc ? maybeDoc.version : SnapshotVersion::None();
- maybeDoc = [FSTDocument documentWithData:[FSTObjectValue objectValue]
- key:key
- version:std::move(version)
- hasLocalMutations:hasLocalMutations];
- }
- HARD_ASSERT([maybeDoc isMemberOfClass:[FSTDocument class]], "Unknown MaybeDocument type %s",
- [maybeDoc class]);
- FSTDocument *doc = (FSTDocument *)maybeDoc;
- HARD_ASSERT([doc.key isEqual:self.key], "Can only patch a document with the same key");
- FSTObjectValue *newData = [self patchObjectValue:doc.data];
- return [FSTDocument documentWithData:newData
- key:doc.key
- version:doc.version
- hasLocalMutations:hasLocalMutations];
- }
- - (FSTObjectValue *)patchObjectValue:(FSTObjectValue *)objectValue {
- FSTObjectValue *result = objectValue;
- for (const FieldPath &fieldPath : self.fieldMask) {
- FSTFieldValue *newValue = [self.value valueForPath:fieldPath];
- if (newValue) {
- result = [result objectBySettingValue:newValue forPath:fieldPath];
- } else {
- result = [result objectByDeletingPath:fieldPath];
- }
- }
- return result;
- }
- @end
- @implementation FSTTransformMutation {
- /** The field transforms to use when transforming the document. */
- std::vector<FieldTransform> _fieldTransforms;
- }
- - (instancetype)initWithKey:(DocumentKey)key
- fieldTransforms:(std::vector<FieldTransform>)fieldTransforms {
- // NOTE: We set a precondition of exists: true as a safety-check, since we always combine
- // FSTTransformMutations with a FSTSetMutation or FSTPatchMutation which (if successful) should
- // end up with an existing document.
- if (self = [super initWithKey:std::move(key) precondition:Precondition::Exists(true)]) {
- _fieldTransforms = std::move(fieldTransforms);
- }
- return self;
- }
- - (const std::vector<FieldTransform> &)fieldTransforms {
- return _fieldTransforms;
- }
- - (BOOL)isEqual:(id)other {
- if (other == self) {
- return YES;
- }
- if (![other isKindOfClass:[FSTTransformMutation class]]) {
- return NO;
- }
- FSTTransformMutation *otherMutation = (FSTTransformMutation *)other;
- return [self.key isEqual:otherMutation.key] &&
- self.fieldTransforms == otherMutation.fieldTransforms &&
- self.precondition == otherMutation.precondition;
- }
- - (NSUInteger)hash {
- NSUInteger result = [self.key hash];
- result = 31 * result + self.precondition.Hash();
- for (const auto &transform : self.fieldTransforms) {
- result = 31 * result + transform.Hash();
- }
- return result;
- }
- - (NSString *)description {
- std::string fieldTransforms;
- for (const auto &transform : self.fieldTransforms) {
- fieldTransforms += " " + transform.path().CanonicalString();
- }
- return [NSString stringWithFormat:@"<FSTTransformMutation key=%s transforms=%s precondition=%@>",
- self.key.ToString().c_str(), fieldTransforms.c_str(),
- self.precondition.description()];
- }
- - (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
- baseDocument:(nullable FSTMaybeDocument *)baseDoc
- localWriteTime:(FIRTimestamp *)localWriteTime
- mutationResult:(nullable FSTMutationResult *)mutationResult {
- if (mutationResult) {
- HARD_ASSERT(mutationResult.transformResults,
- "Transform results missing for FSTTransformMutation.");
- }
- if (!self.precondition.IsValidFor(maybeDoc)) {
- return maybeDoc;
- }
- // We only support transforms with precondition exists, so we can only apply it to an existing
- // document
- HARD_ASSERT([maybeDoc isMemberOfClass:[FSTDocument class]], "Unknown MaybeDocument type %s",
- [maybeDoc class]);
- FSTDocument *doc = (FSTDocument *)maybeDoc;
- HARD_ASSERT([doc.key isEqual:self.key], "Can only transform a document with the same key");
- BOOL hasLocalMutations = (mutationResult == nil);
- NSArray<FSTFieldValue *> *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
- version:doc.version
- 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<FSTFieldValue *> *)
- serverTransformResultsWithBaseDocument:(nullable FSTMaybeDocument *)baseDocument
- serverTransformResults:(NSArray<FSTFieldValue *> *)serverTransformResults {
- NSMutableArray<FSTFieldValue *> *transformResults = [NSMutableArray array];
- HARD_ASSERT(self.fieldTransforms.size() == serverTransformResults.count,
- "server transform result count (%s) should match field transforms count (%s)",
- (unsigned long)serverTransformResults.count, self.fieldTransforms.size());
- for (NSUInteger i = 0; i < serverTransformResults.count; i++) {
- const FieldTransform &fieldTransform = self.fieldTransforms[i];
- const TransformOperation &transform = fieldTransform.transformation();
- FSTFieldValue *previousValue = nil;
- if ([baseDocument isMemberOfClass:[FSTDocument class]]) {
- previousValue = [((FSTDocument *)baseDocument) fieldForPath:fieldTransform.path()];
- }
- [transformResults
- addObject:transform.ApplyToRemoteDocument(previousValue, serverTransformResults[i])];
- }
- 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.
- *
- * @param baseDocument The document prior to applying this mutation batch.
- * @param localWriteTime The local time of the transform mutation (used to generate
- * FSTServerTimestampValues).
- * @return The transform results array.
- */
- - (NSArray<FSTFieldValue *> *)localTransformResultsWithBaseDocument:
- (nullable FSTMaybeDocument *)baseDocument
- writeTime:(FIRTimestamp *)localWriteTime {
- NSMutableArray<FSTFieldValue *> *transformResults = [NSMutableArray array];
- for (const FieldTransform &fieldTransform : self.fieldTransforms) {
- const TransformOperation &transform = fieldTransform.transformation();
- FSTFieldValue *previousValue = nil;
- if ([baseDocument isMemberOfClass:[FSTDocument class]]) {
- previousValue = [((FSTDocument *)baseDocument) fieldForPath:fieldTransform.path()];
- }
- [transformResults addObject:transform.ApplyToLocalView(previousValue, localWriteTime)];
- }
- return transformResults;
- }
- - (FSTObjectValue *)transformObject:(FSTObjectValue *)objectValue
- transformResults:(NSArray<FSTFieldValue *> *)transformResults {
- HARD_ASSERT(transformResults.count == self.fieldTransforms.size(),
- "Transform results length mismatch.");
- for (size_t i = 0; i < self.fieldTransforms.size(); i++) {
- const FieldTransform &fieldTransform = self.fieldTransforms[i];
- const FieldPath &fieldPath = fieldTransform.path();
- objectValue = [objectValue objectBySettingValue:transformResults[i] forPath:fieldPath];
- }
- return objectValue;
- }
- @end
- #pragma mark - FSTDeleteMutation
- @implementation FSTDeleteMutation
- - (BOOL)isEqual:(id)other {
- if (other == self) {
- return YES;
- }
- if (![other isKindOfClass:[FSTDeleteMutation class]]) {
- return NO;
- }
- FSTDeleteMutation *otherMutation = (FSTDeleteMutation *)other;
- return [self.key isEqual:otherMutation.key] && self.precondition == otherMutation.precondition;
- }
- - (NSUInteger)hash {
- NSUInteger result = [self.key hash];
- result = 31 * result + self.precondition.Hash();
- return result;
- }
- - (NSString *)description {
- return [NSString stringWithFormat:@"<FSTDeleteMutation key=%s precondition=%@>",
- self.key.ToString().c_str(), self.precondition.description()];
- }
- - (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
- baseDocument:(nullable FSTMaybeDocument *)baseDoc
- localWriteTime:(FIRTimestamp *)localWriteTime
- mutationResult:(nullable FSTMutationResult *)mutationResult {
- if (mutationResult) {
- HARD_ASSERT(!mutationResult.transformResults,
- "Transform results received by FSTDeleteMutation.");
- }
- if (!self.precondition.IsValidFor(maybeDoc)) {
- return maybeDoc;
- }
- if (maybeDoc) {
- HARD_ASSERT([maybeDoc.key isEqual:self.key], "Can only delete a document with the same key");
- }
- return [FSTDeletedDocument documentWithKey:self.key version:SnapshotVersion::None()];
- }
- @end
- NS_ASSUME_NONNULL_END
|