FSTMutation.mm 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  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 <set>
  19. #include <string>
  20. #include <utility>
  21. #include <vector>
  22. #import "FIRTimestamp.h"
  23. #import "Firestore/Source/Model/FSTDocument.h"
  24. #import "Firestore/Source/Model/FSTFieldValue.h"
  25. #import "Firestore/Source/Util/FSTClasses.h"
  26. #include "Firestore/core/src/firebase/firestore/model/document_key.h"
  27. #include "Firestore/core/src/firebase/firestore/model/field_mask.h"
  28. #include "Firestore/core/src/firebase/firestore/model/field_path.h"
  29. #include "Firestore/core/src/firebase/firestore/model/field_transform.h"
  30. #include "Firestore/core/src/firebase/firestore/model/precondition.h"
  31. #include "Firestore/core/src/firebase/firestore/model/transform_operations.h"
  32. #include "Firestore/core/src/firebase/firestore/util/hard_assert.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::SnapshotVersion;
  41. using firebase::firestore::model::TransformOperation;
  42. using firebase::firestore::util::Hash;
  43. NS_ASSUME_NONNULL_BEGIN
  44. #pragma mark - FSTMutationResult
  45. @implementation FSTMutationResult {
  46. SnapshotVersion _version;
  47. }
  48. - (instancetype)initWithVersion:(SnapshotVersion)version
  49. transformResults:(nullable NSArray<FSTFieldValue *> *)transformResults {
  50. if (self = [super init]) {
  51. _version = std::move(version);
  52. _transformResults = transformResults;
  53. }
  54. return self;
  55. }
  56. - (const SnapshotVersion &)version {
  57. return _version;
  58. }
  59. @end
  60. #pragma mark - FSTMutation
  61. @implementation FSTMutation {
  62. DocumentKey _key;
  63. Precondition _precondition;
  64. }
  65. - (instancetype)initWithKey:(DocumentKey)key precondition:(Precondition)precondition {
  66. if (self = [super init]) {
  67. _key = std::move(key);
  68. _precondition = std::move(precondition);
  69. }
  70. return self;
  71. }
  72. - (FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument *)maybeDoc
  73. mutationResult:(FSTMutationResult *)mutationResult {
  74. @throw FSTAbstractMethodException(); // NOLINT
  75. }
  76. - (nullable FSTMaybeDocument *)applyToLocalDocument:(nullable FSTMaybeDocument *)maybeDoc
  77. baseDocument:(nullable FSTMaybeDocument *)baseDoc
  78. localWriteTime:(FIRTimestamp *)localWriteTime {
  79. @throw FSTAbstractMethodException(); // NOLINT
  80. }
  81. - (const DocumentKey &)key {
  82. return _key;
  83. }
  84. - (const firebase::firestore::model::Precondition &)precondition {
  85. return _precondition;
  86. }
  87. - (BOOL)idempotent {
  88. @throw FSTAbstractMethodException(); // NOLINT
  89. }
  90. - (nullable const FieldMask *)fieldMask {
  91. @throw FSTAbstractMethodException(); // NOLINT
  92. }
  93. - (void)verifyKeyMatches:(nullable FSTMaybeDocument *)maybeDoc {
  94. if (maybeDoc) {
  95. HARD_ASSERT(maybeDoc.key == self.key, "Can only set a document with the same key");
  96. }
  97. }
  98. /**
  99. * Returns the version from the given document for use as the result of a mutation. Mutations are
  100. * defined to return the version of the base document only if it is an existing document. Deleted
  101. * and unknown documents have a post-mutation version of {@code SnapshotVersion::None()}.
  102. */
  103. - (const SnapshotVersion &)postMutationVersionForDocument:(FSTMaybeDocument *)maybeDoc {
  104. return [maybeDoc isKindOfClass:[FSTDocument class]] ? maybeDoc.version : SnapshotVersion::None();
  105. }
  106. @end
  107. #pragma mark - FSTSetMutation
  108. @implementation FSTSetMutation
  109. - (instancetype)initWithKey:(DocumentKey)key
  110. value:(FSTObjectValue *)value
  111. precondition:(Precondition)precondition {
  112. if (self = [super initWithKey:std::move(key) precondition:std::move(precondition)]) {
  113. _value = value;
  114. }
  115. return self;
  116. }
  117. - (NSString *)description {
  118. return [NSString stringWithFormat:@"<FSTSetMutation key=%s value=%@ precondition=%@>",
  119. self.key.ToString().c_str(), self.value,
  120. self.precondition.description()];
  121. }
  122. - (BOOL)isEqual:(id)other {
  123. if (other == self) {
  124. return YES;
  125. }
  126. if (![other isKindOfClass:[FSTSetMutation class]]) {
  127. return NO;
  128. }
  129. FSTSetMutation *otherMutation = (FSTSetMutation *)other;
  130. return self.key == otherMutation.key && [self.value isEqual:otherMutation.value] &&
  131. self.precondition == otherMutation.precondition;
  132. }
  133. - (NSUInteger)hash {
  134. return Hash(self.key, self.precondition, [self.value hash]);
  135. }
  136. - (nullable FSTMaybeDocument *)applyToLocalDocument:(nullable FSTMaybeDocument *)maybeDoc
  137. baseDocument:(nullable FSTMaybeDocument *)baseDoc
  138. localWriteTime:(FIRTimestamp *)localWriteTime {
  139. [self verifyKeyMatches:maybeDoc];
  140. if (!self.precondition.IsValidFor(maybeDoc)) {
  141. return maybeDoc;
  142. }
  143. SnapshotVersion version = [self postMutationVersionForDocument:maybeDoc];
  144. return [FSTDocument documentWithData:self.value
  145. key:self.key
  146. version:version
  147. state:FSTDocumentStateLocalMutations];
  148. }
  149. - (FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument *)maybeDoc
  150. mutationResult:(FSTMutationResult *)mutationResult {
  151. [self verifyKeyMatches:maybeDoc];
  152. HARD_ASSERT(!mutationResult.transformResults, "Transform results received by FSTSetMutation.");
  153. // Unlike applyToLocalView, if we're applying a mutation to a remote document the server has
  154. // accepted the mutation so the precondition must have held.
  155. return [FSTDocument documentWithData:self.value
  156. key:self.key
  157. version:mutationResult.version
  158. state:FSTDocumentStateCommittedMutations];
  159. }
  160. - (nullable const FieldMask *)fieldMask {
  161. return nullptr;
  162. }
  163. - (BOOL)idempotent {
  164. return YES;
  165. }
  166. @end
  167. #pragma mark - FSTPatchMutation
  168. @implementation FSTPatchMutation {
  169. FieldMask _fieldMask;
  170. }
  171. - (instancetype)initWithKey:(DocumentKey)key
  172. fieldMask:(FieldMask)fieldMask
  173. value:(FSTObjectValue *)value
  174. precondition:(Precondition)precondition {
  175. self = [super initWithKey:std::move(key) precondition:std::move(precondition)];
  176. if (self) {
  177. _fieldMask = std::move(fieldMask);
  178. _value = value;
  179. }
  180. return self;
  181. }
  182. - (const FieldMask *)fieldMask {
  183. return &_fieldMask;
  184. }
  185. - (BOOL)isEqual:(id)other {
  186. if (other == self) {
  187. return YES;
  188. }
  189. if (![other isKindOfClass:[FSTPatchMutation class]]) {
  190. return NO;
  191. }
  192. FSTPatchMutation *otherMutation = (FSTPatchMutation *)other;
  193. return self.key == otherMutation.key && _fieldMask == *(otherMutation.fieldMask) &&
  194. [self.value isEqual:otherMutation.value] &&
  195. self.precondition == otherMutation.precondition;
  196. }
  197. - (NSUInteger)hash {
  198. return Hash(self.key, self.precondition, _fieldMask, [self.value hash]);
  199. }
  200. - (NSString *)description {
  201. return [NSString stringWithFormat:@"<FSTPatchMutation key=%s mask=%s value=%@ precondition=%@>",
  202. self.key.ToString().c_str(), _fieldMask.ToString().c_str(),
  203. self.value, self.precondition.description()];
  204. }
  205. /**
  206. * Patches the data of document if available or creates a new document. Note that this does not
  207. * check whether or not the precondition of this patch holds.
  208. */
  209. - (FSTObjectValue *)patchDocument:(nullable FSTMaybeDocument *)maybeDoc {
  210. FSTObjectValue *data;
  211. if ([maybeDoc isKindOfClass:[FSTDocument class]]) {
  212. data = ((FSTDocument *)maybeDoc).data;
  213. } else {
  214. data = [FSTObjectValue objectValue];
  215. }
  216. return [self patchObjectValue:data];
  217. }
  218. - (nullable FSTMaybeDocument *)applyToLocalDocument:(nullable FSTMaybeDocument *)maybeDoc
  219. baseDocument:(nullable FSTMaybeDocument *)baseDoc
  220. localWriteTime:(FIRTimestamp *)localWriteTime {
  221. [self verifyKeyMatches:maybeDoc];
  222. if (!self.precondition.IsValidFor(maybeDoc)) {
  223. return maybeDoc;
  224. }
  225. FSTObjectValue *newData = [self patchDocument:maybeDoc];
  226. SnapshotVersion version = [self postMutationVersionForDocument:maybeDoc];
  227. return [FSTDocument documentWithData:newData
  228. key:self.key
  229. version:version
  230. state:FSTDocumentStateLocalMutations];
  231. }
  232. - (FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument *)maybeDoc
  233. mutationResult:(FSTMutationResult *)mutationResult {
  234. [self verifyKeyMatches:maybeDoc];
  235. HARD_ASSERT(!mutationResult.transformResults, "Transform results received by FSTPatchMutation.");
  236. if (!self.precondition.IsValidFor(maybeDoc)) {
  237. // Since the mutation was not rejected, we know that the precondition matched on the backend.
  238. // We therefore must not have the expected version of the document in our cache and return a
  239. // FSTUnknownDocument with the known updateTime.
  240. return [FSTUnknownDocument documentWithKey:self.key version:mutationResult.version];
  241. }
  242. FSTObjectValue *newData = [self patchDocument:maybeDoc];
  243. return [FSTDocument documentWithData:newData
  244. key:self.key
  245. version:mutationResult.version
  246. state:FSTDocumentStateCommittedMutations];
  247. }
  248. - (FSTObjectValue *)patchObjectValue:(FSTObjectValue *)objectValue {
  249. FSTObjectValue *result = objectValue;
  250. for (const FieldPath &fieldPath : _fieldMask) {
  251. if (!fieldPath.empty()) {
  252. FSTFieldValue *newValue = [self.value valueForPath:fieldPath];
  253. if (newValue) {
  254. result = [result objectBySettingValue:newValue forPath:fieldPath];
  255. } else {
  256. result = [result objectByDeletingPath:fieldPath];
  257. }
  258. }
  259. }
  260. return result;
  261. }
  262. - (BOOL)idempotent {
  263. return YES;
  264. }
  265. @end
  266. @implementation FSTTransformMutation {
  267. /** The field transforms to use when transforming the document. */
  268. std::vector<FieldTransform> _fieldTransforms;
  269. FieldMask _fieldMask;
  270. }
  271. - (instancetype)initWithKey:(DocumentKey)key
  272. fieldTransforms:(std::vector<FieldTransform>)fieldTransforms {
  273. // NOTE: We set a precondition of exists: true as a safety-check, since we always combine
  274. // FSTTransformMutations with a FSTSetMutation or FSTPatchMutation which (if successful) should
  275. // end up with an existing document.
  276. if (self = [super initWithKey:std::move(key) precondition:Precondition::Exists(true)]) {
  277. _fieldTransforms = std::move(fieldTransforms);
  278. std::set<FieldPath> fields;
  279. for (const auto &transform : _fieldTransforms) {
  280. fields.insert(transform.path());
  281. }
  282. _fieldMask = FieldMask(std::move(fields));
  283. }
  284. return self;
  285. }
  286. - (const std::vector<FieldTransform> &)fieldTransforms {
  287. return _fieldTransforms;
  288. }
  289. - (BOOL)isEqual:(id)other {
  290. if (other == self) {
  291. return YES;
  292. }
  293. if (![other isKindOfClass:[FSTTransformMutation class]]) {
  294. return NO;
  295. }
  296. FSTTransformMutation *otherMutation = (FSTTransformMutation *)other;
  297. return self.key == otherMutation.key && self.fieldTransforms == otherMutation.fieldTransforms &&
  298. self.precondition == otherMutation.precondition;
  299. }
  300. - (NSUInteger)hash {
  301. NSUInteger result = self.key.Hash();
  302. result = 31 * result + self.precondition.Hash();
  303. for (const auto &transform : self.fieldTransforms) {
  304. result = 31 * result + transform.Hash();
  305. }
  306. return result;
  307. }
  308. - (NSString *)description {
  309. std::string fieldTransforms;
  310. for (const auto &transform : self.fieldTransforms) {
  311. fieldTransforms += " " + transform.path().CanonicalString();
  312. }
  313. return [NSString stringWithFormat:@"<FSTTransformMutation key=%s transforms=%s precondition=%@>",
  314. self.key.ToString().c_str(), fieldTransforms.c_str(),
  315. self.precondition.description()];
  316. }
  317. - (nullable FSTMaybeDocument *)applyToLocalDocument:(nullable FSTMaybeDocument *)maybeDoc
  318. baseDocument:(nullable FSTMaybeDocument *)baseDoc
  319. localWriteTime:(FIRTimestamp *)localWriteTime {
  320. [self verifyKeyMatches:maybeDoc];
  321. if (!self.precondition.IsValidFor(maybeDoc)) {
  322. return maybeDoc;
  323. }
  324. // We only support transforms with precondition exists, so we can only apply it to an existing
  325. // document
  326. HARD_ASSERT([maybeDoc isMemberOfClass:[FSTDocument class]], "Unknown MaybeDocument type %s",
  327. [maybeDoc class]);
  328. FSTDocument *doc = (FSTDocument *)maybeDoc;
  329. NSArray<FSTFieldValue *> *transformResults =
  330. [self localTransformResultsWithBaseDocument:baseDoc writeTime:localWriteTime];
  331. FSTObjectValue *newData = [self transformObject:doc.data transformResults:transformResults];
  332. return [FSTDocument documentWithData:newData
  333. key:doc.key
  334. version:doc.version
  335. state:FSTDocumentStateLocalMutations];
  336. }
  337. - (FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument *)maybeDoc
  338. mutationResult:(FSTMutationResult *)mutationResult {
  339. [self verifyKeyMatches:maybeDoc];
  340. HARD_ASSERT(mutationResult.transformResults,
  341. "Transform results missing for FSTTransformMutation.");
  342. if (!self.precondition.IsValidFor(maybeDoc)) {
  343. // Since the mutation was not rejected, we know that the precondition matched on the backend.
  344. // We therefore must not have the expected version of the document in our cache and return an
  345. // FSTUnknownDocument with the known updateTime.
  346. return [FSTUnknownDocument documentWithKey:self.key version:mutationResult.version];
  347. }
  348. // We only support transforms with precondition exists, so we can only apply it to an existing
  349. // document
  350. HARD_ASSERT([maybeDoc isMemberOfClass:[FSTDocument class]], "Unknown MaybeDocument type %s",
  351. [maybeDoc class]);
  352. FSTDocument *doc = (FSTDocument *)maybeDoc;
  353. NSArray<FSTFieldValue *> *transformResults =
  354. [self serverTransformResultsWithBaseDocument:maybeDoc
  355. serverTransformResults:mutationResult.transformResults];
  356. FSTObjectValue *newData = [self transformObject:doc.data transformResults:transformResults];
  357. return [FSTDocument documentWithData:newData
  358. key:self.key
  359. version:mutationResult.version
  360. state:FSTDocumentStateCommittedMutations];
  361. }
  362. /**
  363. * Creates an array of "transform results" (a transform result is a field value representing the
  364. * result of applying a transform) for use after a FSTTransformMutation has been acknowledged by
  365. * the server.
  366. *
  367. * @param baseDocument The document prior to applying this mutation batch.
  368. * @param serverTransformResults The transform results received by the server.
  369. * @return The transform results array.
  370. */
  371. - (NSArray<FSTFieldValue *> *)
  372. serverTransformResultsWithBaseDocument:(nullable FSTMaybeDocument *)baseDocument
  373. serverTransformResults:(NSArray<FSTFieldValue *> *)serverTransformResults {
  374. NSMutableArray<FSTFieldValue *> *transformResults = [NSMutableArray array];
  375. HARD_ASSERT(self.fieldTransforms.size() == serverTransformResults.count,
  376. "server transform result count (%s) should match field transforms count (%s)",
  377. (unsigned long)serverTransformResults.count, self.fieldTransforms.size());
  378. for (NSUInteger i = 0; i < serverTransformResults.count; i++) {
  379. const FieldTransform &fieldTransform = self.fieldTransforms[i];
  380. const TransformOperation &transform = fieldTransform.transformation();
  381. FSTFieldValue *previousValue = nil;
  382. if ([baseDocument isMemberOfClass:[FSTDocument class]]) {
  383. previousValue = [((FSTDocument *)baseDocument) fieldForPath:fieldTransform.path()];
  384. }
  385. [transformResults
  386. addObject:transform.ApplyToRemoteDocument(previousValue, serverTransformResults[i])];
  387. }
  388. return transformResults;
  389. }
  390. /**
  391. * Creates an array of "transform results" (a transform result is a field value representing the
  392. * result of applying a transform) for use when applying an FSTTransformMutation locally.
  393. *
  394. * @param baseDocument The document prior to applying this mutation batch.
  395. * @param localWriteTime The local time of the transform mutation (used to generate
  396. * FSTServerTimestampValues).
  397. * @return The transform results array.
  398. */
  399. - (NSArray<FSTFieldValue *> *)localTransformResultsWithBaseDocument:
  400. (nullable FSTMaybeDocument *)baseDocument
  401. writeTime:(FIRTimestamp *)localWriteTime {
  402. NSMutableArray<FSTFieldValue *> *transformResults = [NSMutableArray array];
  403. for (const FieldTransform &fieldTransform : self.fieldTransforms) {
  404. const TransformOperation &transform = fieldTransform.transformation();
  405. FSTFieldValue *previousValue = nil;
  406. if ([baseDocument isMemberOfClass:[FSTDocument class]]) {
  407. previousValue = [((FSTDocument *)baseDocument) fieldForPath:fieldTransform.path()];
  408. }
  409. [transformResults addObject:transform.ApplyToLocalView(previousValue, localWriteTime)];
  410. }
  411. return transformResults;
  412. }
  413. - (FSTObjectValue *)transformObject:(FSTObjectValue *)objectValue
  414. transformResults:(NSArray<FSTFieldValue *> *)transformResults {
  415. HARD_ASSERT(transformResults.count == self.fieldTransforms.size(),
  416. "Transform results length mismatch.");
  417. for (size_t i = 0; i < self.fieldTransforms.size(); i++) {
  418. const FieldTransform &fieldTransform = self.fieldTransforms[i];
  419. const FieldPath &fieldPath = fieldTransform.path();
  420. objectValue = [objectValue objectBySettingValue:transformResults[i] forPath:fieldPath];
  421. }
  422. return objectValue;
  423. }
  424. - (nullable const FieldMask *)fieldMask {
  425. return &_fieldMask;
  426. }
  427. - (BOOL)idempotent {
  428. for (const auto &transform : self.fieldTransforms) {
  429. if (!transform.idempotent()) {
  430. return NO;
  431. }
  432. }
  433. return YES;
  434. }
  435. @end
  436. #pragma mark - FSTDeleteMutation
  437. @implementation FSTDeleteMutation
  438. - (BOOL)isEqual:(id)other {
  439. if (other == self) {
  440. return YES;
  441. }
  442. if (![other isKindOfClass:[FSTDeleteMutation class]]) {
  443. return NO;
  444. }
  445. FSTDeleteMutation *otherMutation = (FSTDeleteMutation *)other;
  446. return self.key == otherMutation.key && self.precondition == otherMutation.precondition;
  447. }
  448. - (NSUInteger)hash {
  449. return Hash(self.key, self.precondition);
  450. }
  451. - (NSString *)description {
  452. return [NSString stringWithFormat:@"<FSTDeleteMutation key=%s precondition=%@>",
  453. self.key.ToString().c_str(), self.precondition.description()];
  454. }
  455. - (nullable FSTMaybeDocument *)applyToLocalDocument:(nullable FSTMaybeDocument *)maybeDoc
  456. baseDocument:(nullable FSTMaybeDocument *)baseDoc
  457. localWriteTime:(FIRTimestamp *)localWriteTime {
  458. [self verifyKeyMatches:maybeDoc];
  459. if (!self.precondition.IsValidFor(maybeDoc)) {
  460. return maybeDoc;
  461. }
  462. return [FSTDeletedDocument documentWithKey:self.key
  463. version:SnapshotVersion::None()
  464. hasCommittedMutations:NO];
  465. }
  466. - (FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument *)maybeDoc
  467. mutationResult:(FSTMutationResult *)mutationResult {
  468. [self verifyKeyMatches:maybeDoc];
  469. if (mutationResult) {
  470. HARD_ASSERT(!mutationResult.transformResults,
  471. "Transform results received by FSTDeleteMutation.");
  472. }
  473. // Unlike applyToLocalView, if we're applying a mutation to a remote document the server has
  474. // accepted the mutation so the precondition must have held.
  475. // We store the deleted document at the commit version of the delete. Any document version
  476. // that the server sends us before the delete was applied is discarded
  477. return [FSTDeletedDocument documentWithKey:self.key
  478. version:mutationResult.version
  479. hasCommittedMutations:YES];
  480. }
  481. - (nullable const FieldMask *)fieldMask {
  482. return nullptr;
  483. }
  484. - (BOOL)idempotent {
  485. return YES;
  486. }
  487. @end
  488. NS_ASSUME_NONNULL_END