FSTMutation.mm 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  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. #import "FIRTimestamp.h"
  18. #import "Firestore/Source/Core/FSTSnapshotVersion.h"
  19. #import "Firestore/Source/Model/FSTDocument.h"
  20. #import "Firestore/Source/Model/FSTDocumentKey.h"
  21. #import "Firestore/Source/Model/FSTFieldValue.h"
  22. #import "Firestore/Source/Util/FSTAssert.h"
  23. #import "Firestore/Source/Util/FSTClasses.h"
  24. #include "Firestore/core/src/firebase/firestore/model/field_path.h"
  25. using firebase::firestore::model::FieldPath;
  26. NS_ASSUME_NONNULL_BEGIN
  27. #pragma mark - FSTFieldMask
  28. @implementation FSTFieldMask {
  29. std::vector<FieldPath> _fields;
  30. }
  31. - (instancetype)initWithFields:(std::vector<FieldPath>)fields {
  32. if (self = [super init]) {
  33. _fields = std::move(fields);
  34. }
  35. return self;
  36. }
  37. - (BOOL)isEqual:(id)other {
  38. if (other == self) {
  39. return YES;
  40. }
  41. if (![other isKindOfClass:[FSTFieldMask class]]) {
  42. return NO;
  43. }
  44. FSTFieldMask *otherMask = (FSTFieldMask *)other;
  45. return _fields == otherMask->_fields;
  46. }
  47. - (NSUInteger)hash {
  48. NSUInteger hashResult = 0;
  49. for (const FieldPath &field : _fields) {
  50. hashResult = hashResult * 31u + field.Hash();
  51. }
  52. return hashResult;
  53. }
  54. - (const std::vector<FieldPath> &)fields {
  55. return _fields;
  56. }
  57. @end
  58. #pragma mark - FSTServerTimestampTransform
  59. @implementation FSTServerTimestampTransform
  60. + (instancetype)serverTimestampTransform {
  61. static FSTServerTimestampTransform *sharedInstance = nil;
  62. static dispatch_once_t onceToken;
  63. dispatch_once(&onceToken, ^{
  64. sharedInstance = [[FSTServerTimestampTransform alloc] init];
  65. });
  66. return sharedInstance;
  67. }
  68. - (BOOL)isEqual:(id)other {
  69. if (other == self) {
  70. return YES;
  71. }
  72. return [other isKindOfClass:[FSTServerTimestampTransform class]];
  73. }
  74. - (NSUInteger)hash {
  75. // arbitrary number since all instances are equal.
  76. return 37;
  77. }
  78. @end
  79. #pragma mark - FSTFieldTransform
  80. @implementation FSTFieldTransform {
  81. FieldPath _path;
  82. }
  83. - (instancetype)initWithPath:(FieldPath)path transform:(id<FSTTransformOperation>)transform {
  84. self = [super init];
  85. if (self) {
  86. _path = std::move(path);
  87. _transform = transform;
  88. }
  89. return self;
  90. }
  91. - (BOOL)isEqual:(id)other {
  92. if (other == self) return YES;
  93. if (![[other class] isEqual:[self class]]) return NO;
  94. FSTFieldTransform *otherFieldTransform = other;
  95. return self.path == otherFieldTransform.path &&
  96. [self.transform isEqual:otherFieldTransform.transform];
  97. }
  98. - (NSUInteger)hash {
  99. NSUInteger hash = self.path.Hash();
  100. hash = hash * 31 + [self.transform hash];
  101. return hash;
  102. }
  103. - (const firebase::firestore::model::FieldPath &)path {
  104. return _path;
  105. }
  106. @end
  107. #pragma mark - FSTPrecondition
  108. @implementation FSTPrecondition
  109. + (FSTPrecondition *)preconditionWithExists:(BOOL)exists {
  110. FSTPreconditionExists existsEnum = exists ? FSTPreconditionExistsYes : FSTPreconditionExistsNo;
  111. return [[FSTPrecondition alloc] initWithUpdateTime:nil exists:existsEnum];
  112. }
  113. + (FSTPrecondition *)preconditionWithUpdateTime:(FSTSnapshotVersion *)updateTime {
  114. return [[FSTPrecondition alloc] initWithUpdateTime:updateTime exists:FSTPreconditionExistsNotSet];
  115. }
  116. + (FSTPrecondition *)none {
  117. static dispatch_once_t onceToken;
  118. static FSTPrecondition *noPrecondition;
  119. dispatch_once(&onceToken, ^{
  120. noPrecondition =
  121. [[FSTPrecondition alloc] initWithUpdateTime:nil exists:FSTPreconditionExistsNotSet];
  122. });
  123. return noPrecondition;
  124. }
  125. - (instancetype)initWithUpdateTime:(FSTSnapshotVersion *_Nullable)updateTime
  126. exists:(FSTPreconditionExists)exists {
  127. if (self = [super init]) {
  128. _updateTime = updateTime;
  129. _exists = exists;
  130. }
  131. return self;
  132. }
  133. - (BOOL)isValidForDocument:(FSTMaybeDocument *_Nullable)maybeDoc {
  134. if (self.updateTime) {
  135. return
  136. [maybeDoc isKindOfClass:[FSTDocument class]] && [maybeDoc.version isEqual:self.updateTime];
  137. } else if (self.exists != FSTPreconditionExistsNotSet) {
  138. if (self.exists == FSTPreconditionExistsYes) {
  139. return [maybeDoc isKindOfClass:[FSTDocument class]];
  140. } else {
  141. FSTAssert(self.exists == FSTPreconditionExistsNo, @"Invalid precondition");
  142. return maybeDoc == nil || [maybeDoc isKindOfClass:[FSTDeletedDocument class]];
  143. }
  144. } else {
  145. FSTAssert(self.isNone, @"Precondition should be empty");
  146. return YES;
  147. }
  148. }
  149. - (BOOL)isNone {
  150. return self.updateTime == nil && self.exists == FSTPreconditionExistsNotSet;
  151. }
  152. - (BOOL)isEqual:(id)other {
  153. if (self == other) {
  154. return YES;
  155. }
  156. if (![other isKindOfClass:[FSTPrecondition class]]) {
  157. return NO;
  158. }
  159. FSTPrecondition *otherPrecondition = (FSTPrecondition *)other;
  160. // Compare references to cover nil equality
  161. return (self.updateTime == otherPrecondition.updateTime ||
  162. [self.updateTime isEqual:otherPrecondition.updateTime]) &&
  163. self.exists == otherPrecondition.exists;
  164. }
  165. - (NSUInteger)hash {
  166. NSUInteger hash = [self.updateTime hash];
  167. hash = hash * 31 + self.exists;
  168. return hash;
  169. }
  170. - (NSString *)description {
  171. if (self.isNone) {
  172. return @"<FSTPrecondition <none>>";
  173. } else {
  174. NSString *existsString;
  175. switch (self.exists) {
  176. case FSTPreconditionExistsYes:
  177. existsString = @"yes";
  178. break;
  179. case FSTPreconditionExistsNo:
  180. existsString = @"no";
  181. break;
  182. default:
  183. existsString = @"<not-set>";
  184. break;
  185. }
  186. return [NSString stringWithFormat:@"<FSTPrecondition updateTime=%@ exists=%@>", self.updateTime,
  187. existsString];
  188. }
  189. }
  190. @end
  191. #pragma mark - FSTMutationResult
  192. @implementation FSTMutationResult
  193. - (instancetype)initWithVersion:(FSTSnapshotVersion *_Nullable)version
  194. transformResults:(NSArray<FSTFieldValue *> *_Nullable)transformResults {
  195. if (self = [super init]) {
  196. _version = version;
  197. _transformResults = transformResults;
  198. }
  199. return self;
  200. }
  201. @end
  202. #pragma mark - FSTMutation
  203. @implementation FSTMutation
  204. - (instancetype)initWithKey:(FSTDocumentKey *)key precondition:(FSTPrecondition *)precondition {
  205. if (self = [super init]) {
  206. _key = key;
  207. _precondition = precondition;
  208. }
  209. return self;
  210. }
  211. - (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
  212. baseDocument:(nullable FSTMaybeDocument *)baseDoc
  213. localWriteTime:(FIRTimestamp *)localWriteTime
  214. mutationResult:(nullable FSTMutationResult *)mutationResult {
  215. @throw FSTAbstractMethodException(); // NOLINT
  216. }
  217. - (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
  218. baseDocument:(nullable FSTMaybeDocument *)baseDoc
  219. localWriteTime:(nullable FIRTimestamp *)localWriteTime {
  220. return
  221. [self applyTo:maybeDoc baseDocument:baseDoc localWriteTime:localWriteTime mutationResult:nil];
  222. }
  223. @end
  224. #pragma mark - FSTSetMutation
  225. @implementation FSTSetMutation
  226. - (instancetype)initWithKey:(FSTDocumentKey *)key
  227. value:(FSTObjectValue *)value
  228. precondition:(FSTPrecondition *)precondition {
  229. if (self = [super initWithKey:key precondition:precondition]) {
  230. _value = value;
  231. }
  232. return self;
  233. }
  234. - (NSString *)description {
  235. return [NSString stringWithFormat:@"<FSTSetMutation key=%@ value=%@ precondition=%@>", self.key,
  236. self.value, self.precondition];
  237. }
  238. - (BOOL)isEqual:(id)other {
  239. if (other == self) {
  240. return YES;
  241. }
  242. if (![other isKindOfClass:[FSTSetMutation class]]) {
  243. return NO;
  244. }
  245. FSTSetMutation *otherMutation = (FSTSetMutation *)other;
  246. return [self.key isEqual:otherMutation.key] && [self.value isEqual:otherMutation.value] &&
  247. [self.precondition isEqual:otherMutation.precondition];
  248. }
  249. - (NSUInteger)hash {
  250. NSUInteger result = [self.key hash];
  251. result = 31 * result + [self.precondition hash];
  252. result = 31 * result + [self.value hash];
  253. return result;
  254. }
  255. - (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
  256. baseDocument:(nullable FSTMaybeDocument *)baseDoc
  257. localWriteTime:(FIRTimestamp *)localWriteTime
  258. mutationResult:(nullable FSTMutationResult *)mutationResult {
  259. if (mutationResult) {
  260. FSTAssert(!mutationResult.transformResults, @"Transform results received by FSTSetMutation.");
  261. }
  262. if (![self.precondition isValidForDocument:maybeDoc]) {
  263. return maybeDoc;
  264. }
  265. BOOL hasLocalMutations = (mutationResult == nil);
  266. if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) {
  267. // If the document didn't exist before, create it.
  268. return [FSTDocument documentWithData:self.value
  269. key:self.key
  270. version:[FSTSnapshotVersion noVersion]
  271. hasLocalMutations:hasLocalMutations];
  272. }
  273. FSTAssert([maybeDoc isMemberOfClass:[FSTDocument class]], @"Unknown MaybeDocument type %@",
  274. [maybeDoc class]);
  275. FSTDocument *doc = (FSTDocument *)maybeDoc;
  276. FSTAssert([doc.key isEqual:self.key], @"Can only set a document with the same key");
  277. return [FSTDocument documentWithData:self.value
  278. key:doc.key
  279. version:doc.version
  280. hasLocalMutations:hasLocalMutations];
  281. }
  282. @end
  283. #pragma mark - FSTPatchMutation
  284. @implementation FSTPatchMutation
  285. - (instancetype)initWithKey:(FSTDocumentKey *)key
  286. fieldMask:(FSTFieldMask *)fieldMask
  287. value:(FSTObjectValue *)value
  288. precondition:(FSTPrecondition *)precondition {
  289. self = [super initWithKey:key precondition:precondition];
  290. if (self) {
  291. _fieldMask = fieldMask;
  292. _value = value;
  293. }
  294. return self;
  295. }
  296. - (BOOL)isEqual:(id)other {
  297. if (other == self) {
  298. return YES;
  299. }
  300. if (![other isKindOfClass:[FSTPatchMutation class]]) {
  301. return NO;
  302. }
  303. FSTPatchMutation *otherMutation = (FSTPatchMutation *)other;
  304. return [self.key isEqual:otherMutation.key] && [self.fieldMask isEqual:otherMutation.fieldMask] &&
  305. [self.value isEqual:otherMutation.value] &&
  306. [self.precondition isEqual:otherMutation.precondition];
  307. }
  308. - (NSUInteger)hash {
  309. NSUInteger result = [self.key hash];
  310. result = 31 * result + [self.precondition hash];
  311. result = 31 * result + [self.fieldMask hash];
  312. result = 31 * result + [self.value hash];
  313. return result;
  314. }
  315. - (NSString *)description {
  316. return [NSString stringWithFormat:@"<FSTPatchMutation key=%@ mask=%@ value=%@ precondition=%@>",
  317. self.key, self.fieldMask, self.value, self.precondition];
  318. }
  319. - (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
  320. baseDocument:(nullable FSTMaybeDocument *)baseDoc
  321. localWriteTime:(FIRTimestamp *)localWriteTime
  322. mutationResult:(nullable FSTMutationResult *)mutationResult {
  323. if (mutationResult) {
  324. FSTAssert(!mutationResult.transformResults, @"Transform results received by FSTPatchMutation.");
  325. }
  326. if (![self.precondition isValidForDocument:maybeDoc]) {
  327. return maybeDoc;
  328. }
  329. BOOL hasLocalMutations = (mutationResult == nil);
  330. if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) {
  331. // Precondition applied, so create the document if necessary
  332. FSTDocumentKey *key = maybeDoc ? maybeDoc.key : self.key;
  333. FSTSnapshotVersion *version = maybeDoc ? maybeDoc.version : [FSTSnapshotVersion noVersion];
  334. maybeDoc = [FSTDocument documentWithData:[FSTObjectValue objectValue]
  335. key:key
  336. version:version
  337. hasLocalMutations:hasLocalMutations];
  338. }
  339. FSTAssert([maybeDoc isMemberOfClass:[FSTDocument class]], @"Unknown MaybeDocument type %@",
  340. [maybeDoc class]);
  341. FSTDocument *doc = (FSTDocument *)maybeDoc;
  342. FSTAssert([doc.key isEqual:self.key], @"Can only patch a document with the same key");
  343. FSTObjectValue *newData = [self patchObjectValue:doc.data];
  344. return [FSTDocument documentWithData:newData
  345. key:doc.key
  346. version:doc.version
  347. hasLocalMutations:hasLocalMutations];
  348. }
  349. - (FSTObjectValue *)patchObjectValue:(FSTObjectValue *)objectValue {
  350. FSTObjectValue *result = objectValue;
  351. for (const FieldPath &fieldPath : self.fieldMask.fields) {
  352. FSTFieldValue *newValue = [self.value valueForPath:fieldPath];
  353. if (newValue) {
  354. result = [result objectBySettingValue:newValue forPath:fieldPath];
  355. } else {
  356. result = [result objectByDeletingPath:fieldPath];
  357. }
  358. }
  359. return result;
  360. }
  361. @end
  362. @implementation FSTTransformMutation
  363. - (instancetype)initWithKey:(FSTDocumentKey *)key
  364. fieldTransforms:(NSArray<FSTFieldTransform *> *)fieldTransforms {
  365. // NOTE: We set a precondition of exists: true as a safety-check, since we always combine
  366. // FSTTransformMutations with a FSTSetMutation or FSTPatchMutation which (if successful) should
  367. // end up with an existing document.
  368. if (self = [super initWithKey:key precondition:[FSTPrecondition preconditionWithExists:YES]]) {
  369. _fieldTransforms = fieldTransforms;
  370. }
  371. return self;
  372. }
  373. - (BOOL)isEqual:(id)other {
  374. if (other == self) {
  375. return YES;
  376. }
  377. if (![other isKindOfClass:[FSTTransformMutation class]]) {
  378. return NO;
  379. }
  380. FSTTransformMutation *otherMutation = (FSTTransformMutation *)other;
  381. return [self.key isEqual:otherMutation.key] &&
  382. [self.fieldTransforms isEqual:otherMutation.fieldTransforms] &&
  383. [self.precondition isEqual:otherMutation.precondition];
  384. }
  385. - (NSUInteger)hash {
  386. NSUInteger result = [self.key hash];
  387. result = 31 * result + [self.precondition hash];
  388. result = 31 * result + [self.fieldTransforms hash];
  389. return result;
  390. }
  391. - (NSString *)description {
  392. return [NSString stringWithFormat:@"<FSTTransformMutation key=%@ transforms=%@ precondition=%@>",
  393. self.key, self.fieldTransforms, self.precondition];
  394. }
  395. - (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
  396. baseDocument:(nullable FSTMaybeDocument *)baseDoc
  397. localWriteTime:(FIRTimestamp *)localWriteTime
  398. mutationResult:(nullable FSTMutationResult *)mutationResult {
  399. if (mutationResult) {
  400. FSTAssert(mutationResult.transformResults,
  401. @"Transform results missing for FSTTransformMutation.");
  402. }
  403. if (![self.precondition isValidForDocument:maybeDoc]) {
  404. return maybeDoc;
  405. }
  406. // We only support transforms with precondition exists, so we can only apply it to an existing
  407. // document
  408. FSTAssert([maybeDoc isMemberOfClass:[FSTDocument class]], @"Unknown MaybeDocument type %@",
  409. [maybeDoc class]);
  410. FSTDocument *doc = (FSTDocument *)maybeDoc;
  411. FSTAssert([doc.key isEqual:self.key], @"Can only patch a document with the same key");
  412. BOOL hasLocalMutations = (mutationResult == nil);
  413. NSArray<FSTFieldValue *> *transformResults =
  414. mutationResult
  415. ? mutationResult.transformResults
  416. : [self localTransformResultsWithBaseDocument:baseDoc writeTime:localWriteTime];
  417. FSTObjectValue *newData = [self transformObject:doc.data transformResults:transformResults];
  418. return [FSTDocument documentWithData:newData
  419. key:doc.key
  420. version:doc.version
  421. hasLocalMutations:hasLocalMutations];
  422. }
  423. /**
  424. * Creates an array of "transform results" (a transform result is a field value representing the
  425. * result of applying a transform) for use when applying an FSTTransformMutation locally.
  426. *
  427. * @param baseDocument The document prior to applying this mutation batch.
  428. * @param localWriteTime The local time of the transform mutation (used to generate
  429. * FSTServerTimestampValues).
  430. * @return The transform results array.
  431. */
  432. - (NSArray<FSTFieldValue *> *)localTransformResultsWithBaseDocument:
  433. (FSTMaybeDocument *_Nullable)baseDocument
  434. writeTime:(FIRTimestamp *)localWriteTime {
  435. NSMutableArray<FSTFieldValue *> *transformResults = [NSMutableArray array];
  436. for (FSTFieldTransform *fieldTransform in self.fieldTransforms) {
  437. if ([fieldTransform.transform isKindOfClass:[FSTServerTimestampTransform class]]) {
  438. FSTFieldValue *previousValue = nil;
  439. if ([baseDocument isMemberOfClass:[FSTDocument class]]) {
  440. previousValue = [((FSTDocument *)baseDocument) fieldForPath:fieldTransform.path];
  441. }
  442. [transformResults
  443. addObject:[FSTServerTimestampValue serverTimestampValueWithLocalWriteTime:localWriteTime
  444. previousValue:previousValue]];
  445. } else {
  446. FSTFail(@"Encountered unknown transform: %@", fieldTransform);
  447. }
  448. }
  449. return transformResults;
  450. }
  451. - (FSTObjectValue *)transformObject:(FSTObjectValue *)objectValue
  452. transformResults:(NSArray<FSTFieldValue *> *)transformResults {
  453. FSTAssert(transformResults.count == self.fieldTransforms.count,
  454. @"Transform results length mismatch.");
  455. for (NSUInteger i = 0; i < self.fieldTransforms.count; i++) {
  456. FSTFieldTransform *fieldTransform = self.fieldTransforms[i];
  457. id<FSTTransformOperation> transform = fieldTransform.transform;
  458. FieldPath fieldPath = fieldTransform.path;
  459. if ([transform isKindOfClass:[FSTServerTimestampTransform class]]) {
  460. objectValue = [objectValue objectBySettingValue:transformResults[i] forPath:fieldPath];
  461. } else {
  462. FSTFail(@"Encountered unknown transform: %@", transform);
  463. }
  464. }
  465. return objectValue;
  466. }
  467. @end
  468. #pragma mark - FSTDeleteMutation
  469. @implementation FSTDeleteMutation
  470. - (BOOL)isEqual:(id)other {
  471. if (other == self) {
  472. return YES;
  473. }
  474. if (![other isKindOfClass:[FSTDeleteMutation class]]) {
  475. return NO;
  476. }
  477. FSTDeleteMutation *otherMutation = (FSTDeleteMutation *)other;
  478. return [self.key isEqual:otherMutation.key] &&
  479. [self.precondition isEqual:otherMutation.precondition];
  480. }
  481. - (NSUInteger)hash {
  482. NSUInteger result = [self.key hash];
  483. result = 31 * result + [self.precondition hash];
  484. return result;
  485. }
  486. - (NSString *)description {
  487. return [NSString
  488. stringWithFormat:@"<FSTDeleteMutation key=%@ precondition=%@>", self.key, self.precondition];
  489. }
  490. - (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
  491. baseDocument:(nullable FSTMaybeDocument *)baseDoc
  492. localWriteTime:(FIRTimestamp *)localWriteTime
  493. mutationResult:(nullable FSTMutationResult *)mutationResult {
  494. if (mutationResult) {
  495. FSTAssert(!mutationResult.transformResults,
  496. @"Transform results received by FSTDeleteMutation.");
  497. }
  498. if (![self.precondition isValidForDocument:maybeDoc]) {
  499. return maybeDoc;
  500. }
  501. if (maybeDoc) {
  502. FSTAssert([maybeDoc.key isEqual:self.key], @"Can only delete a document with the same key");
  503. }
  504. return [FSTDeletedDocument documentWithKey:self.key version:[FSTSnapshotVersion noVersion]];
  505. }
  506. @end
  507. NS_ASSUME_NONNULL_END