diff options
Diffstat (limited to 'Firestore/Source')
-rw-r--r-- | Firestore/Source/API/FIRDocumentReference.mm | 25 | ||||
-rw-r--r-- | Firestore/Source/API/FIRQuery.mm | 38 | ||||
-rw-r--r-- | Firestore/Source/Core/FSTFirestoreClient.h | 22 | ||||
-rw-r--r-- | Firestore/Source/Core/FSTFirestoreClient.mm | 63 | ||||
-rw-r--r-- | Firestore/Source/Public/FIRDocumentReference.h | 19 | ||||
-rw-r--r-- | Firestore/Source/Public/FIRFirestoreSource.h | 48 | ||||
-rw-r--r-- | Firestore/Source/Public/FIRQuery.h | 20 |
7 files changed, 229 insertions, 6 deletions
diff --git a/Firestore/Source/API/FIRDocumentReference.mm b/Firestore/Source/API/FIRDocumentReference.mm index 2423472..c2fc546 100644 --- a/Firestore/Source/API/FIRDocumentReference.mm +++ b/Firestore/Source/API/FIRDocumentReference.mm @@ -22,6 +22,7 @@ #include <utility> #import "FIRFirestoreErrors.h" +#import "FIRFirestoreSource.h" #import "FIRSnapshotMetadata.h" #import "Firestore/Source/API/FIRCollectionReference+Internal.h" #import "Firestore/Source/API/FIRDocumentReference+Internal.h" @@ -162,6 +163,17 @@ NS_ASSUME_NONNULL_BEGIN - (void)getDocumentWithCompletion:(void (^)(FIRDocumentSnapshot *_Nullable document, NSError *_Nullable error))completion { + return [self getDocumentWithSource:FIRFirestoreSourceDefault completion:completion]; +} + +- (void)getDocumentWithSource:(FIRFirestoreSource)source + completion:(void (^)(FIRDocumentSnapshot *_Nullable document, + NSError *_Nullable error))completion { + if (source == FIRFirestoreSourceCache) { + [self.firestore.client getDocumentFromLocalCache:self completion:completion]; + return; + } + FSTListenOptions *options = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:YES includeDocumentMetadataChanges:YES waitForSyncWhenOnline:YES]; @@ -188,7 +200,6 @@ NS_ASSUME_NONNULL_BEGIN // offline. // 2) Actually call the completion handler with an error if the document doesn't exist when // you are offline. - // TODO(dimond): Use proper error domain completion(nil, [NSError errorWithDomain:FIRFirestoreErrorDomain code:FIRFirestoreErrorCodeUnavailable @@ -196,6 +207,18 @@ NS_ASSUME_NONNULL_BEGIN NSLocalizedDescriptionKey : @"Failed to get document because the client is offline.", }]); + } else if (snapshot.exists && snapshot.metadata.fromCache && + source == FIRFirestoreSourceServer) { + completion(nil, + [NSError errorWithDomain:FIRFirestoreErrorDomain + code:FIRFirestoreErrorCodeUnavailable + userInfo:@{ + NSLocalizedDescriptionKey : + @"Failed to get document from server. (However, this " + @"document does exist in the local cache. Run again " + @"without setting source to FIRFirestoreSourceServer to " + @"retrieve the cached document.)" + }]); } else { completion(snapshot, nil); } diff --git a/Firestore/Source/API/FIRQuery.mm b/Firestore/Source/API/FIRQuery.mm index 14dcaef..2d78ac0 100644 --- a/Firestore/Source/API/FIRQuery.mm +++ b/Firestore/Source/API/FIRQuery.mm @@ -17,6 +17,8 @@ #import "FIRQuery.h" #import "FIRDocumentReference.h" +#import "FIRFirestoreErrors.h" +#import "FIRFirestoreSource.h" #import "Firestore/Source/API/FIRDocumentReference+Internal.h" #import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h" #import "Firestore/Source/API/FIRFieldPath+Internal.h" @@ -96,9 +98,21 @@ NS_ASSUME_NONNULL_BEGIN - (void)getDocumentsWithCompletion:(void (^)(FIRQuerySnapshot *_Nullable snapshot, NSError *_Nullable error))completion { - FSTListenOptions *options = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:YES - includeDocumentMetadataChanges:YES - waitForSyncWhenOnline:YES]; + [self getDocumentsWithSource:FIRFirestoreSourceDefault completion:completion]; +} + +- (void)getDocumentsWithSource:(FIRFirestoreSource)source + completion:(void (^)(FIRQuerySnapshot *_Nullable snapshot, + NSError *_Nullable error))completion { + if (source == FIRFirestoreSourceCache) { + [self.firestore.client getDocumentsFromLocalCache:self completion:completion]; + return; + } + + FSTListenOptions *listenOptions = + [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:YES + includeDocumentMetadataChanges:YES + waitForSyncWhenOnline:YES]; dispatch_semaphore_t registered = dispatch_semaphore_create(0); __block id<FIRListenerRegistration> listenerRegistration; @@ -113,10 +127,24 @@ NS_ASSUME_NONNULL_BEGIN dispatch_semaphore_wait(registered, DISPATCH_TIME_FOREVER); [listenerRegistration remove]; - completion(snapshot, nil); + if (snapshot.metadata.fromCache && source == FIRFirestoreSourceServer) { + completion(nil, + [NSError errorWithDomain:FIRFirestoreErrorDomain + code:FIRFirestoreErrorCodeUnavailable + userInfo:@{ + NSLocalizedDescriptionKey : + @"Failed to get documents from server. (However, these " + @"documents may exist in the local cache. Run again " + @"without setting source to FIRFirestoreSourceServer to " + @"retrieve the cached documents.)" + }]); + } else { + completion(snapshot, nil); + } }; - listenerRegistration = [self addSnapshotListenerInternalWithOptions:options listener:listener]; + listenerRegistration = + [self addSnapshotListenerInternalWithOptions:listenOptions listener:listener]; dispatch_semaphore_signal(registered); } diff --git a/Firestore/Source/Core/FSTFirestoreClient.h b/Firestore/Source/Core/FSTFirestoreClient.h index 6da5ed3..7285e65 100644 --- a/Firestore/Source/Core/FSTFirestoreClient.h +++ b/Firestore/Source/Core/FSTFirestoreClient.h @@ -24,6 +24,12 @@ #include "Firestore/core/src/firebase/firestore/core/database_info.h" #include "Firestore/core/src/firebase/firestore/model/database_id.h" +@class FIRDocumentReference; +@class FIRDocumentSnapshot; +@class FIRQuery; +@class FIRQuerySnapshot; +@class FSTDatabaseID; +@class FSTDatabaseInfo; @class FSTDispatchQueue; @class FSTDocument; @class FSTListenOptions; @@ -72,6 +78,22 @@ NS_ASSUME_NONNULL_BEGIN /** Stops listening to a query previously listened to. */ - (void)removeListener:(FSTQueryListener *)listener; +/** + * Retrieves a document from the cache via the indicated completion. If the doc + * doesn't exist, an error will be sent to the completion. + */ +- (void)getDocumentFromLocalCache:(FIRDocumentReference *)doc + completion:(void (^)(FIRDocumentSnapshot *_Nullable document, + NSError *_Nullable error))completion; + +/** + * Retrieves a (possibly empty) set of documents from the cache via the + * indicated completion. + */ +- (void)getDocumentsFromLocalCache:(FIRQuery *)query + completion:(void (^)(FIRQuerySnapshot *_Nullable query, + NSError *_Nullable error))completion; + /** Write mutations. completion will be notified when it's written to the backend. */ - (void)writeMutations:(NSArray<FSTMutation *> *)mutations completion:(nullable FSTVoidErrorBlock)completion; diff --git a/Firestore/Source/Core/FSTFirestoreClient.mm b/Firestore/Source/Core/FSTFirestoreClient.mm index 33d1903..4f1a20b 100644 --- a/Firestore/Source/Core/FSTFirestoreClient.mm +++ b/Firestore/Source/Core/FSTFirestoreClient.mm @@ -19,15 +19,25 @@ #include <future> // NOLINT(build/c++11) #include <memory> +#import "FIRFirestoreErrors.h" +#import "Firestore/Source/API/FIRDocumentReference+Internal.h" +#import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h" +#import "Firestore/Source/API/FIRQuery+Internal.h" +#import "Firestore/Source/API/FIRQuerySnapshot+Internal.h" +#import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h" #import "Firestore/Source/Core/FSTEventManager.h" +#import "Firestore/Source/Core/FSTQuery.h" #import "Firestore/Source/Core/FSTSyncEngine.h" #import "Firestore/Source/Core/FSTTransaction.h" +#import "Firestore/Source/Core/FSTView.h" #import "Firestore/Source/Local/FSTEagerGarbageCollector.h" #import "Firestore/Source/Local/FSTLevelDB.h" #import "Firestore/Source/Local/FSTLocalSerializer.h" #import "Firestore/Source/Local/FSTLocalStore.h" #import "Firestore/Source/Local/FSTMemoryPersistence.h" #import "Firestore/Source/Local/FSTNoOpGarbageCollector.h" +#import "Firestore/Source/Model/FSTDocument.h" +#import "Firestore/Source/Model/FSTDocumentSet.h" #import "Firestore/Source/Remote/FSTDatastore.h" #import "Firestore/Source/Remote/FSTRemoteStore.h" #import "Firestore/Source/Remote/FSTSerializerBeta.h" @@ -271,6 +281,59 @@ NS_ASSUME_NONNULL_BEGIN }]; } +- (void)getDocumentFromLocalCache:(FIRDocumentReference *)doc + completion:(void (^)(FIRDocumentSnapshot *_Nullable document, + NSError *_Nullable error))completion { + [self.workerDispatchQueue dispatchAsync:^{ + FSTMaybeDocument *maybeDoc = [self.localStore readDocument:doc.key]; + if (maybeDoc) { + completion([FIRDocumentSnapshot snapshotWithFirestore:doc.firestore + documentKey:doc.key + document:(FSTDocument *)maybeDoc + fromCache:YES], + nil); + } else { + completion(nil, + [NSError errorWithDomain:FIRFirestoreErrorDomain + code:FIRFirestoreErrorCodeUnavailable + userInfo:@{ + NSLocalizedDescriptionKey : + @"Failed to get document from cache. (However, this " + @"document may exist on the server. Run again without " + @"setting source to FIRFirestoreSourceCache to attempt to " + @"retrieve the document from the server.)", + }]); + } + }]; +} + +- (void)getDocumentsFromLocalCache:(FIRQuery *)query + completion:(void (^)(FIRQuerySnapshot *_Nullable query, + NSError *_Nullable error))completion { + [self.workerDispatchQueue dispatchAsync:^{ + + FSTDocumentDictionary *docs = [self.localStore executeQuery:query.query]; + FSTDocumentKeySet *remoteKeys = [FSTDocumentKeySet keySet]; + + FSTView *view = [[FSTView alloc] initWithQuery:query.query remoteDocuments:remoteKeys]; + FSTViewDocumentChanges *viewDocChanges = [view computeChangesWithDocuments:docs]; + FSTViewChange *viewChange = [view applyChangesToDocuments:viewDocChanges]; + FSTAssert(viewChange.limboChanges.count == 0, + @"View returned limbo documents during local-only query execution."); + + FSTViewSnapshot *snapshot = viewChange.snapshot; + FIRSnapshotMetadata *metadata = + [FIRSnapshotMetadata snapshotMetadataWithPendingWrites:snapshot.hasPendingWrites + fromCache:snapshot.fromCache]; + + completion([FIRQuerySnapshot snapshotWithFirestore:query.firestore + originalQuery:query.query + snapshot:snapshot + metadata:metadata], + nil); + }]; +} + - (void)writeMutations:(NSArray<FSTMutation *> *)mutations completion:(nullable FSTVoidErrorBlock)completion { [self.workerDispatchQueue dispatchAsync:^{ diff --git a/Firestore/Source/Public/FIRDocumentReference.h b/Firestore/Source/Public/FIRDocumentReference.h index e7ba6eb..4aa8c45 100644 --- a/Firestore/Source/Public/FIRDocumentReference.h +++ b/Firestore/Source/Public/FIRDocumentReference.h @@ -16,6 +16,7 @@ #import <Foundation/Foundation.h> +#import "FIRFirestoreSource.h" #import "FIRListenerRegistration.h" @class FIRCollectionReference; @@ -166,12 +167,30 @@ NS_SWIFT_NAME(DocumentReference) /** * Reads the document referenced by this `FIRDocumentReference`. * + * This method attempts to provide up-to-date data when possible by waiting for + * data from the server, but it may return cached data or fail if you are + * offline and the server cannot be reached. See the + * `getDocument(source:completion:)` method to change this behavior. + * * @param completion a block to execute once the document has been successfully read. */ - (void)getDocumentWithCompletion:(FIRDocumentSnapshotBlock)completion NS_SWIFT_NAME(getDocument(completion:)); /** + * Reads the document referenced by this `FIRDocumentReference`. + * + * @param source indicates whether the results should be fetched from the cache + * only (`Source.cache`), the server only (`Source.server`), or to attempt + * the server and fall back to the cache (`Source.default`). + * @param completion a block to execute once the document has been successfully read. + */ +// clang-format off +- (void)getDocumentWithSource:(FIRFirestoreSource)source completion:(FIRDocumentSnapshotBlock)completion + NS_SWIFT_NAME(getDocument(source:completion:)); +// clang-format on + +/** * Attaches a listener for DocumentSnapshot events. * * @param listener The listener to attach. diff --git a/Firestore/Source/Public/FIRFirestoreSource.h b/Firestore/Source/Public/FIRFirestoreSource.h new file mode 100644 index 0000000..c133747 --- /dev/null +++ b/Firestore/Source/Public/FIRFirestoreSource.h @@ -0,0 +1,48 @@ +/* + * Copyright 2018 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. + */ + +#import <Foundation/Foundation.h> + +/** + * An enum that configures the behavior of `DocumentReference.getDocument()` and + * `Query.getDocuments()`. By providing a source enum the `getDocument[s]` + * methods can be configured to fetch results only from the server, only from + * the local cache, or attempt to fetch results from the server and fall back to + * the cache (which is the default). + * + * Setting the source to `Source.default` causes Firestore to try to retrieve an + * up-to-date (server-retrieved) snapshot, but fall back to returning cached + * data if the server can't be reached. + * + * Setting the source to `Source.server` causes Firestore to avoid the cache, + * generating an error if the server cannot be reached. Note that the cache will + * still be updated if the server request succeeds. Also note that + * latency-compensation still takes effect, so any pending write operations will + * be visible in the returned data (merged into the server-provided data). + * + * Setting the source to `Source.cache` causes Firestore to immediately return a + * value from the cache, ignoring the server completely (implying that the + * returned value may be stale with respect to the value on the server). If + * there is no data in the cache to satisfy the `getDocument[s]` call, + * `DocumentReference.getDocument()` will return an error and + * `QuerySnapshot.getDocuments()` will return an empty `QuerySnapshot` with no + * documents. + */ +typedef NS_ENUM(NSUInteger, FIRFirestoreSource) { + FIRFirestoreSourceDefault, + FIRFirestoreSourceServer, + FIRFirestoreSourceCache +} NS_SWIFT_NAME(FirestoreSource); diff --git a/Firestore/Source/Public/FIRQuery.h b/Firestore/Source/Public/FIRQuery.h index a28af39..946cf06 100644 --- a/Firestore/Source/Public/FIRQuery.h +++ b/Firestore/Source/Public/FIRQuery.h @@ -16,6 +16,7 @@ #import <Foundation/Foundation.h> +#import "FIRFirestoreSource.h" #import "FIRListenerRegistration.h" @class FIRFieldPath; @@ -44,6 +45,11 @@ NS_SWIFT_NAME(Query) /** * Reads the documents matching this query. * + * This method attempts to provide up-to-date data when possible by waiting for + * data from the server, but it may return cached data or fail if you are + * offline and the server cannot be reached. See the + * `getDocuments(source:completion:)` method to change this behavior. + * * @param completion a block to execute once the documents have been successfully read. * documentSet will be `nil` only if error is `non-nil`. */ @@ -51,6 +57,20 @@ NS_SWIFT_NAME(Query) NS_SWIFT_NAME(getDocuments(completion:)); /** + * Reads the documents matching this query. + * + * @param source indicates whether the results should be fetched from the cache + * only (`Source.cache`), the server only (`Source.server`), or to attempt + * the server and fall back to the cache (`Source.default`). + * @param completion a block to execute once the documents have been successfully read. + * documentSet will be `nil` only if error is `non-nil`. + */ +// clang-format off +- (void)getDocumentsWithSource:(FIRFirestoreSource)source completion:(FIRQuerySnapshotBlock)completion + NS_SWIFT_NAME(getDocuments(source:completion:)); +// clang-format on + +/** * Attaches a listener for QuerySnapshot events. * * @param listener The listener to attach. |