FSTView.mm 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  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/Core/FSTView.h"
  17. #include <utility>
  18. #import "Firestore/Source/Core/FSTQuery.h"
  19. #import "Firestore/Source/Core/FSTViewSnapshot.h"
  20. #import "Firestore/Source/Model/FSTDocument.h"
  21. #import "Firestore/Source/Model/FSTDocumentSet.h"
  22. #import "Firestore/Source/Model/FSTFieldValue.h"
  23. #import "Firestore/Source/Remote/FSTRemoteEvent.h"
  24. #include "Firestore/core/src/firebase/firestore/model/document_key.h"
  25. #include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
  26. using firebase::firestore::model::DocumentKey;
  27. using firebase::firestore::model::DocumentKeySet;
  28. NS_ASSUME_NONNULL_BEGIN
  29. #pragma mark - FSTViewDocumentChanges
  30. /** The result of applying a set of doc changes to a view. */
  31. @interface FSTViewDocumentChanges ()
  32. - (instancetype)initWithDocumentSet:(FSTDocumentSet *)documentSet
  33. changeSet:(FSTDocumentViewChangeSet *)changeSet
  34. needsRefill:(BOOL)needsRefill
  35. mutatedKeys:(DocumentKeySet)mutatedKeys NS_DESIGNATED_INITIALIZER;
  36. @end
  37. @implementation FSTViewDocumentChanges {
  38. DocumentKeySet _mutatedKeys;
  39. }
  40. - (instancetype)initWithDocumentSet:(FSTDocumentSet *)documentSet
  41. changeSet:(FSTDocumentViewChangeSet *)changeSet
  42. needsRefill:(BOOL)needsRefill
  43. mutatedKeys:(DocumentKeySet)mutatedKeys {
  44. self = [super init];
  45. if (self) {
  46. _documentSet = documentSet;
  47. _changeSet = changeSet;
  48. _needsRefill = needsRefill;
  49. _mutatedKeys = std::move(mutatedKeys);
  50. }
  51. return self;
  52. }
  53. - (const DocumentKeySet &)mutatedKeys {
  54. return _mutatedKeys;
  55. }
  56. @end
  57. #pragma mark - FSTLimboDocumentChange
  58. @interface FSTLimboDocumentChange ()
  59. + (instancetype)changeWithType:(FSTLimboDocumentChangeType)type key:(DocumentKey)key;
  60. - (instancetype)initWithType:(FSTLimboDocumentChangeType)type
  61. key:(DocumentKey)key NS_DESIGNATED_INITIALIZER;
  62. @end
  63. @implementation FSTLimboDocumentChange {
  64. DocumentKey _key;
  65. }
  66. + (instancetype)changeWithType:(FSTLimboDocumentChangeType)type key:(DocumentKey)key {
  67. return [[FSTLimboDocumentChange alloc] initWithType:type key:std::move(key)];
  68. }
  69. - (instancetype)initWithType:(FSTLimboDocumentChangeType)type key:(DocumentKey)key {
  70. self = [super init];
  71. if (self) {
  72. _type = type;
  73. _key = std::move(key);
  74. }
  75. return self;
  76. }
  77. - (const DocumentKey &)key {
  78. return _key;
  79. }
  80. - (BOOL)isEqual:(id)other {
  81. if (self == other) {
  82. return YES;
  83. }
  84. if (![other isKindOfClass:[FSTLimboDocumentChange class]]) {
  85. return NO;
  86. }
  87. FSTLimboDocumentChange *otherChange = (FSTLimboDocumentChange *)other;
  88. return self.type == otherChange.type && [self.key isEqual:otherChange.key];
  89. }
  90. - (NSUInteger)hash {
  91. NSUInteger hash = self.type;
  92. hash = hash * 31u + [self.key hash];
  93. return hash;
  94. }
  95. @end
  96. #pragma mark - FSTViewChange
  97. @interface FSTViewChange ()
  98. + (FSTViewChange *)changeWithSnapshot:(nullable FSTViewSnapshot *)snapshot
  99. limboChanges:(NSArray<FSTLimboDocumentChange *> *)limboChanges;
  100. - (instancetype)initWithSnapshot:(nullable FSTViewSnapshot *)snapshot
  101. limboChanges:(NSArray<FSTLimboDocumentChange *> *)limboChanges
  102. NS_DESIGNATED_INITIALIZER;
  103. @end
  104. @implementation FSTViewChange
  105. + (FSTViewChange *)changeWithSnapshot:(nullable FSTViewSnapshot *)snapshot
  106. limboChanges:(NSArray<FSTLimboDocumentChange *> *)limboChanges {
  107. return [[self alloc] initWithSnapshot:snapshot limboChanges:limboChanges];
  108. }
  109. - (instancetype)initWithSnapshot:(nullable FSTViewSnapshot *)snapshot
  110. limboChanges:(NSArray<FSTLimboDocumentChange *> *)limboChanges {
  111. self = [super init];
  112. if (self) {
  113. _snapshot = snapshot;
  114. _limboChanges = limboChanges;
  115. }
  116. return self;
  117. }
  118. @end
  119. #pragma mark - FSTView
  120. static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChangeType c1,
  121. FSTDocumentViewChangeType c2);
  122. @interface FSTView ()
  123. @property(nonatomic, strong, readonly) FSTQuery *query;
  124. @property(nonatomic, assign) FSTSyncState syncState;
  125. /**
  126. * A flag whether the view is current with the backend. A view is considered current after it
  127. * has seen the current flag from the backend and did not lose consistency within the watch stream
  128. * (e.g. because of an existence filter mismatch).
  129. */
  130. @property(nonatomic, assign, getter=isCurrent) BOOL current;
  131. @property(nonatomic, strong) FSTDocumentSet *documentSet;
  132. @end
  133. @implementation FSTView {
  134. /** Documents included in the remote target. */
  135. DocumentKeySet _syncedDocuments;
  136. /** Documents in the view but not in the remote target */
  137. DocumentKeySet _limboDocuments;
  138. /** Document Keys that have local changes. */
  139. DocumentKeySet _mutatedKeys;
  140. }
  141. - (instancetype)initWithQuery:(FSTQuery *)query remoteDocuments:(DocumentKeySet)remoteDocuments {
  142. self = [super init];
  143. if (self) {
  144. _query = query;
  145. _documentSet = [FSTDocumentSet documentSetWithComparator:query.comparator];
  146. _syncedDocuments = std::move(remoteDocuments);
  147. }
  148. return self;
  149. }
  150. - (const DocumentKeySet &)syncedDocuments {
  151. return _syncedDocuments;
  152. }
  153. - (FSTViewDocumentChanges *)computeChangesWithDocuments:(FSTMaybeDocumentDictionary *)docChanges {
  154. return [self computeChangesWithDocuments:docChanges previousChanges:nil];
  155. }
  156. - (FSTViewDocumentChanges *)computeChangesWithDocuments:(FSTMaybeDocumentDictionary *)docChanges
  157. previousChanges:
  158. (nullable FSTViewDocumentChanges *)previousChanges {
  159. FSTDocumentViewChangeSet *changeSet =
  160. previousChanges ? previousChanges.changeSet : [FSTDocumentViewChangeSet changeSet];
  161. FSTDocumentSet *oldDocumentSet = previousChanges ? previousChanges.documentSet : self.documentSet;
  162. __block DocumentKeySet newMutatedKeys =
  163. previousChanges ? previousChanges.mutatedKeys : _mutatedKeys;
  164. __block FSTDocumentSet *newDocumentSet = oldDocumentSet;
  165. __block BOOL needsRefill = NO;
  166. // Track the last doc in a (full) limit. This is necessary, because some update (a delete, or an
  167. // update moving a doc past the old limit) might mean there is some other document in the local
  168. // cache that either should come (1) between the old last limit doc and the new last document,
  169. // in the case of updates, or (2) after the new last document, in the case of deletes. So we
  170. // keep this doc at the old limit to compare the updates to.
  171. //
  172. // Note that this should never get used in a refill (when previousChanges is set), because there
  173. // will only be adds -- no deletes or updates.
  174. FSTDocument *_Nullable lastDocInLimit =
  175. (self.query.limit && oldDocumentSet.count == self.query.limit) ? oldDocumentSet.lastDocument
  176. : nil;
  177. [docChanges enumerateKeysAndObjectsUsingBlock:^(FSTDocumentKey *key,
  178. FSTMaybeDocument *maybeNewDoc, BOOL *stop) {
  179. FSTDocument *_Nullable oldDoc = [oldDocumentSet documentForKey:key];
  180. FSTDocument *_Nullable newDoc = nil;
  181. if ([maybeNewDoc isKindOfClass:[FSTDocument class]]) {
  182. newDoc = (FSTDocument *)maybeNewDoc;
  183. }
  184. if (newDoc) {
  185. HARD_ASSERT([key isEqual:newDoc.key], "Mismatching key in document changes: %s != %s", key,
  186. newDoc.key.ToString());
  187. if (![self.query matchesDocument:newDoc]) {
  188. newDoc = nil;
  189. }
  190. }
  191. if (newDoc) {
  192. newDocumentSet = [newDocumentSet documentSetByAddingDocument:newDoc];
  193. if (newDoc.hasLocalMutations) {
  194. newMutatedKeys = newMutatedKeys.insert(key);
  195. } else {
  196. newMutatedKeys = newMutatedKeys.erase(key);
  197. }
  198. } else {
  199. newDocumentSet = [newDocumentSet documentSetByRemovingKey:key];
  200. newMutatedKeys = newMutatedKeys.erase(key);
  201. }
  202. // Calculate change
  203. if (oldDoc && newDoc) {
  204. BOOL docsEqual = [oldDoc.data isEqual:newDoc.data];
  205. if (!docsEqual || oldDoc.hasLocalMutations != newDoc.hasLocalMutations) {
  206. // only report a change if document actually changed.
  207. if (docsEqual) {
  208. [changeSet addChange:[FSTDocumentViewChange
  209. changeWithDocument:newDoc
  210. type:FSTDocumentViewChangeTypeMetadata]];
  211. } else {
  212. [changeSet addChange:[FSTDocumentViewChange
  213. changeWithDocument:newDoc
  214. type:FSTDocumentViewChangeTypeModified]];
  215. }
  216. if (lastDocInLimit && self.query.comparator(newDoc, lastDocInLimit) > 0) {
  217. // This doc moved from inside the limit to after the limit. That means there may be some
  218. // doc in the local cache that's actually less than this one.
  219. needsRefill = YES;
  220. }
  221. }
  222. } else if (!oldDoc && newDoc) {
  223. [changeSet
  224. addChange:[FSTDocumentViewChange changeWithDocument:newDoc
  225. type:FSTDocumentViewChangeTypeAdded]];
  226. } else if (oldDoc && !newDoc) {
  227. [changeSet
  228. addChange:[FSTDocumentViewChange changeWithDocument:oldDoc
  229. type:FSTDocumentViewChangeTypeRemoved]];
  230. if (lastDocInLimit) {
  231. // A doc was removed from a full limit query. We'll need to re-query from the local cache
  232. // to see if we know about some other doc that should be in the results.
  233. needsRefill = YES;
  234. }
  235. }
  236. }];
  237. if (self.query.limit) {
  238. // TODO(klimt): Make DocumentSet size be constant time.
  239. while (newDocumentSet.count > self.query.limit) {
  240. FSTDocument *oldDoc = [newDocumentSet lastDocument];
  241. newDocumentSet = [newDocumentSet documentSetByRemovingKey:oldDoc.key];
  242. [changeSet
  243. addChange:[FSTDocumentViewChange changeWithDocument:oldDoc
  244. type:FSTDocumentViewChangeTypeRemoved]];
  245. }
  246. }
  247. HARD_ASSERT(!needsRefill || !previousChanges,
  248. "View was refilled using docs that themselves needed refilling.");
  249. return [[FSTViewDocumentChanges alloc] initWithDocumentSet:newDocumentSet
  250. changeSet:changeSet
  251. needsRefill:needsRefill
  252. mutatedKeys:newMutatedKeys];
  253. }
  254. - (FSTViewChange *)applyChangesToDocuments:(FSTViewDocumentChanges *)docChanges {
  255. return [self applyChangesToDocuments:docChanges targetChange:nil];
  256. }
  257. - (FSTViewChange *)applyChangesToDocuments:(FSTViewDocumentChanges *)docChanges
  258. targetChange:(nullable FSTTargetChange *)targetChange {
  259. HARD_ASSERT(!docChanges.needsRefill, "Cannot apply changes that need a refill");
  260. FSTDocumentSet *oldDocuments = self.documentSet;
  261. self.documentSet = docChanges.documentSet;
  262. _mutatedKeys = docChanges.mutatedKeys;
  263. // Sort changes based on type and query comparator.
  264. NSArray<FSTDocumentViewChange *> *changes = [docChanges.changeSet changes];
  265. changes = [changes sortedArrayUsingComparator:^NSComparisonResult(FSTDocumentViewChange *c1,
  266. FSTDocumentViewChange *c2) {
  267. NSComparisonResult typeComparison = FSTCompareDocumentViewChangeTypes(c1.type, c2.type);
  268. if (typeComparison != NSOrderedSame) {
  269. return typeComparison;
  270. }
  271. return self.query.comparator(c1.document, c2.document);
  272. }];
  273. [self applyTargetChange:targetChange];
  274. NSArray<FSTLimboDocumentChange *> *limboChanges = [self updateLimboDocuments];
  275. BOOL synced = _limboDocuments.empty() && self.isCurrent;
  276. FSTSyncState newSyncState = synced ? FSTSyncStateSynced : FSTSyncStateLocal;
  277. BOOL syncStateChanged = newSyncState != self.syncState;
  278. self.syncState = newSyncState;
  279. if (changes.count == 0 && !syncStateChanged) {
  280. // No changes.
  281. return [FSTViewChange changeWithSnapshot:nil limboChanges:limboChanges];
  282. } else {
  283. FSTViewSnapshot *snapshot =
  284. [[FSTViewSnapshot alloc] initWithQuery:self.query
  285. documents:docChanges.documentSet
  286. oldDocuments:oldDocuments
  287. documentChanges:changes
  288. fromCache:newSyncState == FSTSyncStateLocal
  289. hasPendingWrites:!docChanges.mutatedKeys.empty()
  290. syncStateChanged:syncStateChanged];
  291. return [FSTViewChange changeWithSnapshot:snapshot limboChanges:limboChanges];
  292. }
  293. }
  294. - (FSTViewChange *)applyChangedOnlineState:(FSTOnlineState)onlineState {
  295. if (self.isCurrent && onlineState == FSTOnlineStateOffline) {
  296. // If we're offline, set `current` to NO and then call applyChanges to refresh our syncState
  297. // and generate an FSTViewChange as appropriate. We are guaranteed to get a new FSTTargetChange
  298. // that sets `current` back to YES once the client is back online.
  299. self.current = NO;
  300. return
  301. [self applyChangesToDocuments:[[FSTViewDocumentChanges alloc]
  302. initWithDocumentSet:self.documentSet
  303. changeSet:[FSTDocumentViewChangeSet changeSet]
  304. needsRefill:NO
  305. mutatedKeys:_mutatedKeys]];
  306. } else {
  307. // No effect, just return a no-op FSTViewChange.
  308. return [[FSTViewChange alloc] initWithSnapshot:nil limboChanges:@[]];
  309. }
  310. }
  311. #pragma mark - Private methods
  312. /** Returns whether the doc for the given key should be in limbo. */
  313. - (BOOL)shouldBeLimboDocumentKey:(const DocumentKey &)key {
  314. // If the remote end says it's part of this query, it's not in limbo.
  315. if (_syncedDocuments.contains(key)) {
  316. return NO;
  317. }
  318. // The local store doesn't think it's a result, so it shouldn't be in limbo.
  319. if (![self.documentSet containsKey:key]) {
  320. return NO;
  321. }
  322. // If there are local changes to the doc, they might explain why the server doesn't know that it's
  323. // part of the query. So don't put it in limbo.
  324. // TODO(klimt): Ideally, we would only consider changes that might actually affect this specific
  325. // query.
  326. if ([self.documentSet documentForKey:key].hasLocalMutations) {
  327. return NO;
  328. }
  329. // Everything else is in limbo.
  330. return YES;
  331. }
  332. /**
  333. * Updates syncedDocuments and current based on the given change.
  334. */
  335. - (void)applyTargetChange:(nullable FSTTargetChange *)targetChange {
  336. if (targetChange) {
  337. FSTTargetMapping *targetMapping = targetChange.mapping;
  338. if ([targetMapping isKindOfClass:[FSTResetMapping class]]) {
  339. _syncedDocuments = ((FSTResetMapping *)targetMapping).documents;
  340. } else if ([targetMapping isKindOfClass:[FSTUpdateMapping class]]) {
  341. for (const DocumentKey &key : ((FSTUpdateMapping *)targetMapping).addedDocuments) {
  342. _syncedDocuments = _syncedDocuments.insert(key);
  343. }
  344. for (const DocumentKey &key : ((FSTUpdateMapping *)targetMapping).removedDocuments) {
  345. _syncedDocuments = _syncedDocuments.erase(key);
  346. }
  347. }
  348. switch (targetChange.currentStatusUpdate) {
  349. case FSTCurrentStatusUpdateMarkCurrent:
  350. self.current = YES;
  351. break;
  352. case FSTCurrentStatusUpdateMarkNotCurrent:
  353. self.current = NO;
  354. break;
  355. case FSTCurrentStatusUpdateNone:
  356. break;
  357. }
  358. }
  359. }
  360. /** Updates limboDocuments and returns any changes as FSTLimboDocumentChanges. */
  361. - (NSArray<FSTLimboDocumentChange *> *)updateLimboDocuments {
  362. // We can only determine limbo documents when we're in-sync with the server.
  363. if (!self.isCurrent) {
  364. return @[];
  365. }
  366. // TODO(klimt): Do this incrementally so that it's not quadratic when updating many documents.
  367. DocumentKeySet oldLimboDocuments = std::move(_limboDocuments);
  368. _limboDocuments = DocumentKeySet{};
  369. for (FSTDocument *doc in self.documentSet.documentEnumerator) {
  370. if ([self shouldBeLimboDocumentKey:doc.key]) {
  371. _limboDocuments = _limboDocuments.insert(doc.key);
  372. }
  373. }
  374. // Diff the new limbo docs with the old limbo docs.
  375. NSMutableArray<FSTLimboDocumentChange *> *changes =
  376. [NSMutableArray arrayWithCapacity:(oldLimboDocuments.size() + _limboDocuments.size())];
  377. for (const DocumentKey &key : oldLimboDocuments) {
  378. if (!_limboDocuments.contains(key)) {
  379. [changes addObject:[FSTLimboDocumentChange changeWithType:FSTLimboDocumentChangeTypeRemoved
  380. key:key]];
  381. }
  382. }
  383. for (const DocumentKey &key : _limboDocuments) {
  384. if (!oldLimboDocuments.contains(key)) {
  385. [changes addObject:[FSTLimboDocumentChange changeWithType:FSTLimboDocumentChangeTypeAdded
  386. key:key]];
  387. }
  388. }
  389. return changes;
  390. }
  391. @end
  392. static inline int DocumentViewChangeTypePosition(FSTDocumentViewChangeType changeType) {
  393. switch (changeType) {
  394. case FSTDocumentViewChangeTypeRemoved:
  395. return 0;
  396. case FSTDocumentViewChangeTypeAdded:
  397. return 1;
  398. case FSTDocumentViewChangeTypeModified:
  399. return 2;
  400. case FSTDocumentViewChangeTypeMetadata:
  401. // A metadata change is converted to a modified change at the public API layer. Since we sort
  402. // by document key and then change type, metadata and modified changes must be sorted
  403. // equivalently.
  404. return 2;
  405. default:
  406. HARD_FAIL("Unknown FSTDocumentViewChangeType %s", changeType);
  407. }
  408. }
  409. static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChangeType c1,
  410. FSTDocumentViewChangeType c2) {
  411. int pos1 = DocumentViewChangeTypePosition(c1);
  412. int pos2 = DocumentViewChangeTypePosition(c2);
  413. if (pos1 == pos2) {
  414. return NSOrderedSame;
  415. } else if (pos1 < pos2) {
  416. return NSOrderedAscending;
  417. } else {
  418. return NSOrderedDescending;
  419. }
  420. }
  421. NS_ASSUME_NONNULL_END