FSTMutation.mm 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "Firestore/Source/Model/FSTMutation.h"
  17. #include <memory>
  18. #include <string>
  19. #include <utility>
  20. #include <vector>
  21. #import "FIRTimestamp.h"
  22. #import "Firestore/Source/Core/FSTSnapshotVersion.h"
  23. #import "Firestore/Source/Model/FSTDocument.h"
  24. #import "Firestore/Source/Model/FSTFieldValue.h"
  25. #import "Firestore/Source/Util/FSTAssert.h"
  26. #import "Firestore/Source/Util/FSTClasses.h"
  27. #include "Firestore/core/src/firebase/firestore/model/document_key.h"
  28. #include "Firestore/core/src/firebase/firestore/model/field_mask.h"
  29. #include "Firestore/core/src/firebase/firestore/model/field_path.h"
  30. #include "Firestore/core/src/firebase/firestore/model/field_transform.h"
  31. #include "Firestore/core/src/firebase/firestore/model/precondition.h"
  32. #include "Firestore/core/src/firebase/firestore/model/transform_operations.h"
  33. using firebase::firestore::model::ArrayTransform;
  34. using firebase::firestore::model::DocumentKey;
  35. using firebase::firestore::model::FieldMask;
  36. using firebase::firestore::model::FieldPath;
  37. using firebase::firestore::model::FieldTransform;
  38. using firebase::firestore::model::Precondition;
  39. using firebase::firestore::model::ServerTimestampTransform;
  40. using firebase::firestore::model::TransformOperation;
  41. NS_ASSUME_NONNULL_BEGIN
  42. #pragma mark - FSTMutationResult
  43. @implementation FSTMutationResult
  44. - (instancetype)initWithVersion:(nullable FSTSnapshotVersion *)version
  45. transformResults:(nullable NSArray<FSTFieldValue *> *)transformResults {
  46. if (self = [super init]) {
  47. _version = version;
  48. _transformResults = transformResults;
  49. }
  50. return self;
  51. }
  52. @end
  53. #pragma mark - FSTMutation
  54. @implementation FSTMutation {
  55. DocumentKey _key;
  56. Precondition _precondition;
  57. }
  58. - (instancetype)initWithKey:(DocumentKey)key precondition:(Precondition)precondition {
  59. if (self = [super init]) {
  60. _key = std::move(key);
  61. _precondition = std::move(precondition);
  62. }
  63. return self;
  64. }
  65. - (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
  66. baseDocument:(nullable FSTMaybeDocument *)baseDoc
  67. localWriteTime:(FIRTimestamp *)localWriteTime
  68. mutationResult:(nullable FSTMutationResult *)mutationResult {
  69. @throw FSTAbstractMethodException(); // NOLINT
  70. }
  71. - (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
  72. baseDocument:(nullable FSTMaybeDocument *)baseDoc
  73. localWriteTime:(nullable FIRTimestamp *)localWriteTime {
  74. return
  75. [self applyTo:maybeDoc baseDocument:baseDoc localWriteTime:localWriteTime mutationResult:nil];
  76. }
  77. - (const DocumentKey &)key {
  78. return _key;
  79. }
  80. - (const firebase::firestore::model::Precondition &)precondition {
  81. return _precondition;
  82. }
  83. @end
  84. #pragma mark - FSTSetMutation
  85. @implementation FSTSetMutation
  86. - (instancetype)initWithKey:(DocumentKey)key
  87. value:(FSTObjectValue *)value
  88. precondition:(Precondition)precondition {
  89. if (self = [super initWithKey:std::move(key) precondition:std::move(precondition)]) {
  90. _value = value;
  91. }
  92. return self;
  93. }
  94. - (NSString *)description {
  95. return [NSString stringWithFormat:@"<FSTSetMutation key=%s value=%@ precondition=%@>",
  96. self.key.ToString().c_str(), self.value,
  97. self.precondition.description()];
  98. }
  99. - (BOOL)isEqual:(id)other {
  100. if (other == self) {
  101. return YES;
  102. }
  103. if (![other isKindOfClass:[FSTSetMutation class]]) {
  104. return NO;
  105. }
  106. FSTSetMutation *otherMutation = (FSTSetMutation *)other;
  107. return [self.key isEqual:otherMutation.key] && [self.value isEqual:otherMutation.value] &&
  108. self.precondition == otherMutation.precondition;
  109. }
  110. - (NSUInteger)hash {
  111. NSUInteger result = [self.key hash];
  112. result = 31 * result + self.precondition.Hash();
  113. result = 31 * result + [self.value hash];
  114. return result;
  115. }
  116. - (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
  117. baseDocument:(nullable FSTMaybeDocument *)baseDoc
  118. localWriteTime:(FIRTimestamp *)localWriteTime
  119. mutationResult:(nullable FSTMutationResult *)mutationResult {
  120. if (mutationResult) {
  121. FSTAssert(!mutationResult.transformResults, @"Transform results received by FSTSetMutation.");
  122. }
  123. if (!self.precondition.IsValidFor(maybeDoc)) {
  124. return maybeDoc;
  125. }
  126. BOOL hasLocalMutations = (mutationResult == nil);
  127. if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) {
  128. // If the document didn't exist before, create it.
  129. return [FSTDocument documentWithData:self.value
  130. key:self.key
  131. version:[FSTSnapshotVersion noVersion]
  132. hasLocalMutations:hasLocalMutations];
  133. }
  134. FSTAssert([maybeDoc isMemberOfClass:[FSTDocument class]], @"Unknown MaybeDocument type %@",
  135. [maybeDoc class]);
  136. FSTDocument *doc = (FSTDocument *)maybeDoc;
  137. FSTAssert([doc.key isEqual:self.key], @"Can only set a document with the same key");
  138. return [FSTDocument documentWithData:self.value
  139. key:doc.key
  140. version:doc.version
  141. hasLocalMutations:hasLocalMutations];
  142. }
  143. @end
  144. #pragma mark - FSTPatchMutation
  145. @implementation FSTPatchMutation {
  146. FieldMask _fieldMask;
  147. }
  148. - (instancetype)initWithKey:(DocumentKey)key
  149. fieldMask:(FieldMask)fieldMask
  150. value:(FSTObjectValue *)value
  151. precondition:(Precondition)precondition {
  152. self = [super initWithKey:std::move(key) precondition:std::move(precondition)];
  153. if (self) {
  154. _fieldMask = std::move(fieldMask);
  155. _value = value;
  156. }
  157. return self;
  158. }
  159. - (const firebase::firestore::model::FieldMask &)fieldMask {
  160. return _fieldMask;
  161. }
  162. - (BOOL)isEqual:(id)other {
  163. if (other == self) {
  164. return YES;
  165. }
  166. if (![other isKindOfClass:[FSTPatchMutation class]]) {
  167. return NO;
  168. }
  169. FSTPatchMutation *otherMutation = (FSTPatchMutation *)other;
  170. return [self.key isEqual:otherMutation.key] && self.fieldMask == otherMutation.fieldMask &&
  171. [self.value isEqual:otherMutation.value] &&
  172. self.precondition == otherMutation.precondition;
  173. }
  174. - (NSUInteger)hash {
  175. NSUInteger result = [self.key hash];
  176. result = 31 * result + self.precondition.Hash();
  177. result = 31 * result + self.fieldMask.Hash();
  178. result = 31 * result + [self.value hash];
  179. return result;
  180. }
  181. - (NSString *)description {
  182. return [NSString stringWithFormat:@"<FSTPatchMutation key=%s mask=%s value=%@ precondition=%@>",
  183. self.key.ToString().c_str(), self.fieldMask.ToString().c_str(),
  184. self.value, self.precondition.description()];
  185. }
  186. - (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
  187. baseDocument:(nullable FSTMaybeDocument *)baseDoc
  188. localWriteTime:(FIRTimestamp *)localWriteTime
  189. mutationResult:(nullable FSTMutationResult *)mutationResult {
  190. if (mutationResult) {
  191. FSTAssert(!mutationResult.transformResults, @"Transform results received by FSTPatchMutation.");
  192. }
  193. if (!self.precondition.IsValidFor(maybeDoc)) {
  194. return maybeDoc;
  195. }
  196. BOOL hasLocalMutations = (mutationResult == nil);
  197. if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) {
  198. // Precondition applied, so create the document if necessary
  199. const DocumentKey &key = maybeDoc ? maybeDoc.key : self.key;
  200. FSTSnapshotVersion *version = maybeDoc ? maybeDoc.version : [FSTSnapshotVersion noVersion];
  201. maybeDoc = [FSTDocument documentWithData:[FSTObjectValue objectValue]
  202. key:key
  203. version:version
  204. hasLocalMutations:hasLocalMutations];
  205. }
  206. FSTAssert([maybeDoc isMemberOfClass:[FSTDocument class]], @"Unknown MaybeDocument type %@",
  207. [maybeDoc class]);
  208. FSTDocument *doc = (FSTDocument *)maybeDoc;
  209. FSTAssert([doc.key isEqual:self.key], @"Can only patch a document with the same key");
  210. FSTObjectValue *newData = [self patchObjectValue:doc.data];
  211. return [FSTDocument documentWithData:newData
  212. key:doc.key
  213. version:doc.version
  214. hasLocalMutations:hasLocalMutations];
  215. }
  216. - (FSTObjectValue *)patchObjectValue:(FSTObjectValue *)objectValue {
  217. FSTObjectValue *result = objectValue;
  218. for (const FieldPath &fieldPath : self.fieldMask) {
  219. FSTFieldValue *newValue = [self.value valueForPath:fieldPath];
  220. if (newValue) {
  221. result = [result objectBySettingValue:newValue forPath:fieldPath];
  222. } else {
  223. result = [result objectByDeletingPath:fieldPath];
  224. }
  225. }
  226. return result;
  227. }
  228. @end
  229. @implementation FSTTransformMutation {
  230. /** The field transforms to use when transforming the document. */
  231. std::vector<FieldTransform> _fieldTransforms;
  232. }
  233. - (instancetype)initWithKey:(DocumentKey)key
  234. fieldTransforms:(std::vector<FieldTransform>)fieldTransforms {
  235. // NOTE: We set a precondition of exists: true as a safety-check, since we always combine
  236. // FSTTransformMutations with a FSTSetMutation or FSTPatchMutation which (if successful) should
  237. // end up with an existing document.
  238. if (self = [super initWithKey:std::move(key) precondition:Precondition::Exists(true)]) {
  239. _fieldTransforms = std::move(fieldTransforms);
  240. }
  241. return self;
  242. }
  243. - (const std::vector<FieldTransform> &)fieldTransforms {
  244. return _fieldTransforms;
  245. }
  246. - (BOOL)isEqual:(id)other {
  247. if (other == self) {
  248. return YES;
  249. }
  250. if (![other isKindOfClass:[FSTTransformMutation class]]) {
  251. return NO;
  252. }
  253. FSTTransformMutation *otherMutation = (FSTTransformMutation *)other;
  254. return [self.key isEqual:otherMutation.key] &&
  255. self.fieldTransforms == otherMutation.fieldTransforms &&
  256. self.precondition == otherMutation.precondition;
  257. }
  258. - (NSUInteger)hash {
  259. NSUInteger result = [self.key hash];
  260. result = 31 * result + self.precondition.Hash();
  261. for (const auto &transform : self.fieldTransforms) {
  262. result = 31 * result + transform.Hash();
  263. }
  264. return result;
  265. }
  266. - (NSString *)description {
  267. std::string fieldTransforms;
  268. for (const auto &transform : self.fieldTransforms) {
  269. fieldTransforms += " " + transform.path().CanonicalString();
  270. }
  271. return [NSString stringWithFormat:@"<FSTTransformMutation key=%s transforms=%s precondition=%@>",
  272. self.key.ToString().c_str(), fieldTransforms.c_str(),
  273. self.precondition.description()];
  274. }
  275. - (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
  276. baseDocument:(nullable FSTMaybeDocument *)baseDoc
  277. localWriteTime:(FIRTimestamp *)localWriteTime
  278. mutationResult:(nullable FSTMutationResult *)mutationResult {
  279. if (mutationResult) {
  280. FSTAssert(mutationResult.transformResults,
  281. @"Transform results missing for FSTTransformMutation.");
  282. }
  283. if (!self.precondition.IsValidFor(maybeDoc)) {
  284. return maybeDoc;
  285. }
  286. // We only support transforms with precondition exists, so we can only apply it to an existing
  287. // document
  288. FSTAssert([maybeDoc isMemberOfClass:[FSTDocument class]], @"Unknown MaybeDocument type %@",
  289. [maybeDoc class]);
  290. FSTDocument *doc = (FSTDocument *)maybeDoc;
  291. FSTAssert([doc.key isEqual:self.key], @"Can only transform a document with the same key");
  292. BOOL hasLocalMutations = (mutationResult == nil);
  293. NSArray<FSTFieldValue *> *transformResults;
  294. if (mutationResult) {
  295. transformResults =
  296. [self serverTransformResultsWithBaseDocument:baseDoc
  297. serverTransformResults:mutationResult.transformResults];
  298. } else {
  299. transformResults =
  300. [self localTransformResultsWithBaseDocument:baseDoc writeTime:localWriteTime];
  301. }
  302. FSTObjectValue *newData = [self transformObject:doc.data transformResults:transformResults];
  303. return [FSTDocument documentWithData:newData
  304. key:doc.key
  305. version:doc.version
  306. hasLocalMutations:hasLocalMutations];
  307. }
  308. /**
  309. * Creates an array of "transform results" (a transform result is a field value representing the
  310. * result of applying a transform) for use after a FSTTransformMutation has been acknowledged by
  311. * the server.
  312. *
  313. * @param baseDocument The document prior to applying this mutation batch.
  314. * @param serverTransformResults The transform results received by the server.
  315. * @return The transform results array.
  316. */
  317. - (NSArray<FSTFieldValue *> *)
  318. serverTransformResultsWithBaseDocument:(nullable FSTMaybeDocument *)baseDocument
  319. serverTransformResults:(NSArray<FSTFieldValue *> *)serverTransformResults {
  320. NSMutableArray<FSTFieldValue *> *transformResults = [NSMutableArray array];
  321. FSTAssert(self.fieldTransforms.size() == serverTransformResults.count,
  322. @"server transform result count (%lu) should match field transforms count (%zu)",
  323. (unsigned long)serverTransformResults.count, self.fieldTransforms.size());
  324. for (NSUInteger i = 0; i < serverTransformResults.count; i++) {
  325. const FieldTransform &fieldTransform = self.fieldTransforms[i];
  326. FSTFieldValue *previousValue = nil;
  327. if ([baseDocument isMemberOfClass:[FSTDocument class]]) {
  328. previousValue = [((FSTDocument *)baseDocument) fieldForPath:fieldTransform.path()];
  329. }
  330. FSTFieldValue *transformResult;
  331. // The server just sends null as the transform result for array union / remove operations, so
  332. // we have to calculate a result the same as we do for local applications.
  333. if (fieldTransform.transformation().type() == TransformOperation::Type::ArrayUnion) {
  334. transformResult = [self
  335. arrayUnionResultWithElements:ArrayTransform::Elements(fieldTransform.transformation())
  336. previousValue:previousValue];
  337. } else if (fieldTransform.transformation().type() == TransformOperation::Type::ArrayRemove) {
  338. transformResult = [self
  339. arrayRemoveResultWithElements:ArrayTransform::Elements(fieldTransform.transformation())
  340. previousValue:previousValue];
  341. } else {
  342. // Just use the server-supplied result.
  343. transformResult = serverTransformResults[i];
  344. }
  345. [transformResults addObject:transformResult];
  346. }
  347. return transformResults;
  348. }
  349. /**
  350. * Creates an array of "transform results" (a transform result is a field value representing the
  351. * result of applying a transform) for use when applying an FSTTransformMutation locally.
  352. *
  353. * @param baseDocument The document prior to applying this mutation batch.
  354. * @param localWriteTime The local time of the transform mutation (used to generate
  355. * FSTServerTimestampValues).
  356. * @return The transform results array.
  357. */
  358. - (NSArray<FSTFieldValue *> *)localTransformResultsWithBaseDocument:
  359. (nullable FSTMaybeDocument *)baseDocument
  360. writeTime:(FIRTimestamp *)localWriteTime {
  361. NSMutableArray<FSTFieldValue *> *transformResults = [NSMutableArray array];
  362. for (const FieldTransform &fieldTransform : self.fieldTransforms) {
  363. FSTFieldValue *previousValue = nil;
  364. if ([baseDocument isMemberOfClass:[FSTDocument class]]) {
  365. previousValue = [((FSTDocument *)baseDocument) fieldForPath:fieldTransform.path()];
  366. }
  367. FSTFieldValue *transformResult;
  368. if (fieldTransform.transformation().type() == TransformOperation::Type::ServerTimestamp) {
  369. transformResult =
  370. [FSTServerTimestampValue serverTimestampValueWithLocalWriteTime:localWriteTime
  371. previousValue:previousValue];
  372. } else if (fieldTransform.transformation().type() == TransformOperation::Type::ArrayUnion) {
  373. transformResult = [self
  374. arrayUnionResultWithElements:ArrayTransform::Elements(fieldTransform.transformation())
  375. previousValue:previousValue];
  376. } else if (fieldTransform.transformation().type() == TransformOperation::Type::ArrayRemove) {
  377. transformResult = [self
  378. arrayRemoveResultWithElements:ArrayTransform::Elements(fieldTransform.transformation())
  379. previousValue:previousValue];
  380. } else {
  381. FSTFail(@"Encountered unknown transform: %d type", fieldTransform.transformation().type());
  382. }
  383. [transformResults addObject:transformResult];
  384. }
  385. return transformResults;
  386. }
  387. /**
  388. * Transforms the provided `previousValue` via the provided `elements`. Used both for local
  389. * application and after server acknowledgement.
  390. */
  391. - (FSTFieldValue *)arrayUnionResultWithElements:(const std::vector<FSTFieldValue *> &)elements
  392. previousValue:(FSTFieldValue *)previousValue {
  393. NSMutableArray<FSTFieldValue *> *result = [self coercedFieldValuesArray:previousValue];
  394. for (FSTFieldValue *element : elements) {
  395. if (![result containsObject:element]) {
  396. [result addObject:element];
  397. }
  398. }
  399. return [[FSTArrayValue alloc] initWithValueNoCopy:result];
  400. }
  401. /**
  402. * Transforms the provided `previousValue` via the provided `elements`. Used both for local
  403. * application and after server acknowledgement.
  404. */
  405. - (FSTFieldValue *)arrayRemoveResultWithElements:(const std::vector<FSTFieldValue *> &)elements
  406. previousValue:(FSTFieldValue *)previousValue {
  407. NSMutableArray<FSTFieldValue *> *result = [self coercedFieldValuesArray:previousValue];
  408. for (FSTFieldValue *element : elements) {
  409. [result removeObject:element];
  410. }
  411. return [[FSTArrayValue alloc] initWithValueNoCopy:result];
  412. }
  413. /**
  414. * Inspects the provided value, returning a mutable copy of the internal array if it's an
  415. * FSTArrayValue and an empty mutable array if it's nil or any other type of FSTFieldValue.
  416. */
  417. - (NSMutableArray<FSTFieldValue *> *)coercedFieldValuesArray:(nullable FSTFieldValue *)value {
  418. if ([value isMemberOfClass:[FSTArrayValue class]]) {
  419. return [NSMutableArray arrayWithArray:((FSTArrayValue *)value).internalValue];
  420. } else {
  421. // coerce to empty array.
  422. return [NSMutableArray array];
  423. }
  424. }
  425. - (FSTObjectValue *)transformObject:(FSTObjectValue *)objectValue
  426. transformResults:(NSArray<FSTFieldValue *> *)transformResults {
  427. FSTAssert(transformResults.count == self.fieldTransforms.size(),
  428. @"Transform results length mismatch.");
  429. for (size_t i = 0; i < self.fieldTransforms.size(); i++) {
  430. const FieldTransform &fieldTransform = self.fieldTransforms[i];
  431. const FieldPath &fieldPath = fieldTransform.path();
  432. objectValue = [objectValue objectBySettingValue:transformResults[i] forPath:fieldPath];
  433. }
  434. return objectValue;
  435. }
  436. @end
  437. #pragma mark - FSTDeleteMutation
  438. @implementation FSTDeleteMutation
  439. - (BOOL)isEqual:(id)other {
  440. if (other == self) {
  441. return YES;
  442. }
  443. if (![other isKindOfClass:[FSTDeleteMutation class]]) {
  444. return NO;
  445. }
  446. FSTDeleteMutation *otherMutation = (FSTDeleteMutation *)other;
  447. return [self.key isEqual:otherMutation.key] && self.precondition == otherMutation.precondition;
  448. }
  449. - (NSUInteger)hash {
  450. NSUInteger result = [self.key hash];
  451. result = 31 * result + self.precondition.Hash();
  452. return result;
  453. }
  454. - (NSString *)description {
  455. return [NSString stringWithFormat:@"<FSTDeleteMutation key=%s precondition=%@>",
  456. self.key.ToString().c_str(), self.precondition.description()];
  457. }
  458. - (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
  459. baseDocument:(nullable FSTMaybeDocument *)baseDoc
  460. localWriteTime:(FIRTimestamp *)localWriteTime
  461. mutationResult:(nullable FSTMutationResult *)mutationResult {
  462. if (mutationResult) {
  463. FSTAssert(!mutationResult.transformResults,
  464. @"Transform results received by FSTDeleteMutation.");
  465. }
  466. if (!self.precondition.IsValidFor(maybeDoc)) {
  467. return maybeDoc;
  468. }
  469. if (maybeDoc) {
  470. FSTAssert([maybeDoc.key isEqual:self.key], @"Can only delete a document with the same key");
  471. }
  472. return [FSTDeletedDocument documentWithKey:self.key version:[FSTSnapshotVersion noVersion]];
  473. }
  474. @end
  475. NS_ASSUME_NONNULL_END