FSTMutation.mm 20 KB

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