FSTMutation.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  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 || ![[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. - (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
  197. localWriteTime:(FSTTimestamp *)localWriteTime
  198. mutationResult:(FSTMutationResult *_Nullable)mutationResult {
  199. @throw FSTAbstractMethodException(); // NOLINT
  200. }
  201. - (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
  202. localWriteTime:(FSTTimestamp *)localWriteTime {
  203. return [self applyTo:maybeDoc localWriteTime:localWriteTime mutationResult:nil];
  204. }
  205. @end
  206. #pragma mark - FSTSetMutation
  207. @implementation FSTSetMutation
  208. - (instancetype)initWithKey:(FSTDocumentKey *)key
  209. value:(FSTObjectValue *)value
  210. precondition:(FSTPrecondition *)precondition {
  211. if (self = [super initWithKey:key precondition:precondition]) {
  212. _value = value;
  213. }
  214. return self;
  215. }
  216. - (NSString *)description {
  217. return [NSString stringWithFormat:@"<FSTSetMutation key=%@ value=%@ precondition=%@>", self.key,
  218. self.value, self.precondition];
  219. }
  220. - (BOOL)isEqual:(id)other {
  221. if (other == self) {
  222. return YES;
  223. }
  224. if (![other isKindOfClass:[FSTSetMutation class]]) {
  225. return NO;
  226. }
  227. FSTSetMutation *otherMutation = (FSTSetMutation *)other;
  228. return [self.key isEqual:otherMutation.key] && [self.value isEqual:otherMutation.value] &&
  229. [self.precondition isEqual:otherMutation.precondition];
  230. }
  231. - (NSUInteger)hash {
  232. NSUInteger result = [self.key hash];
  233. result = 31 * result + [self.precondition hash];
  234. result = 31 * result + [self.value hash];
  235. return result;
  236. }
  237. - (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
  238. localWriteTime:(FSTTimestamp *)localWriteTime
  239. mutationResult:(FSTMutationResult *_Nullable)mutationResult {
  240. if (mutationResult) {
  241. FSTAssert(!mutationResult.transformResults, @"Transform results received by FSTSetMutation.");
  242. }
  243. if (![self.precondition isValidForDocument:maybeDoc]) {
  244. return maybeDoc;
  245. }
  246. BOOL hasLocalMutations = (mutationResult == nil);
  247. if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) {
  248. // If the document didn't exist before, create it.
  249. return [FSTDocument documentWithData:self.value
  250. key:self.key
  251. version:[FSTSnapshotVersion noVersion]
  252. hasLocalMutations:hasLocalMutations];
  253. }
  254. FSTAssert([maybeDoc isMemberOfClass:[FSTDocument class]], @"Unknown MaybeDocument type %@",
  255. [maybeDoc class]);
  256. FSTDocument *doc = (FSTDocument *)maybeDoc;
  257. FSTAssert([doc.key isEqual:self.key], @"Can only set a document with the same key");
  258. return [FSTDocument documentWithData:self.value
  259. key:doc.key
  260. version:doc.version
  261. hasLocalMutations:hasLocalMutations];
  262. }
  263. @end
  264. #pragma mark - FSTPatchMutation
  265. @implementation FSTPatchMutation
  266. - (instancetype)initWithKey:(FSTDocumentKey *)key
  267. fieldMask:(FSTFieldMask *)fieldMask
  268. value:(FSTObjectValue *)value
  269. precondition:(FSTPrecondition *)precondition {
  270. self = [super initWithKey:key precondition:precondition];
  271. if (self) {
  272. _fieldMask = fieldMask;
  273. _value = value;
  274. }
  275. return self;
  276. }
  277. - (BOOL)isEqual:(id)other {
  278. if (other == self) {
  279. return YES;
  280. }
  281. if (![other isKindOfClass:[FSTPatchMutation class]]) {
  282. return NO;
  283. }
  284. FSTPatchMutation *otherMutation = (FSTPatchMutation *)other;
  285. return [self.key isEqual:otherMutation.key] && [self.fieldMask isEqual:otherMutation.fieldMask] &&
  286. [self.value isEqual:otherMutation.value] &&
  287. [self.precondition isEqual:otherMutation.precondition];
  288. }
  289. - (NSUInteger)hash {
  290. NSUInteger result = [self.key hash];
  291. result = 31 * result + [self.precondition hash];
  292. result = 31 * result + [self.fieldMask hash];
  293. result = 31 * result + [self.value hash];
  294. return result;
  295. }
  296. - (NSString *)description {
  297. return [NSString stringWithFormat:@"<FSTPatchMutation key=%@ mask=%@ value=%@ precondition=%@>",
  298. self.key, self.fieldMask, self.value, self.precondition];
  299. }
  300. - (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
  301. localWriteTime:(FSTTimestamp *)localWriteTime
  302. mutationResult:(FSTMutationResult *_Nullable)mutationResult {
  303. if (mutationResult) {
  304. FSTAssert(!mutationResult.transformResults, @"Transform results received by FSTPatchMutation.");
  305. }
  306. if (![self.precondition isValidForDocument:maybeDoc]) {
  307. return maybeDoc;
  308. }
  309. BOOL hasLocalMutations = (mutationResult == nil);
  310. if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) {
  311. // Precondition applied, so create the document if necessary
  312. FSTDocumentKey *key = maybeDoc ? maybeDoc.key : self.key;
  313. FSTSnapshotVersion *version = maybeDoc ? maybeDoc.version : [FSTSnapshotVersion noVersion];
  314. maybeDoc = [FSTDocument documentWithData:[FSTObjectValue objectValue]
  315. key:key
  316. version:version
  317. hasLocalMutations:hasLocalMutations];
  318. }
  319. FSTAssert([maybeDoc isMemberOfClass:[FSTDocument class]], @"Unknown MaybeDocument type %@",
  320. [maybeDoc class]);
  321. FSTDocument *doc = (FSTDocument *)maybeDoc;
  322. FSTAssert([doc.key isEqual:self.key], @"Can only patch a document with the same key");
  323. FSTObjectValue *newData = [self patchObjectValue:doc.data];
  324. return [FSTDocument documentWithData:newData
  325. key:doc.key
  326. version:doc.version
  327. hasLocalMutations:hasLocalMutations];
  328. }
  329. - (FSTObjectValue *)patchObjectValue:(FSTObjectValue *)objectValue {
  330. FSTObjectValue *result = objectValue;
  331. for (FSTFieldPath *fieldPath in self.fieldMask.fields) {
  332. FSTFieldValue *newValue = [self.value valueForPath:fieldPath];
  333. if (newValue) {
  334. result = [result objectBySettingValue:newValue forPath:fieldPath];
  335. } else {
  336. result = [result objectByDeletingPath:fieldPath];
  337. }
  338. }
  339. return result;
  340. }
  341. @end
  342. @implementation FSTTransformMutation
  343. - (instancetype)initWithKey:(FSTDocumentKey *)key
  344. fieldTransforms:(NSArray<FSTFieldTransform *> *)fieldTransforms {
  345. // NOTE: We set a precondition of exists: true as a safety-check, since we always combine
  346. // FSTTransformMutations with a FSTSetMutation or FSTPatchMutation which (if successful) should
  347. // end up with an existing document.
  348. if (self = [super initWithKey:key precondition:[FSTPrecondition preconditionWithExists:YES]]) {
  349. _fieldTransforms = fieldTransforms;
  350. }
  351. return self;
  352. }
  353. - (BOOL)isEqual:(id)other {
  354. if (other == self) {
  355. return YES;
  356. }
  357. if (![other isKindOfClass:[FSTTransformMutation class]]) {
  358. return NO;
  359. }
  360. FSTTransformMutation *otherMutation = (FSTTransformMutation *)other;
  361. return [self.key isEqual:otherMutation.key] &&
  362. [self.fieldTransforms isEqual:otherMutation.fieldTransforms] &&
  363. [self.precondition isEqual:otherMutation.precondition];
  364. }
  365. - (NSUInteger)hash {
  366. NSUInteger result = [self.key hash];
  367. result = 31 * result + [self.precondition hash];
  368. result = 31 * result + [self.fieldTransforms hash];
  369. return result;
  370. }
  371. - (NSString *)description {
  372. return [NSString stringWithFormat:@"<FSTTransformMutation key=%@ transforms=%@ precondition=%@>",
  373. self.key, self.fieldTransforms, self.precondition];
  374. }
  375. - (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
  376. localWriteTime:(FSTTimestamp *)localWriteTime
  377. mutationResult:(FSTMutationResult *_Nullable)mutationResult {
  378. if (mutationResult) {
  379. FSTAssert(mutationResult.transformResults,
  380. @"Transform results missing for FSTTransformMutation.");
  381. }
  382. if (![self.precondition isValidForDocument:maybeDoc]) {
  383. return maybeDoc;
  384. }
  385. // We only support transforms with precondition exists, so we can only apply it to an existing
  386. // document
  387. FSTAssert([maybeDoc isMemberOfClass:[FSTDocument class]], @"Unknown MaybeDocument type %@",
  388. [maybeDoc class]);
  389. FSTDocument *doc = (FSTDocument *)maybeDoc;
  390. FSTAssert([doc.key isEqual:self.key], @"Can only patch a document with the same key");
  391. BOOL hasLocalMutations = (mutationResult == nil);
  392. NSArray<FSTFieldValue *> *transformResults =
  393. mutationResult ? mutationResult.transformResults
  394. : [self localTransformResultsWithWriteTime:localWriteTime];
  395. FSTObjectValue *newData = [self transformObject:doc.data transformResults:transformResults];
  396. return [FSTDocument documentWithData:newData
  397. key:doc.key
  398. version:doc.version
  399. hasLocalMutations:hasLocalMutations];
  400. }
  401. /**
  402. * Creates an array of "transform results" (a transform result is a field value representing the
  403. * result of applying a transform) for use when applying an FSTTransformMutation locally.
  404. *
  405. * @param localWriteTime The local time of the transform mutation (used to generate
  406. * FSTServerTimestampValues).
  407. * @return The transform results array.
  408. */
  409. - (NSArray<FSTFieldValue *> *)localTransformResultsWithWriteTime:(FSTTimestamp *)localWriteTime {
  410. NSMutableArray<FSTFieldValue *> *transformResults = [NSMutableArray array];
  411. for (FSTFieldTransform *fieldTransform in self.fieldTransforms) {
  412. if ([fieldTransform.transform isKindOfClass:[FSTServerTimestampTransform class]]) {
  413. [transformResults addObject:[FSTServerTimestampValue
  414. serverTimestampValueWithLocalWriteTime:localWriteTime]];
  415. } else {
  416. FSTFail(@"Encountered unknown transform: %@", fieldTransform);
  417. }
  418. }
  419. return transformResults;
  420. }
  421. - (FSTObjectValue *)transformObject:(FSTObjectValue *)objectValue
  422. transformResults:(NSArray<FSTFieldValue *> *)transformResults {
  423. FSTAssert(transformResults.count == self.fieldTransforms.count,
  424. @"Transform results length mismatch.");
  425. for (NSUInteger i = 0; i < self.fieldTransforms.count; i++) {
  426. FSTFieldTransform *fieldTransform = self.fieldTransforms[i];
  427. id<FSTTransformOperation> transform = fieldTransform.transform;
  428. FSTFieldPath *fieldPath = fieldTransform.path;
  429. if ([transform isKindOfClass:[FSTServerTimestampTransform class]]) {
  430. objectValue = [objectValue objectBySettingValue:transformResults[i] forPath:fieldPath];
  431. } else {
  432. FSTFail(@"Encountered unknown transform: %@", transform);
  433. }
  434. }
  435. return objectValue;
  436. }
  437. @end
  438. #pragma mark - FSTDeleteMutation
  439. @implementation FSTDeleteMutation
  440. - (BOOL)isEqual:(id)other {
  441. if (other == self) {
  442. return YES;
  443. }
  444. if (![other isKindOfClass:[FSTDeleteMutation class]]) {
  445. return NO;
  446. }
  447. FSTDeleteMutation *otherMutation = (FSTDeleteMutation *)other;
  448. return [self.key isEqual:otherMutation.key] &&
  449. [self.precondition isEqual:otherMutation.precondition];
  450. }
  451. - (NSUInteger)hash {
  452. NSUInteger result = [self.key hash];
  453. result = 31 * result + [self.precondition hash];
  454. return result;
  455. }
  456. - (NSString *)description {
  457. return [NSString
  458. stringWithFormat:@"<FSTDeleteMutation key=%@ precondition=%@>", self.key, self.precondition];
  459. }
  460. - (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
  461. localWriteTime:(FSTTimestamp *)localWriteTime
  462. mutationResult:(FSTMutationResult *_Nullable)mutationResult {
  463. if (mutationResult) {
  464. FSTAssert(!mutationResult.transformResults,
  465. @"Transform results received by FSTDeleteMutation.");
  466. }
  467. if (![self.precondition isValidForDocument:maybeDoc]) {
  468. return maybeDoc;
  469. }
  470. if (maybeDoc) {
  471. FSTAssert([maybeDoc.key isEqual:self.key], @"Can only delete a document with the same key");
  472. }
  473. return [FSTDeletedDocument documentWithKey:self.key version:[FSTSnapshotVersion noVersion]];
  474. }
  475. @end
  476. NS_ASSUME_NONNULL_END