FSTMutation.mm 19 KB

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