FSTView.mm 20 KB

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