FSTSyncEngine.mm 25 KB

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