FSTMutation.mm 18 KB

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