FSTMutation.mm 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555
  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. - (nullable 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. @end
  87. #pragma mark - FSTSetMutation
  88. @implementation FSTSetMutation
  89. - (instancetype)initWithKey:(DocumentKey)key
  90. value:(FSTObjectValue *)value
  91. precondition:(Precondition)precondition {
  92. if (self = [super initWithKey:std::move(key) precondition:std::move(precondition)]) {
  93. _value = value;
  94. }
  95. return self;
  96. }
  97. - (NSString *)description {
  98. return [NSString stringWithFormat:@"<FSTSetMutation key=%s value=%@ precondition=%@>",
  99. self.key.ToString().c_str(), self.value,
  100. self.precondition.description()];
  101. }
  102. - (BOOL)isEqual:(id)other {
  103. if (other == self) {
  104. return YES;
  105. }
  106. if (![other isKindOfClass:[FSTSetMutation class]]) {
  107. return NO;
  108. }
  109. FSTSetMutation *otherMutation = (FSTSetMutation *)other;
  110. return self.key == otherMutation.key && [self.value isEqual:otherMutation.value] &&
  111. self.precondition == otherMutation.precondition;
  112. }
  113. - (NSUInteger)hash {
  114. return Hash(self.key, self.precondition, [self.value hash]);
  115. }
  116. - (nullable FSTMaybeDocument *)applyToLocalDocument:(nullable FSTMaybeDocument *)maybeDoc
  117. baseDocument:(nullable FSTMaybeDocument *)baseDoc
  118. localWriteTime:(FIRTimestamp *)localWriteTime {
  119. if (!self.precondition.IsValidFor(maybeDoc)) {
  120. return maybeDoc;
  121. }
  122. if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) {
  123. // If the document didn't exist before, create it.
  124. return [FSTDocument documentWithData:self.value
  125. key:self.key
  126. version:SnapshotVersion::None()
  127. hasLocalMutations:YES];
  128. }
  129. HARD_ASSERT([maybeDoc isMemberOfClass:[FSTDocument class]], "Unknown MaybeDocument type %s",
  130. [maybeDoc class]);
  131. FSTDocument *doc = (FSTDocument *)maybeDoc;
  132. HARD_ASSERT(doc.key == self.key, "Can only set a document with the same key");
  133. return [FSTDocument documentWithData:self.value
  134. key:doc.key
  135. version:doc.version
  136. hasLocalMutations:YES];
  137. }
  138. - (nullable FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument *)maybeDoc
  139. mutationResult:(FSTMutationResult *)mutationResult {
  140. if (mutationResult) {
  141. HARD_ASSERT(!mutationResult.transformResults, "Transform results received by FSTSetMutation.");
  142. }
  143. if (!self.precondition.IsValidFor(maybeDoc)) {
  144. return maybeDoc;
  145. }
  146. if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) {
  147. // If the document didn't exist before, create it.
  148. return [FSTDocument documentWithData:self.value
  149. key:self.key
  150. version:SnapshotVersion::None()
  151. hasLocalMutations:NO];
  152. }
  153. HARD_ASSERT([maybeDoc isMemberOfClass:[FSTDocument class]], "Unknown MaybeDocument type %s",
  154. [maybeDoc class]);
  155. FSTDocument *doc = (FSTDocument *)maybeDoc;
  156. HARD_ASSERT(doc.key == self.key, "Can only set a document with the same key");
  157. return [FSTDocument documentWithData:self.value
  158. key:doc.key
  159. version:doc.version
  160. hasLocalMutations:NO];
  161. }
  162. @end
  163. #pragma mark - FSTPatchMutation
  164. @implementation FSTPatchMutation {
  165. FieldMask _fieldMask;
  166. }
  167. - (instancetype)initWithKey:(DocumentKey)key
  168. fieldMask:(FieldMask)fieldMask
  169. value:(FSTObjectValue *)value
  170. precondition:(Precondition)precondition {
  171. self = [super initWithKey:std::move(key) precondition:std::move(precondition)];
  172. if (self) {
  173. _fieldMask = std::move(fieldMask);
  174. _value = value;
  175. }
  176. return self;
  177. }
  178. - (const firebase::firestore::model::FieldMask &)fieldMask {
  179. return _fieldMask;
  180. }
  181. - (BOOL)isEqual:(id)other {
  182. if (other == self) {
  183. return YES;
  184. }
  185. if (![other isKindOfClass:[FSTPatchMutation class]]) {
  186. return NO;
  187. }
  188. FSTPatchMutation *otherMutation = (FSTPatchMutation *)other;
  189. return self.key == otherMutation.key && self.fieldMask == otherMutation.fieldMask &&
  190. [self.value isEqual:otherMutation.value] &&
  191. self.precondition == otherMutation.precondition;
  192. }
  193. - (NSUInteger)hash {
  194. return Hash(self.key, self.precondition, self.fieldMask, [self.value hash]);
  195. }
  196. - (NSString *)description {
  197. return [NSString stringWithFormat:@"<FSTPatchMutation key=%s mask=%s value=%@ precondition=%@>",
  198. self.key.ToString().c_str(), self.fieldMask.ToString().c_str(),
  199. self.value, self.precondition.description()];
  200. }
  201. - (nullable FSTMaybeDocument *)applyToLocalDocument:(nullable FSTMaybeDocument *)maybeDoc
  202. baseDocument:(nullable FSTMaybeDocument *)baseDoc
  203. localWriteTime:(FIRTimestamp *)localWriteTime {
  204. if (!self.precondition.IsValidFor(maybeDoc)) {
  205. return maybeDoc;
  206. }
  207. if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) {
  208. // Precondition applied, so create the document if necessary
  209. const DocumentKey &key = maybeDoc ? maybeDoc.key : self.key;
  210. SnapshotVersion version = maybeDoc ? maybeDoc.version : SnapshotVersion::None();
  211. maybeDoc = [FSTDocument documentWithData:[FSTObjectValue objectValue]
  212. key:key
  213. version:std::move(version)
  214. hasLocalMutations:YES];
  215. }
  216. HARD_ASSERT([maybeDoc isMemberOfClass:[FSTDocument class]], "Unknown MaybeDocument type %s",
  217. [maybeDoc class]);
  218. FSTDocument *doc = (FSTDocument *)maybeDoc;
  219. HARD_ASSERT(doc.key == self.key, "Can only patch a document with the same key");
  220. FSTObjectValue *newData = [self patchObjectValue:doc.data];
  221. return
  222. [FSTDocument documentWithData:newData key:doc.key version:doc.version hasLocalMutations:YES];
  223. }
  224. - (nullable FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument *)maybeDoc
  225. mutationResult:(FSTMutationResult *)mutationResult {
  226. HARD_ASSERT(!mutationResult.transformResults, "Transform results received by FSTPatchMutation.");
  227. if (!self.precondition.IsValidFor(maybeDoc)) {
  228. return maybeDoc;
  229. }
  230. BOOL hasLocalMutations = (mutationResult == nil);
  231. if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) {
  232. // Precondition applied, so create the document if necessary
  233. const DocumentKey &key = maybeDoc ? maybeDoc.key : self.key;
  234. SnapshotVersion version = maybeDoc ? maybeDoc.version : SnapshotVersion::None();
  235. maybeDoc = [FSTDocument documentWithData:[FSTObjectValue objectValue]
  236. key:key
  237. version:std::move(version)
  238. hasLocalMutations:hasLocalMutations];
  239. }
  240. HARD_ASSERT([maybeDoc isMemberOfClass:[FSTDocument class]], "Unknown MaybeDocument type %s",
  241. [maybeDoc class]);
  242. FSTDocument *doc = (FSTDocument *)maybeDoc;
  243. HARD_ASSERT(doc.key == self.key, "Can only patch a document with the same key");
  244. FSTObjectValue *newData = [self patchObjectValue:doc.data];
  245. return [FSTDocument documentWithData:newData
  246. key:doc.key
  247. version:doc.version
  248. hasLocalMutations:hasLocalMutations];
  249. }
  250. - (FSTObjectValue *)patchObjectValue:(FSTObjectValue *)objectValue {
  251. FSTObjectValue *result = objectValue;
  252. for (const FieldPath &fieldPath : self.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. @end
  265. @implementation FSTTransformMutation {
  266. /** The field transforms to use when transforming the document. */
  267. std::vector<FieldTransform> _fieldTransforms;
  268. }
  269. - (instancetype)initWithKey:(DocumentKey)key
  270. fieldTransforms:(std::vector<FieldTransform>)fieldTransforms {
  271. // NOTE: We set a precondition of exists: true as a safety-check, since we always combine
  272. // FSTTransformMutations with a FSTSetMutation or FSTPatchMutation which (if successful) should
  273. // end up with an existing document.
  274. if (self = [super initWithKey:std::move(key) precondition:Precondition::Exists(true)]) {
  275. _fieldTransforms = std::move(fieldTransforms);
  276. }
  277. return self;
  278. }
  279. - (const std::vector<FieldTransform> &)fieldTransforms {
  280. return _fieldTransforms;
  281. }
  282. - (BOOL)isEqual:(id)other {
  283. if (other == self) {
  284. return YES;
  285. }
  286. if (![other isKindOfClass:[FSTTransformMutation class]]) {
  287. return NO;
  288. }
  289. FSTTransformMutation *otherMutation = (FSTTransformMutation *)other;
  290. return self.key == otherMutation.key && self.fieldTransforms == otherMutation.fieldTransforms &&
  291. self.precondition == otherMutation.precondition;
  292. }
  293. - (NSUInteger)hash {
  294. NSUInteger result = self.key.Hash();
  295. result = 31 * result + self.precondition.Hash();
  296. for (const auto &transform : self.fieldTransforms) {
  297. result = 31 * result + transform.Hash();
  298. }
  299. return result;
  300. }
  301. - (NSString *)description {
  302. std::string fieldTransforms;
  303. for (const auto &transform : self.fieldTransforms) {
  304. fieldTransforms += " " + transform.path().CanonicalString();
  305. }
  306. return [NSString stringWithFormat:@"<FSTTransformMutation key=%s transforms=%s precondition=%@>",
  307. self.key.ToString().c_str(), fieldTransforms.c_str(),
  308. self.precondition.description()];
  309. }
  310. - (nullable FSTMaybeDocument *)applyToLocalDocument:(nullable FSTMaybeDocument *)maybeDoc
  311. baseDocument:(nullable FSTMaybeDocument *)baseDoc
  312. localWriteTime:(FIRTimestamp *)localWriteTime {
  313. if (!self.precondition.IsValidFor(maybeDoc)) {
  314. return maybeDoc;
  315. }
  316. // We only support transforms with precondition exists, so we can only apply it to an existing
  317. // document
  318. HARD_ASSERT([maybeDoc isMemberOfClass:[FSTDocument class]], "Unknown MaybeDocument type %s",
  319. [maybeDoc class]);
  320. FSTDocument *doc = (FSTDocument *)maybeDoc;
  321. HARD_ASSERT(doc.key == self.key, "Can only transform a document with the same key");
  322. NSArray<FSTFieldValue *> *transformResults =
  323. [self localTransformResultsWithBaseDocument:baseDoc writeTime:localWriteTime];
  324. FSTObjectValue *newData = [self transformObject:doc.data transformResults:transformResults];
  325. return
  326. [FSTDocument documentWithData:newData key:doc.key version:doc.version hasLocalMutations:YES];
  327. }
  328. - (nullable FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument *)maybeDoc
  329. mutationResult:(FSTMutationResult *)mutationResult {
  330. HARD_ASSERT(mutationResult.transformResults,
  331. "Transform results missing for FSTTransformMutation.");
  332. if (!self.precondition.IsValidFor(maybeDoc)) {
  333. return maybeDoc;
  334. }
  335. // We only support transforms with precondition exists, so we can only apply it to an existing
  336. // document
  337. HARD_ASSERT([maybeDoc isMemberOfClass:[FSTDocument class]], "Unknown MaybeDocument type %s",
  338. [maybeDoc class]);
  339. FSTDocument *doc = (FSTDocument *)maybeDoc;
  340. HARD_ASSERT(doc.key == self.key, "Can only transform a document with the same key");
  341. NSArray<FSTFieldValue *> *transformResults =
  342. [self serverTransformResultsWithBaseDocument:maybeDoc
  343. serverTransformResults:mutationResult.transformResults];
  344. FSTObjectValue *newData = [self transformObject:doc.data transformResults:transformResults];
  345. return
  346. [FSTDocument documentWithData:newData key:doc.key version:doc.version hasLocalMutations:NO];
  347. }
  348. /**
  349. * Creates an array of "transform results" (a transform result is a field value representing the
  350. * result of applying a transform) for use after a FSTTransformMutation has been acknowledged by
  351. * the server.
  352. *
  353. * @param baseDocument The document prior to applying this mutation batch.
  354. * @param serverTransformResults The transform results received by the server.
  355. * @return The transform results array.
  356. */
  357. - (NSArray<FSTFieldValue *> *)
  358. serverTransformResultsWithBaseDocument:(nullable FSTMaybeDocument *)baseDocument
  359. serverTransformResults:(NSArray<FSTFieldValue *> *)serverTransformResults {
  360. NSMutableArray<FSTFieldValue *> *transformResults = [NSMutableArray array];
  361. HARD_ASSERT(self.fieldTransforms.size() == serverTransformResults.count,
  362. "server transform result count (%s) should match field transforms count (%s)",
  363. (unsigned long)serverTransformResults.count, self.fieldTransforms.size());
  364. for (NSUInteger i = 0; i < serverTransformResults.count; i++) {
  365. const FieldTransform &fieldTransform = self.fieldTransforms[i];
  366. const TransformOperation &transform = fieldTransform.transformation();
  367. FSTFieldValue *previousValue = nil;
  368. if ([baseDocument isMemberOfClass:[FSTDocument class]]) {
  369. previousValue = [((FSTDocument *)baseDocument) fieldForPath:fieldTransform.path()];
  370. }
  371. [transformResults
  372. addObject:transform.ApplyToRemoteDocument(previousValue, serverTransformResults[i])];
  373. }
  374. return transformResults;
  375. }
  376. /**
  377. * Creates an array of "transform results" (a transform result is a field value representing the
  378. * result of applying a transform) for use when applying an FSTTransformMutation locally.
  379. *
  380. * @param baseDocument The document prior to applying this mutation batch.
  381. * @param localWriteTime The local time of the transform mutation (used to generate
  382. * FSTServerTimestampValues).
  383. * @return The transform results array.
  384. */
  385. - (NSArray<FSTFieldValue *> *)localTransformResultsWithBaseDocument:
  386. (nullable FSTMaybeDocument *)baseDocument
  387. writeTime:(FIRTimestamp *)localWriteTime {
  388. NSMutableArray<FSTFieldValue *> *transformResults = [NSMutableArray array];
  389. for (const FieldTransform &fieldTransform : self.fieldTransforms) {
  390. const TransformOperation &transform = fieldTransform.transformation();
  391. FSTFieldValue *previousValue = nil;
  392. if ([baseDocument isMemberOfClass:[FSTDocument class]]) {
  393. previousValue = [((FSTDocument *)baseDocument) fieldForPath:fieldTransform.path()];
  394. }
  395. [transformResults addObject:transform.ApplyToLocalView(previousValue, localWriteTime)];
  396. }
  397. return transformResults;
  398. }
  399. - (FSTObjectValue *)transformObject:(FSTObjectValue *)objectValue
  400. transformResults:(NSArray<FSTFieldValue *> *)transformResults {
  401. HARD_ASSERT(transformResults.count == self.fieldTransforms.size(),
  402. "Transform results length mismatch.");
  403. for (size_t i = 0; i < self.fieldTransforms.size(); i++) {
  404. const FieldTransform &fieldTransform = self.fieldTransforms[i];
  405. const FieldPath &fieldPath = fieldTransform.path();
  406. objectValue = [objectValue objectBySettingValue:transformResults[i] forPath:fieldPath];
  407. }
  408. return objectValue;
  409. }
  410. @end
  411. #pragma mark - FSTDeleteMutation
  412. @implementation FSTDeleteMutation
  413. - (BOOL)isEqual:(id)other {
  414. if (other == self) {
  415. return YES;
  416. }
  417. if (![other isKindOfClass:[FSTDeleteMutation class]]) {
  418. return NO;
  419. }
  420. FSTDeleteMutation *otherMutation = (FSTDeleteMutation *)other;
  421. return self.key == otherMutation.key && self.precondition == otherMutation.precondition;
  422. }
  423. - (NSUInteger)hash {
  424. return Hash(self.key, self.precondition);
  425. }
  426. - (NSString *)description {
  427. return [NSString stringWithFormat:@"<FSTDeleteMutation key=%s precondition=%@>",
  428. self.key.ToString().c_str(), self.precondition.description()];
  429. }
  430. - (nullable FSTMaybeDocument *)applyToLocalDocument:(nullable FSTMaybeDocument *)maybeDoc
  431. baseDocument:(nullable FSTMaybeDocument *)baseDoc
  432. localWriteTime:(FIRTimestamp *)localWriteTime {
  433. if (!self.precondition.IsValidFor(maybeDoc)) {
  434. return maybeDoc;
  435. }
  436. if (maybeDoc) {
  437. HARD_ASSERT(maybeDoc.key == self.key, "Can only delete a document with the same key");
  438. }
  439. return [FSTDeletedDocument documentWithKey:self.key version:SnapshotVersion::None()];
  440. }
  441. - (nullable FSTMaybeDocument *)applyToRemoteDocument:(nullable FSTMaybeDocument *)maybeDoc
  442. mutationResult:(FSTMutationResult *)mutationResult {
  443. if (mutationResult) {
  444. HARD_ASSERT(!mutationResult.transformResults,
  445. "Transform results received by FSTDeleteMutation.");
  446. }
  447. if (!self.precondition.IsValidFor(maybeDoc)) {
  448. return maybeDoc;
  449. }
  450. if (maybeDoc) {
  451. HARD_ASSERT(maybeDoc.key == self.key, "Can only delete a document with the same key");
  452. }
  453. return [FSTDeletedDocument documentWithKey:self.key version:SnapshotVersion::None()];
  454. }
  455. @end
  456. NS_ASSUME_NONNULL_END