FSTSyncEngine.mm 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  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/FSTSyncEngine.h"
  17. #include <map>
  18. #include <memory>
  19. #include <set>
  20. #include <unordered_map>
  21. #include <utility>
  22. #import "FIRFirestoreErrors.h"
  23. #import "Firestore/Source/Core/FSTQuery.h"
  24. #import "Firestore/Source/Core/FSTView.h"
  25. #import "Firestore/Source/Core/FSTViewSnapshot.h"
  26. #import "Firestore/Source/Local/FSTLocalStore.h"
  27. #import "Firestore/Source/Local/FSTLocalViewChanges.h"
  28. #import "Firestore/Source/Local/FSTLocalWriteResult.h"
  29. #import "Firestore/Source/Local/FSTQueryData.h"
  30. #import "Firestore/Source/Model/FSTDocument.h"
  31. #import "Firestore/Source/Model/FSTDocumentSet.h"
  32. #import "Firestore/Source/Model/FSTMutationBatch.h"
  33. #include "Firestore/core/src/firebase/firestore/auth/user.h"
  34. #include "Firestore/core/src/firebase/firestore/core/target_id_generator.h"
  35. #include "Firestore/core/src/firebase/firestore/core/transaction.h"
  36. #include "Firestore/core/src/firebase/firestore/local/reference_set.h"
  37. #include "Firestore/core/src/firebase/firestore/model/document_key.h"
  38. #include "Firestore/core/src/firebase/firestore/model/document_map.h"
  39. #include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
  40. #include "Firestore/core/src/firebase/firestore/remote/remote_event.h"
  41. #include "Firestore/core/src/firebase/firestore/util/error_apple.h"
  42. #include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
  43. #include "Firestore/core/src/firebase/firestore/util/log.h"
  44. #include "Firestore/core/src/firebase/firestore/util/status.h"
  45. #include "absl/types/optional.h"
  46. using firebase::firestore::auth::HashUser;
  47. using firebase::firestore::auth::User;
  48. using firebase::firestore::core::TargetIdGenerator;
  49. using firebase::firestore::core::Transaction;
  50. using firebase::firestore::local::ReferenceSet;
  51. using firebase::firestore::model::BatchId;
  52. using firebase::firestore::model::DocumentKey;
  53. using firebase::firestore::model::DocumentKeySet;
  54. using firebase::firestore::model::DocumentMap;
  55. using firebase::firestore::model::MaybeDocumentMap;
  56. using firebase::firestore::model::ListenSequenceNumber;
  57. using firebase::firestore::model::OnlineState;
  58. using firebase::firestore::model::SnapshotVersion;
  59. using firebase::firestore::model::TargetId;
  60. using firebase::firestore::remote::RemoteEvent;
  61. using firebase::firestore::remote::RemoteStore;
  62. using firebase::firestore::remote::TargetChange;
  63. using firebase::firestore::util::AsyncQueue;
  64. using firebase::firestore::util::MakeNSError;
  65. using firebase::firestore::util::Status;
  66. NS_ASSUME_NONNULL_BEGIN
  67. // Limbo documents don't use persistence, and are eagerly GC'd. So, listens for them don't need
  68. // real sequence numbers.
  69. static const ListenSequenceNumber kIrrelevantSequenceNumber = -1;
  70. #pragma mark - FSTQueryView
  71. /**
  72. * FSTQueryView contains all of the info that FSTSyncEngine needs to track for a particular
  73. * query and view.
  74. */
  75. @interface FSTQueryView : NSObject
  76. - (instancetype)initWithQuery:(FSTQuery *)query
  77. targetID:(TargetId)targetID
  78. resumeToken:(NSData *)resumeToken
  79. view:(FSTView *)view NS_DESIGNATED_INITIALIZER;
  80. - (instancetype)init NS_UNAVAILABLE;
  81. /** The query itself. */
  82. @property(nonatomic, strong, readonly) FSTQuery *query;
  83. /** The targetID created by the client that is used in the watch stream to identify this query. */
  84. @property(nonatomic, assign, readonly) TargetId targetID;
  85. /**
  86. * An identifier from the datastore backend that indicates the last state of the results that
  87. * was received. This can be used to indicate where to continue receiving new doc changes for the
  88. * query.
  89. */
  90. @property(nonatomic, copy, readonly) NSData *resumeToken;
  91. /**
  92. * The view is responsible for computing the final merged truth of what docs are in the query.
  93. * It gets notified of local and remote changes, and applies the query filters and limits to
  94. * determine the most correct possible results.
  95. */
  96. @property(nonatomic, strong, readonly) FSTView *view;
  97. @end
  98. @implementation FSTQueryView
  99. - (instancetype)initWithQuery:(FSTQuery *)query
  100. targetID:(TargetId)targetID
  101. resumeToken:(NSData *)resumeToken
  102. view:(FSTView *)view {
  103. if (self = [super init]) {
  104. _query = query;
  105. _targetID = targetID;
  106. _resumeToken = resumeToken;
  107. _view = view;
  108. }
  109. return self;
  110. }
  111. @end
  112. #pragma mark - LimboResolution
  113. /** Tracks a limbo resolution. */
  114. class LimboResolution {
  115. public:
  116. LimboResolution() {
  117. }
  118. explicit LimboResolution(const DocumentKey &key) : key{key} {
  119. }
  120. DocumentKey key;
  121. /**
  122. * Set to true once we've received a document. This is used in remoteKeysForTarget and
  123. * ultimately used by `WatchChangeAggregator` to decide whether it needs to manufacture a delete
  124. * event for the target once the target is CURRENT.
  125. */
  126. bool document_received = false;
  127. };
  128. #pragma mark - FSTSyncEngine
  129. @interface FSTSyncEngine ()
  130. /** The local store, used to persist mutations and cached documents. */
  131. @property(nonatomic, strong, readonly) FSTLocalStore *localStore;
  132. /** FSTQueryViews for all active queries, indexed by query. */
  133. @property(nonatomic, strong, readonly)
  134. NSMutableDictionary<FSTQuery *, FSTQueryView *> *queryViewsByQuery;
  135. @end
  136. @implementation FSTSyncEngine {
  137. /** The remote store for sending writes, watches, etc. to the backend. */
  138. RemoteStore *_remoteStore;
  139. /** Used for creating the TargetId for the listens used to resolve limbo documents. */
  140. TargetIdGenerator _targetIdGenerator;
  141. /** Stores user completion blocks, indexed by user and BatchId. */
  142. std::unordered_map<User, NSMutableDictionary<NSNumber *, FSTVoidErrorBlock> *, HashUser>
  143. _mutationCompletionBlocks;
  144. /** FSTQueryViews for all active queries, indexed by target ID. */
  145. std::unordered_map<TargetId, FSTQueryView *> _queryViewsByTarget;
  146. /**
  147. * When a document is in limbo, we create a special listen to resolve it. This maps the
  148. * DocumentKey of each limbo document to the TargetId of the listen resolving it.
  149. */
  150. std::map<DocumentKey, TargetId> _limboTargetsByKey;
  151. /**
  152. * Basically the inverse of limboTargetsByKey, a map of target ID to a LimboResolution (which
  153. * includes the DocumentKey as well as whether we've received a document for the target).
  154. */
  155. std::map<TargetId, LimboResolution> _limboResolutionsByTarget;
  156. User _currentUser;
  157. /** Used to track any documents that are currently in limbo. */
  158. ReferenceSet _limboDocumentRefs;
  159. }
  160. - (instancetype)initWithLocalStore:(FSTLocalStore *)localStore
  161. remoteStore:(RemoteStore *)remoteStore
  162. initialUser:(const User &)initialUser {
  163. if (self = [super init]) {
  164. _localStore = localStore;
  165. _remoteStore = remoteStore;
  166. _queryViewsByQuery = [NSMutableDictionary dictionary];
  167. _targetIdGenerator = TargetIdGenerator::SyncEngineTargetIdGenerator();
  168. _currentUser = initialUser;
  169. }
  170. return self;
  171. }
  172. - (TargetId)listenToQuery:(FSTQuery *)query {
  173. [self assertDelegateExistsForSelector:_cmd];
  174. HARD_ASSERT(self.queryViewsByQuery[query] == nil, "We already listen to query: %s", query);
  175. FSTQueryData *queryData = [self.localStore allocateQuery:query];
  176. FSTViewSnapshot *viewSnapshot = [self initializeViewAndComputeSnapshotForQueryData:queryData];
  177. [self.syncEngineDelegate handleViewSnapshots:@[ viewSnapshot ]];
  178. _remoteStore->Listen(queryData);
  179. return queryData.targetID;
  180. }
  181. - (FSTViewSnapshot *)initializeViewAndComputeSnapshotForQueryData:(FSTQueryData *)queryData {
  182. DocumentMap docs = [self.localStore executeQuery:queryData.query];
  183. DocumentKeySet remoteKeys = [self.localStore remoteDocumentKeysForTarget:queryData.targetID];
  184. FSTView *view = [[FSTView alloc] initWithQuery:queryData.query
  185. remoteDocuments:std::move(remoteKeys)];
  186. FSTViewDocumentChanges *viewDocChanges = [view computeChangesWithDocuments:docs.underlying_map()];
  187. FSTViewChange *viewChange = [view applyChangesToDocuments:viewDocChanges];
  188. HARD_ASSERT(viewChange.limboChanges.count == 0,
  189. "View returned limbo docs before target ack from the server.");
  190. FSTQueryView *queryView = [[FSTQueryView alloc] initWithQuery:queryData.query
  191. targetID:queryData.targetID
  192. resumeToken:queryData.resumeToken
  193. view:view];
  194. self.queryViewsByQuery[queryData.query] = queryView;
  195. _queryViewsByTarget[queryData.targetID] = queryView;
  196. return viewChange.snapshot;
  197. }
  198. - (void)stopListeningToQuery:(FSTQuery *)query {
  199. [self assertDelegateExistsForSelector:_cmd];
  200. FSTQueryView *queryView = self.queryViewsByQuery[query];
  201. HARD_ASSERT(queryView, "Trying to stop listening to a query not found");
  202. [self.localStore releaseQuery:query];
  203. _remoteStore->StopListening(queryView.targetID);
  204. [self removeAndCleanupQuery:queryView];
  205. }
  206. - (void)writeMutations:(std::vector<FSTMutation *> &&)mutations
  207. completion:(FSTVoidErrorBlock)completion {
  208. [self assertDelegateExistsForSelector:_cmd];
  209. FSTLocalWriteResult *result = [self.localStore locallyWriteMutations:std::move(mutations)];
  210. [self addMutationCompletionBlock:completion batchID:result.batchID];
  211. [self emitNewSnapshotsAndNotifyLocalStoreWithChanges:result.changes remoteEvent:absl::nullopt];
  212. _remoteStore->FillWritePipeline();
  213. }
  214. - (void)addMutationCompletionBlock:(FSTVoidErrorBlock)completion batchID:(BatchId)batchID {
  215. NSMutableDictionary<NSNumber *, FSTVoidErrorBlock> *completionBlocks =
  216. _mutationCompletionBlocks[_currentUser];
  217. if (!completionBlocks) {
  218. completionBlocks = [NSMutableDictionary dictionary];
  219. _mutationCompletionBlocks[_currentUser] = completionBlocks;
  220. }
  221. [completionBlocks setObject:completion forKey:@(batchID)];
  222. }
  223. /**
  224. * Takes an updateBlock in which a set of reads and writes can be performed atomically. In the
  225. * updateBlock, user code can read and write values using a transaction object. After the
  226. * updateBlock, all changes will be committed. If someone else has changed any of the data
  227. * referenced, then the updateBlock will be called again. If the updateBlock still fails after the
  228. * given number of retries, then the transaction will be rejected.
  229. *
  230. * The transaction object passed to the updateBlock contains methods for accessing documents
  231. * and collections. Unlike other firestore access, data accessed with the transaction will not
  232. * reflect local changes that have not been committed. For this reason, it is required that all
  233. * reads are performed before any writes. Transactions must be performed while online.
  234. */
  235. - (void)transactionWithRetries:(int)retries
  236. workerQueue:(AsyncQueue *)workerQueue
  237. updateBlock:(FSTTransactionBlock)updateBlock
  238. completion:(FSTVoidIDErrorBlock)completion {
  239. workerQueue->VerifyIsCurrentQueue();
  240. HARD_ASSERT(retries >= 0, "Got negative number of retries for transaction");
  241. std::shared_ptr<Transaction> transaction = _remoteStore->CreateTransaction();
  242. updateBlock(transaction, ^(id _Nullable result, NSError *_Nullable error) {
  243. workerQueue->Enqueue(
  244. [self, retries, workerQueue, updateBlock, completion, transaction, result, error] {
  245. if (error) {
  246. completion(nil, error);
  247. return;
  248. }
  249. transaction->Commit([self, retries, workerQueue, updateBlock, completion,
  250. result](const Status &status) {
  251. if (status.ok()) {
  252. completion(result, nil);
  253. return;
  254. }
  255. // TODO(b/35201829): Only retry on real transaction failures.
  256. if (retries == 0) {
  257. NSError *wrappedError =
  258. [NSError errorWithDomain:FIRFirestoreErrorDomain
  259. code:FIRFirestoreErrorCodeFailedPrecondition
  260. userInfo:@{
  261. NSLocalizedDescriptionKey : @"Transaction failed all retries.",
  262. NSUnderlyingErrorKey : MakeNSError(status)
  263. }];
  264. completion(nil, wrappedError);
  265. return;
  266. }
  267. workerQueue->VerifyIsCurrentQueue();
  268. return [self transactionWithRetries:(retries - 1)
  269. workerQueue:workerQueue
  270. updateBlock:updateBlock
  271. completion:completion];
  272. });
  273. });
  274. });
  275. }
  276. - (void)applyRemoteEvent:(const RemoteEvent &)remoteEvent {
  277. [self assertDelegateExistsForSelector:_cmd];
  278. // Update `receivedDocument` as appropriate for any limbo targets.
  279. for (const auto &entry : remoteEvent.target_changes()) {
  280. TargetId targetID = entry.first;
  281. const TargetChange &change = entry.second;
  282. const auto iter = _limboResolutionsByTarget.find(targetID);
  283. if (iter != _limboResolutionsByTarget.end()) {
  284. LimboResolution &limboResolution = iter->second;
  285. // Since this is a limbo resolution lookup, it's for a single document and it could be
  286. // added, modified, or removed, but not a combination.
  287. HARD_ASSERT(change.added_documents().size() + change.modified_documents().size() +
  288. change.removed_documents().size() <=
  289. 1,
  290. "Limbo resolution for single document contains multiple changes.");
  291. if (change.added_documents().size() > 0) {
  292. limboResolution.document_received = true;
  293. } else if (change.modified_documents().size() > 0) {
  294. HARD_ASSERT(limboResolution.document_received,
  295. "Received change for limbo target document without add.");
  296. } else if (change.removed_documents().size() > 0) {
  297. HARD_ASSERT(limboResolution.document_received,
  298. "Received remove for limbo target document without add.");
  299. limboResolution.document_received = false;
  300. } else {
  301. // This was probably just a CURRENT targetChange or similar.
  302. }
  303. }
  304. }
  305. MaybeDocumentMap changes = [self.localStore applyRemoteEvent:remoteEvent];
  306. [self emitNewSnapshotsAndNotifyLocalStoreWithChanges:changes remoteEvent:remoteEvent];
  307. }
  308. - (void)applyChangedOnlineState:(OnlineState)onlineState {
  309. NSMutableArray<FSTViewSnapshot *> *newViewSnapshots = [NSMutableArray array];
  310. [self.queryViewsByQuery
  311. enumerateKeysAndObjectsUsingBlock:^(FSTQuery *query, FSTQueryView *queryView, BOOL *stop) {
  312. FSTViewChange *viewChange = [queryView.view applyChangedOnlineState:onlineState];
  313. HARD_ASSERT(viewChange.limboChanges.count == 0,
  314. "OnlineState should not affect limbo documents.");
  315. if (viewChange.snapshot) {
  316. [newViewSnapshots addObject:viewChange.snapshot];
  317. }
  318. }];
  319. [self.syncEngineDelegate handleViewSnapshots:newViewSnapshots];
  320. [self.syncEngineDelegate applyChangedOnlineState:onlineState];
  321. }
  322. - (void)rejectListenWithTargetID:(const TargetId)targetID error:(NSError *)error {
  323. [self assertDelegateExistsForSelector:_cmd];
  324. const auto iter = _limboResolutionsByTarget.find(targetID);
  325. if (iter != _limboResolutionsByTarget.end()) {
  326. const DocumentKey limboKey = iter->second.key;
  327. // Since this query failed, we won't want to manually unlisten to it.
  328. // So go ahead and remove it from bookkeeping.
  329. _limboTargetsByKey.erase(limboKey);
  330. _limboResolutionsByTarget.erase(targetID);
  331. // TODO(dimond): Retry on transient errors?
  332. // It's a limbo doc. Create a synthetic event saying it was deleted. This is kind of a hack.
  333. // Ideally, we would have a method in the local store to purge a document. However, it would
  334. // be tricky to keep all of the local store's invariants with another method.
  335. FSTDeletedDocument *doc = [FSTDeletedDocument documentWithKey:limboKey
  336. version:SnapshotVersion::None()
  337. hasCommittedMutations:NO];
  338. DocumentKeySet limboDocuments = DocumentKeySet{doc.key};
  339. RemoteEvent event{SnapshotVersion::None(), /*target_changes=*/{}, /*target_mismatches=*/{},
  340. /*document_updates=*/{{limboKey, doc}}, std::move(limboDocuments)};
  341. [self applyRemoteEvent:event];
  342. } else {
  343. auto found = _queryViewsByTarget.find(targetID);
  344. HARD_ASSERT(found != _queryViewsByTarget.end(), "Unknown targetId: %s", targetID);
  345. FSTQueryView *queryView = found->second;
  346. FSTQuery *query = queryView.query;
  347. [self.localStore releaseQuery:query];
  348. [self removeAndCleanupQuery:queryView];
  349. if ([self errorIsInteresting:error]) {
  350. LOG_WARN("Listen for query at %s failed: %s", query.path.CanonicalString(),
  351. error.localizedDescription);
  352. }
  353. [self.syncEngineDelegate handleError:error forQuery:query];
  354. }
  355. }
  356. - (void)applySuccessfulWriteWithResult:(FSTMutationBatchResult *)batchResult {
  357. [self assertDelegateExistsForSelector:_cmd];
  358. // The local store may or may not be able to apply the write result and raise events immediately
  359. // (depending on whether the watcher is caught up), so we raise user callbacks first so that they
  360. // consistently happen before listen events.
  361. [self processUserCallbacksForBatchID:batchResult.batch.batchID error:nil];
  362. MaybeDocumentMap changes = [self.localStore acknowledgeBatchWithResult:batchResult];
  363. [self emitNewSnapshotsAndNotifyLocalStoreWithChanges:changes remoteEvent:absl::nullopt];
  364. }
  365. - (void)rejectFailedWriteWithBatchID:(BatchId)batchID error:(NSError *)error {
  366. [self assertDelegateExistsForSelector:_cmd];
  367. MaybeDocumentMap changes = [self.localStore rejectBatchID:batchID];
  368. if (!changes.empty() && [self errorIsInteresting:error]) {
  369. const DocumentKey &minKey = changes.min()->first;
  370. LOG_WARN("Write at %s failed: %s", minKey.ToString(), error.localizedDescription);
  371. }
  372. // The local store may or may not be able to apply the write result and raise events immediately
  373. // (depending on whether the watcher is caught up), so we raise user callbacks first so that they
  374. // consistently happen before listen events.
  375. [self processUserCallbacksForBatchID:batchID error:error];
  376. [self emitNewSnapshotsAndNotifyLocalStoreWithChanges:changes remoteEvent:absl::nullopt];
  377. }
  378. - (void)processUserCallbacksForBatchID:(BatchId)batchID error:(NSError *_Nullable)error {
  379. NSMutableDictionary<NSNumber *, FSTVoidErrorBlock> *completionBlocks =
  380. _mutationCompletionBlocks[_currentUser];
  381. // NOTE: Mutations restored from persistence won't have completion blocks, so it's okay for
  382. // this (or the completion below) to be nil.
  383. if (completionBlocks) {
  384. NSNumber *boxedBatchID = @(batchID);
  385. FSTVoidErrorBlock completion = completionBlocks[boxedBatchID];
  386. if (completion) {
  387. completion(error);
  388. [completionBlocks removeObjectForKey:boxedBatchID];
  389. }
  390. }
  391. }
  392. - (void)assertDelegateExistsForSelector:(SEL)methodSelector {
  393. HARD_ASSERT(self.syncEngineDelegate, "Tried to call '%s' before delegate was registered.",
  394. NSStringFromSelector(methodSelector));
  395. }
  396. - (void)removeAndCleanupQuery:(FSTQueryView *)queryView {
  397. [self.queryViewsByQuery removeObjectForKey:queryView.query];
  398. _queryViewsByTarget.erase(queryView.targetID);
  399. DocumentKeySet limboKeys = _limboDocumentRefs.ReferencedKeys(queryView.targetID);
  400. _limboDocumentRefs.RemoveReferences(queryView.targetID);
  401. for (const DocumentKey &key : limboKeys) {
  402. if (!_limboDocumentRefs.ContainsKey(key)) {
  403. // We removed the last reference for this key.
  404. [self removeLimboTargetForKey:key];
  405. }
  406. }
  407. }
  408. /**
  409. * Computes a new snapshot from the changes and calls the registered callback with the new snapshot.
  410. */
  411. - (void)emitNewSnapshotsAndNotifyLocalStoreWithChanges:(const MaybeDocumentMap &)changes
  412. remoteEvent:(const absl::optional<RemoteEvent> &)
  413. maybeRemoteEvent {
  414. NSMutableArray<FSTViewSnapshot *> *newSnapshots = [NSMutableArray array];
  415. NSMutableArray<FSTLocalViewChanges *> *documentChangesInAllViews = [NSMutableArray array];
  416. [self.queryViewsByQuery
  417. enumerateKeysAndObjectsUsingBlock:^(FSTQuery *query, FSTQueryView *queryView, BOOL *stop) {
  418. FSTView *view = queryView.view;
  419. FSTViewDocumentChanges *viewDocChanges = [view computeChangesWithDocuments:changes];
  420. if (viewDocChanges.needsRefill) {
  421. // The query has a limit and some docs were removed/updated, so we need to re-run the
  422. // query against the local store to make sure we didn't lose any good docs that had been
  423. // past the limit.
  424. DocumentMap docs = [self.localStore executeQuery:queryView.query];
  425. viewDocChanges = [view computeChangesWithDocuments:docs.underlying_map()
  426. previousChanges:viewDocChanges];
  427. }
  428. absl::optional<TargetChange> targetChange;
  429. if (maybeRemoteEvent.has_value()) {
  430. const RemoteEvent &remoteEvent = maybeRemoteEvent.value();
  431. auto it = remoteEvent.target_changes().find(queryView.targetID);
  432. if (it != remoteEvent.target_changes().end()) {
  433. targetChange = it->second;
  434. }
  435. }
  436. FSTViewChange *viewChange = [queryView.view applyChangesToDocuments:viewDocChanges
  437. targetChange:targetChange];
  438. [self updateTrackedLimboDocumentsWithChanges:viewChange.limboChanges
  439. targetID:queryView.targetID];
  440. if (viewChange.snapshot) {
  441. [newSnapshots addObject:viewChange.snapshot];
  442. FSTLocalViewChanges *docChanges =
  443. [FSTLocalViewChanges changesForViewSnapshot:viewChange.snapshot
  444. withTargetID:queryView.targetID];
  445. [documentChangesInAllViews addObject:docChanges];
  446. }
  447. }];
  448. [self.syncEngineDelegate handleViewSnapshots:newSnapshots];
  449. [self.localStore notifyLocalViewChanges:documentChangesInAllViews];
  450. }
  451. /** Updates the limbo document state for the given targetID. */
  452. - (void)updateTrackedLimboDocumentsWithChanges:(NSArray<FSTLimboDocumentChange *> *)limboChanges
  453. targetID:(TargetId)targetID {
  454. for (FSTLimboDocumentChange *limboChange in limboChanges) {
  455. switch (limboChange.type) {
  456. case FSTLimboDocumentChangeTypeAdded:
  457. _limboDocumentRefs.AddReference(limboChange.key, targetID);
  458. [self trackLimboChange:limboChange];
  459. break;
  460. case FSTLimboDocumentChangeTypeRemoved:
  461. LOG_DEBUG("Document no longer in limbo: %s", limboChange.key.ToString());
  462. _limboDocumentRefs.RemoveReference(limboChange.key, targetID);
  463. if (!_limboDocumentRefs.ContainsKey(limboChange.key)) {
  464. // We removed the last reference for this key
  465. [self removeLimboTargetForKey:limboChange.key];
  466. }
  467. break;
  468. default:
  469. HARD_FAIL("Unknown limbo change type: %s", limboChange.type);
  470. }
  471. }
  472. }
  473. - (void)trackLimboChange:(FSTLimboDocumentChange *)limboChange {
  474. DocumentKey key{limboChange.key};
  475. if (_limboTargetsByKey.find(key) == _limboTargetsByKey.end()) {
  476. LOG_DEBUG("New document in limbo: %s", key.ToString());
  477. TargetId limboTargetID = _targetIdGenerator.NextId();
  478. FSTQuery *query = [FSTQuery queryWithPath:key.path()];
  479. FSTQueryData *queryData = [[FSTQueryData alloc] initWithQuery:query
  480. targetID:limboTargetID
  481. listenSequenceNumber:kIrrelevantSequenceNumber
  482. purpose:FSTQueryPurposeLimboResolution];
  483. _limboResolutionsByTarget.emplace(limboTargetID, LimboResolution{key});
  484. _remoteStore->Listen(queryData);
  485. _limboTargetsByKey[key] = limboTargetID;
  486. }
  487. }
  488. - (void)removeLimboTargetForKey:(const DocumentKey &)key {
  489. const auto iter = _limboTargetsByKey.find(key);
  490. if (iter == _limboTargetsByKey.end()) {
  491. // This target already got removed, because the query failed.
  492. return;
  493. }
  494. TargetId limboTargetID = iter->second;
  495. _remoteStore->StopListening(limboTargetID);
  496. _limboTargetsByKey.erase(key);
  497. _limboResolutionsByTarget.erase(limboTargetID);
  498. }
  499. // Used for testing
  500. - (std::map<DocumentKey, TargetId>)currentLimboDocuments {
  501. // Return defensive copy
  502. return _limboTargetsByKey;
  503. }
  504. - (void)credentialDidChangeWithUser:(const firebase::firestore::auth::User &)user {
  505. BOOL userChanged = (_currentUser != user);
  506. _currentUser = user;
  507. if (userChanged) {
  508. // Notify local store and emit any resulting events from swapping out the mutation queue.
  509. MaybeDocumentMap changes = [self.localStore userDidChange:user];
  510. [self emitNewSnapshotsAndNotifyLocalStoreWithChanges:changes remoteEvent:absl::nullopt];
  511. }
  512. // Notify remote store so it can restart its streams.
  513. _remoteStore->HandleCredentialChange();
  514. }
  515. - (DocumentKeySet)remoteKeysForTarget:(TargetId)targetId {
  516. const auto iter = _limboResolutionsByTarget.find(targetId);
  517. if (iter != _limboResolutionsByTarget.end() && iter->second.document_received) {
  518. return DocumentKeySet{iter->second.key};
  519. } else {
  520. auto found = _queryViewsByTarget.find(targetId);
  521. FSTQueryView *queryView = found != _queryViewsByTarget.end() ? found->second : nil;
  522. return queryView ? queryView.view.syncedDocuments : DocumentKeySet{};
  523. }
  524. }
  525. /**
  526. * Decides if the error likely represents a developer mistake such as forgetting to create an index
  527. * or permission denied. Used to decide whether an error is worth automatically logging as a
  528. * warning.
  529. */
  530. - (BOOL)errorIsInteresting:(NSError *)error {
  531. if (error.domain == FIRFirestoreErrorDomain) {
  532. if (error.code == FIRFirestoreErrorCodeFailedPrecondition &&
  533. [error.localizedDescription containsString:@"requires an index"]) {
  534. return YES;
  535. } else if (error.code == FIRFirestoreErrorCodePermissionDenied) {
  536. return YES;
  537. }
  538. }
  539. return NO;
  540. }
  541. @end
  542. NS_ASSUME_NONNULL_END