2017-10-03 08:55:22 -07:00
|
|
|
|
/*
|
|
|
|
|
|
* Copyright 2017 Google
|
|
|
|
|
|
*
|
|
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
|
|
*
|
|
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
*
|
|
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
|
|
* limitations under the License.
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2017-11-16 20:52:01 -08:00
|
|
|
|
#import "Firestore/Source/Local/FSTLocalStore.h"
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
2018-04-09 11:54:39 -07:00
|
|
|
|
#include <set>
|
|
|
|
|
|
|
2018-02-20 17:33:57 -05:00
|
|
|
|
#import "FIRTimestamp.h"
|
2018-01-17 12:23:45 -08:00
|
|
|
|
#import "Firestore/Source/Core/FSTListenSequence.h"
|
2017-11-16 20:52:01 -08:00
|
|
|
|
#import "Firestore/Source/Core/FSTQuery.h"
|
|
|
|
|
|
#import "Firestore/Source/Local/FSTLocalDocumentsView.h"
|
|
|
|
|
|
#import "Firestore/Source/Local/FSTLocalViewChanges.h"
|
|
|
|
|
|
#import "Firestore/Source/Local/FSTLocalWriteResult.h"
|
|
|
|
|
|
#import "Firestore/Source/Local/FSTMutationQueue.h"
|
|
|
|
|
|
#import "Firestore/Source/Local/FSTPersistence.h"
|
|
|
|
|
|
#import "Firestore/Source/Local/FSTQueryCache.h"
|
|
|
|
|
|
#import "Firestore/Source/Local/FSTQueryData.h"
|
|
|
|
|
|
#import "Firestore/Source/Local/FSTReferenceSet.h"
|
|
|
|
|
|
#import "Firestore/Source/Local/FSTRemoteDocumentCache.h"
|
|
|
|
|
|
#import "Firestore/Source/Model/FSTDocument.h"
|
|
|
|
|
|
#import "Firestore/Source/Model/FSTDocumentDictionary.h"
|
|
|
|
|
|
#import "Firestore/Source/Model/FSTMutation.h"
|
|
|
|
|
|
#import "Firestore/Source/Model/FSTMutationBatch.h"
|
|
|
|
|
|
#import "Firestore/Source/Remote/FSTRemoteEvent.h"
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
2018-02-15 17:23:08 -05:00
|
|
|
|
#include "Firestore/core/src/firebase/firestore/auth/user.h"
|
2018-01-25 18:39:46 -05:00
|
|
|
|
#include "Firestore/core/src/firebase/firestore/core/target_id_generator.h"
|
2018-03-23 17:52:01 -04:00
|
|
|
|
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
|
2018-04-25 14:05:36 -04:00
|
|
|
|
#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
|
2018-05-24 11:00:23 -07:00
|
|
|
|
#include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
|
2018-05-23 07:00:05 -07:00
|
|
|
|
#include "Firestore/core/src/firebase/firestore/util/log.h"
|
2018-01-25 18:39:46 -05:00
|
|
|
|
|
2018-02-15 17:23:08 -05:00
|
|
|
|
using firebase::firestore::auth::User;
|
2018-02-06 13:51:11 -05:00
|
|
|
|
using firebase::firestore::core::TargetIdGenerator;
|
2018-03-24 11:06:32 -04:00
|
|
|
|
using firebase::firestore::model::DocumentKey;
|
2018-04-25 14:05:36 -04:00
|
|
|
|
using firebase::firestore::model::SnapshotVersion;
|
2018-05-05 08:10:51 -07:00
|
|
|
|
using firebase::firestore::model::DocumentKeySet;
|
2018-05-05 09:00:53 -07:00
|
|
|
|
using firebase::firestore::model::DocumentVersionMap;
|
2018-02-06 13:51:11 -05:00
|
|
|
|
|
2017-10-03 08:55:22 -07:00
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
|
|
|
|
|
|
|
|
@interface FSTLocalStore ()
|
|
|
|
|
|
|
|
|
|
|
|
/** Manages our in-memory or durable persistence. */
|
|
|
|
|
|
@property(nonatomic, strong, readonly) id<FSTPersistence> persistence;
|
|
|
|
|
|
|
|
|
|
|
|
/** The set of all mutations that have been sent but not yet been applied to the backend. */
|
|
|
|
|
|
@property(nonatomic, strong) id<FSTMutationQueue> mutationQueue;
|
|
|
|
|
|
|
|
|
|
|
|
/** The set of all cached remote documents. */
|
|
|
|
|
|
@property(nonatomic, strong) id<FSTRemoteDocumentCache> remoteDocumentCache;
|
|
|
|
|
|
|
|
|
|
|
|
/** The "local" view of all documents (layering mutationQueue on top of remoteDocumentCache). */
|
|
|
|
|
|
@property(nonatomic, strong) FSTLocalDocumentsView *localDocuments;
|
|
|
|
|
|
|
|
|
|
|
|
/** The set of document references maintained by any local views. */
|
|
|
|
|
|
@property(nonatomic, strong) FSTReferenceSet *localViewReferences;
|
|
|
|
|
|
|
|
|
|
|
|
/** Maps a query to the data about that query. */
|
|
|
|
|
|
@property(nonatomic, strong) id<FSTQueryCache> queryCache;
|
|
|
|
|
|
|
|
|
|
|
|
/** Maps a targetID to data about its query. */
|
|
|
|
|
|
@property(nonatomic, strong) NSMutableDictionary<NSNumber *, FSTQueryData *> *targetIDs;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* A heldBatchResult is a mutation batch result (from a write acknowledgement) that arrived before
|
|
|
|
|
|
* the watch stream got notified of a snapshot that includes the write. So we "hold" it until
|
|
|
|
|
|
* the watch stream catches up. It ensures that the local write remains visible (latency
|
|
|
|
|
|
* compensation) and doesn't temporarily appear reverted because the watch stream is slower than
|
|
|
|
|
|
* the write stream and so wasn't reflecting it.
|
|
|
|
|
|
*
|
|
|
|
|
|
* NOTE: Eventually we want to move this functionality into the remote store.
|
|
|
|
|
|
*/
|
|
|
|
|
|
@property(nonatomic, strong) NSMutableArray<FSTMutationBatchResult *> *heldBatchResults;
|
|
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
2018-01-25 18:39:46 -05:00
|
|
|
|
@implementation FSTLocalStore {
|
|
|
|
|
|
/** Used to generate targetIDs for queries tracked locally. */
|
2018-02-06 13:51:11 -05:00
|
|
|
|
TargetIdGenerator _targetIDGenerator;
|
2018-01-25 18:39:46 -05:00
|
|
|
|
}
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
|
|
|
|
|
- (instancetype)initWithPersistence:(id<FSTPersistence>)persistence
|
2018-02-15 17:23:08 -05:00
|
|
|
|
initialUser:(const User &)initialUser {
|
2017-10-03 08:55:22 -07:00
|
|
|
|
if (self = [super init]) {
|
|
|
|
|
|
_persistence = persistence;
|
|
|
|
|
|
_mutationQueue = [persistence mutationQueueForUser:initialUser];
|
|
|
|
|
|
_remoteDocumentCache = [persistence remoteDocumentCache];
|
|
|
|
|
|
_queryCache = [persistence queryCache];
|
|
|
|
|
|
_localDocuments = [FSTLocalDocumentsView viewWithRemoteDocumentCache:_remoteDocumentCache
|
|
|
|
|
|
mutationQueue:_mutationQueue];
|
|
|
|
|
|
_localViewReferences = [[FSTReferenceSet alloc] init];
|
2018-05-08 10:49:54 -07:00
|
|
|
|
[_persistence.referenceDelegate addInMemoryPins:_localViewReferences];
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
|
|
|
|
|
_targetIDs = [NSMutableDictionary dictionary];
|
|
|
|
|
|
_heldBatchResults = [NSMutableArray array];
|
2018-01-25 18:39:46 -05:00
|
|
|
|
|
2018-02-06 13:51:11 -05:00
|
|
|
|
_targetIDGenerator = TargetIdGenerator::LocalStoreTargetIdGenerator(0);
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
return self;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)start {
|
|
|
|
|
|
[self startMutationQueue];
|
2018-07-27 13:58:11 -07:00
|
|
|
|
FSTTargetID targetID = [self.queryCache highestTargetID];
|
|
|
|
|
|
_targetIDGenerator = TargetIdGenerator::LocalStoreTargetIdGenerator(targetID);
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)startMutationQueue {
|
2018-03-30 10:18:25 -07:00
|
|
|
|
self.persistence.run("Start MutationQueue", [&]() {
|
|
|
|
|
|
[self.mutationQueue start];
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
2018-03-30 10:18:25 -07:00
|
|
|
|
// If we have any leftover mutation batch results from a prior run, just drop them.
|
|
|
|
|
|
// TODO(http://b/33446471): We probably need to repopulate heldBatchResults or similar instead,
|
|
|
|
|
|
// but that is not straightforward since we're not persisting the write ack versions.
|
|
|
|
|
|
[self.heldBatchResults removeAllObjects];
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
2018-03-30 10:18:25 -07:00
|
|
|
|
// TODO(mikelehen): This is the only usage of getAllMutationBatchesThroughBatchId:. Consider
|
|
|
|
|
|
// removing it in favor of a getAcknowledgedBatches method.
|
|
|
|
|
|
FSTBatchID highestAck = [self.mutationQueue highestAcknowledgedBatchID];
|
|
|
|
|
|
if (highestAck != kFSTBatchIDUnknown) {
|
|
|
|
|
|
NSArray<FSTMutationBatch *> *batches =
|
|
|
|
|
|
[self.mutationQueue allMutationBatchesThroughBatchID:highestAck];
|
|
|
|
|
|
if (batches.count > 0) {
|
|
|
|
|
|
// NOTE: This could be more efficient if we had a removeBatchesThroughBatchID, but this set
|
|
|
|
|
|
// should be very small and this code should go away eventually.
|
|
|
|
|
|
[self.mutationQueue removeMutationBatches:batches];
|
|
|
|
|
|
}
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
2018-03-30 10:18:25 -07:00
|
|
|
|
});
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-02-15 17:23:08 -05:00
|
|
|
|
- (FSTMaybeDocumentDictionary *)userDidChange:(const User &)user {
|
2017-10-03 08:55:22 -07:00
|
|
|
|
// Swap out the mutation queue, grabbing the pending mutation batches before and after.
|
2018-03-30 10:18:25 -07:00
|
|
|
|
NSArray<FSTMutationBatch *> *oldBatches = self.persistence.run(
|
|
|
|
|
|
"OldBatches",
|
|
|
|
|
|
[&]() -> NSArray<FSTMutationBatch *> * { return [self.mutationQueue allMutationBatches]; });
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
|
|
|
|
|
self.mutationQueue = [self.persistence mutationQueueForUser:user];
|
|
|
|
|
|
|
|
|
|
|
|
[self startMutationQueue];
|
|
|
|
|
|
|
2018-03-30 10:18:25 -07:00
|
|
|
|
return self.persistence.run("NewBatches", [&]() -> FSTMaybeDocumentDictionary * {
|
|
|
|
|
|
NSArray<FSTMutationBatch *> *newBatches = [self.mutationQueue allMutationBatches];
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
2018-03-30 10:18:25 -07:00
|
|
|
|
// Recreate our LocalDocumentsView using the new MutationQueue.
|
|
|
|
|
|
self.localDocuments =
|
|
|
|
|
|
[FSTLocalDocumentsView viewWithRemoteDocumentCache:self.remoteDocumentCache
|
|
|
|
|
|
mutationQueue:self.mutationQueue];
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
2018-03-30 10:18:25 -07:00
|
|
|
|
// Union the old/new changed keys.
|
2018-05-05 08:10:51 -07:00
|
|
|
|
DocumentKeySet changedKeys;
|
2018-03-30 10:18:25 -07:00
|
|
|
|
for (NSArray<FSTMutationBatch *> *batches in @[ oldBatches, newBatches ]) {
|
|
|
|
|
|
for (FSTMutationBatch *batch in batches) {
|
|
|
|
|
|
for (FSTMutation *mutation in batch.mutations) {
|
2018-05-05 08:10:51 -07:00
|
|
|
|
changedKeys = changedKeys.insert(mutation.key);
|
2018-03-30 10:18:25 -07:00
|
|
|
|
}
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-03-30 10:18:25 -07:00
|
|
|
|
// Return the set of all (potentially) changed documents as the result of the user change.
|
|
|
|
|
|
return [self.localDocuments documentsForKeys:changedKeys];
|
|
|
|
|
|
});
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (FSTLocalWriteResult *)locallyWriteMutations:(NSArray<FSTMutation *> *)mutations {
|
2018-03-30 10:18:25 -07:00
|
|
|
|
return self.persistence.run("Locally write mutations", [&]() -> FSTLocalWriteResult * {
|
|
|
|
|
|
FIRTimestamp *localWriteTime = [FIRTimestamp timestamp];
|
|
|
|
|
|
FSTMutationBatch *batch =
|
|
|
|
|
|
[self.mutationQueue addMutationBatchWithWriteTime:localWriteTime mutations:mutations];
|
2018-05-05 08:10:51 -07:00
|
|
|
|
DocumentKeySet keys = [batch keys];
|
2018-03-30 10:18:25 -07:00
|
|
|
|
FSTMaybeDocumentDictionary *changedDocuments = [self.localDocuments documentsForKeys:keys];
|
|
|
|
|
|
return [FSTLocalWriteResult resultForBatchID:batch.batchID changes:changedDocuments];
|
|
|
|
|
|
});
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (FSTMaybeDocumentDictionary *)acknowledgeBatchWithResult:(FSTMutationBatchResult *)batchResult {
|
2018-03-30 10:18:25 -07:00
|
|
|
|
return self.persistence.run("Acknowledge batch", [&]() -> FSTMaybeDocumentDictionary * {
|
|
|
|
|
|
id<FSTMutationQueue> mutationQueue = self.mutationQueue;
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
2018-03-30 10:18:25 -07:00
|
|
|
|
[mutationQueue acknowledgeBatch:batchResult.batch streamToken:batchResult.streamToken];
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
2018-05-05 08:10:51 -07:00
|
|
|
|
DocumentKeySet affected;
|
2018-03-30 10:18:25 -07:00
|
|
|
|
if ([self shouldHoldBatchResultWithVersion:batchResult.commitVersion]) {
|
|
|
|
|
|
[self.heldBatchResults addObject:batchResult];
|
|
|
|
|
|
} else {
|
2018-04-02 16:01:03 -07:00
|
|
|
|
affected = [self releaseBatchResults:@[ batchResult ]];
|
2018-03-30 10:18:25 -07:00
|
|
|
|
}
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
2018-03-30 10:18:25 -07:00
|
|
|
|
[self.mutationQueue performConsistencyCheck];
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
2018-03-30 10:18:25 -07:00
|
|
|
|
return [self.localDocuments documentsForKeys:affected];
|
|
|
|
|
|
});
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (FSTMaybeDocumentDictionary *)rejectBatchID:(FSTBatchID)batchID {
|
2018-03-30 10:18:25 -07:00
|
|
|
|
return self.persistence.run("Reject batch", [&]() -> FSTMaybeDocumentDictionary * {
|
|
|
|
|
|
FSTMutationBatch *toReject = [self.mutationQueue lookupMutationBatch:batchID];
|
2018-05-24 11:00:23 -07:00
|
|
|
|
HARD_ASSERT(toReject, "Attempt to reject nonexistent batch!");
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
2018-03-30 10:18:25 -07:00
|
|
|
|
FSTBatchID lastAcked = [self.mutationQueue highestAcknowledgedBatchID];
|
2018-05-24 11:00:23 -07:00
|
|
|
|
HARD_ASSERT(batchID > lastAcked, "Acknowledged batches can't be rejected.");
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
2018-05-05 08:10:51 -07:00
|
|
|
|
DocumentKeySet affected = [self removeMutationBatch:toReject];
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
2018-03-30 10:18:25 -07:00
|
|
|
|
[self.mutationQueue performConsistencyCheck];
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
2018-03-30 10:18:25 -07:00
|
|
|
|
return [self.localDocuments documentsForKeys:affected];
|
|
|
|
|
|
});
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (nullable NSData *)lastStreamToken {
|
|
|
|
|
|
return [self.mutationQueue lastStreamToken];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)setLastStreamToken:(nullable NSData *)streamToken {
|
2018-03-30 10:18:25 -07:00
|
|
|
|
self.persistence.run("Set stream token",
|
|
|
|
|
|
[&]() { [self.mutationQueue setLastStreamToken:streamToken]; });
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-04-25 14:05:36 -04:00
|
|
|
|
- (const SnapshotVersion &)lastRemoteSnapshotVersion {
|
2017-10-03 08:55:22 -07:00
|
|
|
|
return [self.queryCache lastRemoteSnapshotVersion];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (FSTMaybeDocumentDictionary *)applyRemoteEvent:(FSTRemoteEvent *)remoteEvent {
|
2018-03-30 10:18:25 -07:00
|
|
|
|
return self.persistence.run("Apply remote event", [&]() -> FSTMaybeDocumentDictionary * {
|
2018-05-08 10:49:54 -07:00
|
|
|
|
// TODO(gsoltis): move the sequence number into the reference delegate.
|
2018-07-27 13:58:11 -07:00
|
|
|
|
FSTListenSequenceNumber sequenceNumber = self.persistence.currentSequenceNumber;
|
2018-03-30 10:18:25 -07:00
|
|
|
|
id<FSTQueryCache> queryCache = self.queryCache;
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
2018-07-19 07:15:47 -07:00
|
|
|
|
DocumentKeySet authoritativeUpdates;
|
2018-06-12 10:58:35 -07:00
|
|
|
|
for (const auto &entry : remoteEvent.targetChanges) {
|
|
|
|
|
|
FSTTargetID targetID = entry.first;
|
|
|
|
|
|
FSTBoxedTargetID *boxedTargetID = @(targetID);
|
|
|
|
|
|
FSTTargetChange *change = entry.second;
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
2018-03-30 10:18:25 -07:00
|
|
|
|
// Do not ref/unref unassigned targetIDs - it may lead to leaks.
|
2018-06-12 10:58:35 -07:00
|
|
|
|
FSTQueryData *queryData = self.targetIDs[boxedTargetID];
|
2018-03-30 10:18:25 -07:00
|
|
|
|
if (!queryData) {
|
2018-06-12 10:58:35 -07:00
|
|
|
|
continue;
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
2018-03-30 10:18:25 -07:00
|
|
|
|
|
2018-07-19 07:15:47 -07:00
|
|
|
|
// When a global snapshot contains updates (either add or modify) we can completely trust
|
|
|
|
|
|
// these updates as authoritative and blindly apply them to our cache (as a defensive measure
|
|
|
|
|
|
// to promote self-healing in the unfortunate case that our cache is ever somehow corrupted /
|
|
|
|
|
|
// out-of-sync).
|
|
|
|
|
|
//
|
|
|
|
|
|
// If the document is only updated while removing it from a target then watch isn't obligated
|
|
|
|
|
|
// to send the absolute latest version: it can send the first version that caused the document
|
|
|
|
|
|
// not to match.
|
|
|
|
|
|
for (const DocumentKey &key : change.addedDocuments) {
|
|
|
|
|
|
authoritativeUpdates = authoritativeUpdates.insert(key);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (const DocumentKey &key : change.modifiedDocuments) {
|
|
|
|
|
|
authoritativeUpdates = authoritativeUpdates.insert(key);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-06-12 10:58:35 -07:00
|
|
|
|
[queryCache removeMatchingKeys:change.removedDocuments forTargetID:targetID];
|
|
|
|
|
|
[queryCache addMatchingKeys:change.addedDocuments forTargetID:targetID];
|
|
|
|
|
|
|
2018-05-03 12:11:39 -07:00
|
|
|
|
// Update the resume token if the change includes one. Don't clear any preexisting value.
|
|
|
|
|
|
// Bump the sequence number as well, so that documents being removed now are ordered later
|
|
|
|
|
|
// than documents that were previously removed from this target.
|
|
|
|
|
|
NSData *resumeToken = change.resumeToken;
|
|
|
|
|
|
if (resumeToken.length > 0) {
|
2018-06-12 10:58:35 -07:00
|
|
|
|
queryData = [queryData queryDataByReplacingSnapshotVersion:remoteEvent.snapshotVersion
|
2018-05-03 12:11:39 -07:00
|
|
|
|
resumeToken:resumeToken
|
|
|
|
|
|
sequenceNumber:sequenceNumber];
|
2018-06-12 10:58:35 -07:00
|
|
|
|
self.targetIDs[boxedTargetID] = queryData;
|
2018-05-03 12:11:39 -07:00
|
|
|
|
[self.queryCache updateQueryData:queryData];
|
|
|
|
|
|
}
|
2018-06-12 10:58:35 -07:00
|
|
|
|
}
|
2018-03-30 10:18:25 -07:00
|
|
|
|
|
|
|
|
|
|
// TODO(klimt): This could probably be an NSMutableDictionary.
|
2018-05-05 08:10:51 -07:00
|
|
|
|
DocumentKeySet changedDocKeys;
|
2018-05-08 10:49:54 -07:00
|
|
|
|
const DocumentKeySet &limboDocuments = remoteEvent.limboDocumentChanges;
|
2018-03-30 10:18:25 -07:00
|
|
|
|
for (const auto &kv : remoteEvent.documentUpdates) {
|
|
|
|
|
|
const DocumentKey &key = kv.first;
|
|
|
|
|
|
FSTMaybeDocument *doc = kv.second;
|
2018-05-05 08:10:51 -07:00
|
|
|
|
changedDocKeys = changedDocKeys.insert(key);
|
2018-04-02 16:01:03 -07:00
|
|
|
|
FSTMaybeDocument *existingDoc = [self.remoteDocumentCache entryForKey:key];
|
2018-07-19 07:15:47 -07:00
|
|
|
|
|
|
|
|
|
|
// If a document update isn't authoritative, make sure we don't apply an old document version
|
|
|
|
|
|
// to the remote cache. We make an exception for SnapshotVersion.MIN which can happen for
|
|
|
|
|
|
// manufactured events (e.g. in the case of a limbo document resolution failing).
|
|
|
|
|
|
if (!existingDoc || doc.version == SnapshotVersion::None() ||
|
|
|
|
|
|
authoritativeUpdates.contains(doc.key) || doc.version >= existingDoc.version) {
|
2018-04-02 16:01:03 -07:00
|
|
|
|
[self.remoteDocumentCache addEntry:doc];
|
2018-03-30 10:18:25 -07:00
|
|
|
|
} else {
|
2018-05-23 07:00:05 -07:00
|
|
|
|
LOG_DEBUG(
|
|
|
|
|
|
"FSTLocalStore Ignoring outdated watch update for %s. "
|
|
|
|
|
|
"Current version: %s Watch version: %s",
|
|
|
|
|
|
key.ToString(), existingDoc.version.timestamp().ToString(),
|
|
|
|
|
|
doc.version.timestamp().ToString());
|
2018-03-30 10:18:25 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-07-27 13:58:11 -07:00
|
|
|
|
// If this was a limbo resolution, make sure we mark when it was accessed.
|
2018-05-08 10:49:54 -07:00
|
|
|
|
if (limboDocuments.contains(key)) {
|
|
|
|
|
|
[self.persistence.referenceDelegate limboDocumentUpdated:key];
|
|
|
|
|
|
}
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-03-30 10:18:25 -07:00
|
|
|
|
// HACK: The only reason we allow omitting snapshot version is so we can synthesize remote
|
|
|
|
|
|
// events when we get permission denied errors while trying to resolve the state of a locally
|
|
|
|
|
|
// cached document that is in limbo.
|
2018-04-25 14:05:36 -04:00
|
|
|
|
const SnapshotVersion &lastRemoteVersion = [self.queryCache lastRemoteSnapshotVersion];
|
2018-05-01 13:37:53 -04:00
|
|
|
|
const SnapshotVersion &remoteVersion = remoteEvent.snapshotVersion;
|
2018-04-25 14:05:36 -04:00
|
|
|
|
if (remoteVersion != SnapshotVersion::None()) {
|
2018-05-24 11:00:23 -07:00
|
|
|
|
HARD_ASSERT(remoteVersion >= lastRemoteVersion,
|
|
|
|
|
|
"Watch stream reverted to previous snapshot?? (%s < %s)",
|
|
|
|
|
|
remoteVersion.timestamp().ToString(), lastRemoteVersion.timestamp().ToString());
|
2018-03-30 10:18:25 -07:00
|
|
|
|
[self.queryCache setLastRemoteSnapshotVersion:remoteVersion];
|
2018-03-24 11:06:32 -04:00
|
|
|
|
}
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
2018-05-05 08:10:51 -07:00
|
|
|
|
DocumentKeySet releasedWriteKeys = [self releaseHeldBatchResults];
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
2018-03-30 10:18:25 -07:00
|
|
|
|
// Union the two key sets.
|
2018-05-05 08:10:51 -07:00
|
|
|
|
DocumentKeySet keysToRecalc = changedDocKeys;
|
|
|
|
|
|
for (const DocumentKey &key : releasedWriteKeys) {
|
|
|
|
|
|
keysToRecalc = keysToRecalc.insert(key);
|
|
|
|
|
|
}
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
2018-03-30 10:18:25 -07:00
|
|
|
|
return [self.localDocuments documentsForKeys:keysToRecalc];
|
|
|
|
|
|
});
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)notifyLocalViewChanges:(NSArray<FSTLocalViewChanges *> *)viewChanges {
|
2018-03-30 10:18:25 -07:00
|
|
|
|
self.persistence.run("NotifyLocalViewChanges", [&]() {
|
|
|
|
|
|
FSTReferenceSet *localViewReferences = self.localViewReferences;
|
2018-07-31 10:19:57 -07:00
|
|
|
|
for (FSTLocalViewChanges *viewChange in viewChanges) {
|
|
|
|
|
|
for (const DocumentKey &key : viewChange.removedKeys) {
|
2018-07-27 13:58:11 -07:00
|
|
|
|
[self->_persistence.referenceDelegate removeReference:key];
|
2018-05-08 10:49:54 -07:00
|
|
|
|
}
|
2018-07-31 10:19:57 -07:00
|
|
|
|
[localViewReferences addReferencesToKeys:viewChange.addedKeys forID:viewChange.targetID];
|
|
|
|
|
|
[localViewReferences removeReferencesToKeys:viewChange.removedKeys forID:viewChange.targetID];
|
2018-03-30 10:18:25 -07:00
|
|
|
|
}
|
|
|
|
|
|
});
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (nullable FSTMutationBatch *)nextMutationBatchAfterBatchID:(FSTBatchID)batchID {
|
2018-03-30 10:18:25 -07:00
|
|
|
|
FSTMutationBatch *result =
|
|
|
|
|
|
self.persistence.run("NextMutationBatchAfterBatchID", [&]() -> FSTMutationBatch * {
|
|
|
|
|
|
return [self.mutationQueue nextMutationBatchAfterBatchID:batchID];
|
|
|
|
|
|
});
|
2018-03-26 11:57:34 -07:00
|
|
|
|
return result;
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-03-23 17:52:01 -04:00
|
|
|
|
- (nullable FSTMaybeDocument *)readDocument:(const DocumentKey &)key {
|
2018-03-30 10:18:25 -07:00
|
|
|
|
return self.persistence.run("ReadDocument", [&]() -> FSTMaybeDocument *_Nullable {
|
|
|
|
|
|
return [self.localDocuments documentForKey:key];
|
|
|
|
|
|
});
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (FSTQueryData *)allocateQuery:(FSTQuery *)query {
|
2018-03-30 10:18:25 -07:00
|
|
|
|
FSTQueryData *queryData = self.persistence.run("Allocate query", [&]() -> FSTQueryData * {
|
|
|
|
|
|
FSTQueryData *cached = [self.queryCache queryDataForQuery:query];
|
|
|
|
|
|
// TODO(mcg): freshen last accessed date if cached exists?
|
|
|
|
|
|
if (!cached) {
|
|
|
|
|
|
cached = [[FSTQueryData alloc] initWithQuery:query
|
|
|
|
|
|
targetID:_targetIDGenerator.NextId()
|
2018-07-27 13:58:11 -07:00
|
|
|
|
listenSequenceNumber:self.persistence.currentSequenceNumber
|
2018-03-30 10:18:25 -07:00
|
|
|
|
purpose:FSTQueryPurposeListen];
|
|
|
|
|
|
[self.queryCache addQueryData:cached];
|
|
|
|
|
|
}
|
|
|
|
|
|
return cached;
|
|
|
|
|
|
});
|
2017-10-03 08:55:22 -07:00
|
|
|
|
// Sanity check to ensure that even when resuming a query it's not currently active.
|
2018-03-30 10:18:25 -07:00
|
|
|
|
FSTBoxedTargetID *boxedTargetID = @(queryData.targetID);
|
2018-05-24 11:00:23 -07:00
|
|
|
|
HARD_ASSERT(!self.targetIDs[boxedTargetID], "Tried to allocate an already allocated query: %s",
|
|
|
|
|
|
query);
|
2018-03-30 10:18:25 -07:00
|
|
|
|
self.targetIDs[boxedTargetID] = queryData;
|
|
|
|
|
|
return queryData;
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)releaseQuery:(FSTQuery *)query {
|
2018-03-30 10:18:25 -07:00
|
|
|
|
self.persistence.run("Release query", [&]() {
|
|
|
|
|
|
FSTQueryData *queryData = [self.queryCache queryDataForQuery:query];
|
2018-05-24 11:00:23 -07:00
|
|
|
|
HARD_ASSERT(queryData, "Tried to release nonexistent query: %s", query);
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
2018-03-30 10:18:25 -07:00
|
|
|
|
[self.localViewReferences removeReferencesForID:queryData.targetID];
|
2018-05-08 10:49:54 -07:00
|
|
|
|
[self.persistence.referenceDelegate removeTarget:queryData];
|
2018-03-30 10:18:25 -07:00
|
|
|
|
[self.targetIDs removeObjectForKey:@(queryData.targetID)];
|
2017-10-03 08:55:22 -07:00
|
|
|
|
|
2018-03-30 10:18:25 -07:00
|
|
|
|
// If this was the last watch target, then we won't get any more watch snapshots, so we should
|
|
|
|
|
|
// release any held batch results.
|
|
|
|
|
|
if ([self.targetIDs count] == 0) {
|
2018-04-02 16:01:03 -07:00
|
|
|
|
[self releaseHeldBatchResults];
|
2018-03-30 10:18:25 -07:00
|
|
|
|
}
|
|
|
|
|
|
});
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (FSTDocumentDictionary *)executeQuery:(FSTQuery *)query {
|
2018-03-30 10:18:25 -07:00
|
|
|
|
return self.persistence.run("ExecuteQuery", [&]() -> FSTDocumentDictionary * {
|
|
|
|
|
|
return [self.localDocuments documentsMatchingQuery:query];
|
|
|
|
|
|
});
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-05-05 08:10:51 -07:00
|
|
|
|
- (DocumentKeySet)remoteDocumentKeysForTarget:(FSTTargetID)targetID {
|
|
|
|
|
|
return self.persistence.run("RemoteDocumentKeysForTarget", [&]() -> DocumentKeySet {
|
2018-03-30 10:18:25 -07:00
|
|
|
|
return [self.queryCache matchingKeysForTargetID:targetID];
|
|
|
|
|
|
});
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Releases all the held mutation batches up to the current remote version received, and
|
|
|
|
|
|
* applies their mutations to the docs in the remote documents cache.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return the set of keys of docs that were modified by those writes.
|
|
|
|
|
|
*/
|
2018-05-05 08:10:51 -07:00
|
|
|
|
- (DocumentKeySet)releaseHeldBatchResults {
|
2017-10-03 08:55:22 -07:00
|
|
|
|
NSMutableArray<FSTMutationBatchResult *> *toRelease = [NSMutableArray array];
|
|
|
|
|
|
for (FSTMutationBatchResult *batchResult in self.heldBatchResults) {
|
|
|
|
|
|
if (![self isRemoteUpToVersion:batchResult.commitVersion]) {
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
[toRelease addObject:batchResult];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (toRelease.count == 0) {
|
2018-05-05 08:10:51 -07:00
|
|
|
|
return DocumentKeySet{};
|
2017-10-03 08:55:22 -07:00
|
|
|
|
} else {
|
|
|
|
|
|
[self.heldBatchResults removeObjectsInRange:NSMakeRange(0, toRelease.count)];
|
2018-04-02 16:01:03 -07:00
|
|
|
|
return [self releaseBatchResults:toRelease];
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-04-25 14:05:36 -04:00
|
|
|
|
- (BOOL)isRemoteUpToVersion:(const SnapshotVersion &)version {
|
2017-10-03 08:55:22 -07:00
|
|
|
|
// If there are no watch targets, then we won't get remote snapshots, and are always "up-to-date."
|
2018-04-25 14:05:36 -04:00
|
|
|
|
return version <= self.queryCache.lastRemoteSnapshotVersion || self.targetIDs.count == 0;
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-04-25 14:05:36 -04:00
|
|
|
|
- (BOOL)shouldHoldBatchResultWithVersion:(const SnapshotVersion &)version {
|
2017-10-03 08:55:22 -07:00
|
|
|
|
// Check if watcher isn't up to date or prior results are already held.
|
|
|
|
|
|
return ![self isRemoteUpToVersion:version] || self.heldBatchResults.count > 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-05-05 08:10:51 -07:00
|
|
|
|
- (DocumentKeySet)releaseBatchResults:(NSArray<FSTMutationBatchResult *> *)batchResults {
|
2017-10-03 08:55:22 -07:00
|
|
|
|
NSMutableArray<FSTMutationBatch *> *batches = [NSMutableArray array];
|
|
|
|
|
|
for (FSTMutationBatchResult *batchResult in batchResults) {
|
2018-04-02 16:01:03 -07:00
|
|
|
|
[self applyBatchResult:batchResult];
|
2017-10-03 08:55:22 -07:00
|
|
|
|
[batches addObject:batchResult.batch];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-03-30 10:18:25 -07:00
|
|
|
|
return [self removeMutationBatches:batches];
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
2018-05-05 08:10:51 -07:00
|
|
|
|
- (DocumentKeySet)removeMutationBatch:(FSTMutationBatch *)batch {
|
2018-03-30 10:18:25 -07:00
|
|
|
|
return [self removeMutationBatches:@[ batch ]];
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Removes all the mutation batches named in the given array. */
|
2018-05-05 08:10:51 -07:00
|
|
|
|
- (DocumentKeySet)removeMutationBatches:(NSArray<FSTMutationBatch *> *)batches {
|
|
|
|
|
|
DocumentKeySet affectedDocs;
|
2017-10-03 08:55:22 -07:00
|
|
|
|
for (FSTMutationBatch *batch in batches) {
|
|
|
|
|
|
for (FSTMutation *mutation in batch.mutations) {
|
2018-03-23 17:52:01 -04:00
|
|
|
|
const DocumentKey &key = mutation.key;
|
2018-05-05 08:10:51 -07:00
|
|
|
|
affectedDocs = affectedDocs.insert(key);
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-03-29 08:55:10 -07:00
|
|
|
|
[self.mutationQueue removeMutationBatches:batches];
|
2017-10-03 08:55:22 -07:00
|
|
|
|
return affectedDocs;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-04-02 16:01:03 -07:00
|
|
|
|
- (void)applyBatchResult:(FSTMutationBatchResult *)batchResult {
|
2017-10-03 08:55:22 -07:00
|
|
|
|
FSTMutationBatch *batch = batchResult.batch;
|
2018-05-05 08:10:51 -07:00
|
|
|
|
DocumentKeySet docKeys = batch.keys;
|
2018-05-05 09:00:53 -07:00
|
|
|
|
const DocumentVersionMap &versions = batchResult.docVersions;
|
2018-05-05 08:10:51 -07:00
|
|
|
|
for (const DocumentKey &docKey : docKeys) {
|
2018-04-02 16:01:03 -07:00
|
|
|
|
FSTMaybeDocument *_Nullable remoteDoc = [self.remoteDocumentCache entryForKey:docKey];
|
2017-10-03 08:55:22 -07:00
|
|
|
|
FSTMaybeDocument *_Nullable doc = remoteDoc;
|
2018-05-05 09:00:53 -07:00
|
|
|
|
|
|
|
|
|
|
auto ackVersionIter = versions.find(docKey);
|
2018-05-24 11:00:23 -07:00
|
|
|
|
HARD_ASSERT(ackVersionIter != versions.end(),
|
|
|
|
|
|
"docVersions should contain every doc in the write.");
|
2018-05-05 09:00:53 -07:00
|
|
|
|
const SnapshotVersion &ackVersion = ackVersionIter->second;
|
|
|
|
|
|
if (!doc || doc.version < ackVersion) {
|
2017-10-03 08:55:22 -07:00
|
|
|
|
doc = [batch applyTo:doc documentKey:docKey mutationBatchResult:batchResult];
|
|
|
|
|
|
if (!doc) {
|
2018-05-24 11:00:23 -07:00
|
|
|
|
HARD_ASSERT(!remoteDoc, "Mutation batch %s applied to document %s resulted in nil.", batch,
|
|
|
|
|
|
remoteDoc);
|
2017-10-03 08:55:22 -07:00
|
|
|
|
} else {
|
2018-04-02 16:01:03 -07:00
|
|
|
|
[self.remoteDocumentCache addEntry:doc];
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2018-05-05 08:10:51 -07:00
|
|
|
|
}
|
2017-10-03 08:55:22 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
NS_ASSUME_NONNULL_END
|