FSTMutation.mm 21 KB

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