FSTView.mm 19 KB

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