FSTView.mm 19 KB

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