aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firestore/Source/API/FIRDocumentReference.mm
diff options
context:
space:
mode:
Diffstat (limited to 'Firestore/Source/API/FIRDocumentReference.mm')
-rw-r--r--Firestore/Source/API/FIRDocumentReference.mm311
1 files changed, 311 insertions, 0 deletions
diff --git a/Firestore/Source/API/FIRDocumentReference.mm b/Firestore/Source/API/FIRDocumentReference.mm
new file mode 100644
index 0000000..05253f7
--- /dev/null
+++ b/Firestore/Source/API/FIRDocumentReference.mm
@@ -0,0 +1,311 @@
+/*
+ * 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.
+ */
+
+#import "FIRDocumentReference.h"
+
+#import <GRPCClient/GRPCCall.h>
+
+#import "FIRFirestoreErrors.h"
+#import "FIRSnapshotMetadata.h"
+#import "Firestore/Source/API/FIRCollectionReference+Internal.h"
+#import "Firestore/Source/API/FIRDocumentReference+Internal.h"
+#import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h"
+#import "Firestore/Source/API/FIRFirestore+Internal.h"
+#import "Firestore/Source/API/FIRListenerRegistration+Internal.h"
+#import "Firestore/Source/API/FIRSetOptions+Internal.h"
+#import "Firestore/Source/API/FSTUserDataConverter.h"
+#import "Firestore/Source/Core/FSTEventManager.h"
+#import "Firestore/Source/Core/FSTFirestoreClient.h"
+#import "Firestore/Source/Core/FSTQuery.h"
+#import "Firestore/Source/Model/FSTDocumentKey.h"
+#import "Firestore/Source/Model/FSTDocumentSet.h"
+#import "Firestore/Source/Model/FSTFieldValue.h"
+#import "Firestore/Source/Model/FSTMutation.h"
+#import "Firestore/Source/Model/FSTPath.h"
+#import "Firestore/Source/Util/FSTAssert.h"
+#import "Firestore/Source/Util/FSTAsyncQueryListener.h"
+#import "Firestore/Source/Util/FSTUsageValidation.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+#pragma mark - FIRDocumentListenOptions
+
+@interface FIRDocumentListenOptions ()
+
+- (instancetype)initWithIncludeMetadataChanges:(BOOL)includeMetadataChanges
+ NS_DESIGNATED_INITIALIZER;
+
+@property(nonatomic, assign, readonly) BOOL includeMetadataChanges;
+
+@end
+
+@implementation FIRDocumentListenOptions
+
++ (instancetype)options {
+ return [[FIRDocumentListenOptions alloc] init];
+}
+
+- (instancetype)initWithIncludeMetadataChanges:(BOOL)includeMetadataChanges {
+ if (self = [super init]) {
+ _includeMetadataChanges = includeMetadataChanges;
+ }
+ return self;
+}
+
+- (instancetype)init {
+ return [self initWithIncludeMetadataChanges:NO];
+}
+
+- (instancetype)includeMetadataChanges:(BOOL)includeMetadataChanges {
+ return [[FIRDocumentListenOptions alloc] initWithIncludeMetadataChanges:includeMetadataChanges];
+}
+
+@end
+
+#pragma mark - FIRDocumentReference
+
+@interface FIRDocumentReference ()
+- (instancetype)initWithKey:(FSTDocumentKey *)key
+ firestore:(FIRFirestore *)firestore NS_DESIGNATED_INITIALIZER;
+@property(nonatomic, strong, readonly) FSTDocumentKey *key;
+@end
+
+@implementation FIRDocumentReference (Internal)
+
++ (instancetype)referenceWithPath:(FSTResourcePath *)path firestore:(FIRFirestore *)firestore {
+ if (path.length % 2 != 0) {
+ FSTThrowInvalidArgument(
+ @"Invalid document reference. Document references must have an even "
+ "number of segments, but %@ has %d",
+ path.canonicalString, path.length);
+ }
+ return
+ [FIRDocumentReference referenceWithKey:[FSTDocumentKey keyWithPath:path] firestore:firestore];
+}
+
++ (instancetype)referenceWithKey:(FSTDocumentKey *)key firestore:(FIRFirestore *)firestore {
+ return [[FIRDocumentReference alloc] initWithKey:key firestore:firestore];
+}
+
+@end
+
+@implementation FIRDocumentReference
+
+- (instancetype)initWithKey:(FSTDocumentKey *)key firestore:(FIRFirestore *)firestore {
+ if (self = [super init]) {
+ _key = key;
+ _firestore = firestore;
+ }
+ return self;
+}
+
+#pragma mark - NSObject Methods
+
+- (BOOL)isEqual:(nullable id)other {
+ if (other == self) return YES;
+ if (![[other class] isEqual:[self class]]) return NO;
+
+ return [self isEqualToReference:other];
+}
+
+- (BOOL)isEqualToReference:(nullable FIRDocumentReference *)reference {
+ if (self == reference) return YES;
+ if (reference == nil) return NO;
+ return [self.firestore isEqual:reference.firestore] && [self.key isEqualToKey:reference.key];
+}
+
+- (NSUInteger)hash {
+ NSUInteger hash = [self.firestore hash];
+ hash = hash * 31u + [self.key hash];
+ return hash;
+}
+
+#pragma mark - Public Methods
+
+- (NSString *)documentID {
+ return [self.key.path lastSegment];
+}
+
+- (FIRCollectionReference *)parent {
+ FSTResourcePath *parentPath = [self.key.path pathByRemovingLastSegment];
+ return [FIRCollectionReference referenceWithPath:parentPath firestore:self.firestore];
+}
+
+- (NSString *)path {
+ return [self.key.path canonicalString];
+}
+
+- (FIRCollectionReference *)collectionWithPath:(NSString *)collectionPath {
+ if (!collectionPath) {
+ FSTThrowInvalidArgument(@"Collection path cannot be nil.");
+ }
+ FSTResourcePath *subPath = [FSTResourcePath pathWithString:collectionPath];
+ FSTResourcePath *path = [self.key.path pathByAppendingPath:subPath];
+ return [FIRCollectionReference referenceWithPath:path firestore:self.firestore];
+}
+
+- (void)setData:(NSDictionary<NSString *, id> *)documentData {
+ return [self setData:documentData options:[FIRSetOptions overwrite] completion:nil];
+}
+
+- (void)setData:(NSDictionary<NSString *, id> *)documentData options:(FIRSetOptions *)options {
+ return [self setData:documentData options:options completion:nil];
+}
+
+- (void)setData:(NSDictionary<NSString *, id> *)documentData
+ completion:(nullable void (^)(NSError *_Nullable error))completion {
+ return [self setData:documentData options:[FIRSetOptions overwrite] completion:completion];
+}
+
+- (void)setData:(NSDictionary<NSString *, id> *)documentData
+ options:(FIRSetOptions *)options
+ completion:(nullable void (^)(NSError *_Nullable error))completion {
+ FSTParsedSetData *parsed = options.isMerge
+ ? [self.firestore.dataConverter parsedMergeData:documentData]
+ : [self.firestore.dataConverter parsedSetData:documentData];
+ return [self.firestore.client
+ writeMutations:[parsed mutationsWithKey:self.key precondition:[FSTPrecondition none]]
+ completion:completion];
+}
+
+- (void)updateData:(NSDictionary<id, id> *)fields {
+ return [self updateData:fields completion:nil];
+}
+
+- (void)updateData:(NSDictionary<id, id> *)fields
+ completion:(nullable void (^)(NSError *_Nullable error))completion {
+ FSTParsedUpdateData *parsed = [self.firestore.dataConverter parsedUpdateData:fields];
+ return [self.firestore.client
+ writeMutations:[parsed mutationsWithKey:self.key
+ precondition:[FSTPrecondition preconditionWithExists:YES]]
+ completion:completion];
+}
+
+- (void)deleteDocument {
+ return [self deleteDocumentWithCompletion:nil];
+}
+
+- (void)deleteDocumentWithCompletion:(nullable void (^)(NSError *_Nullable error))completion {
+ FSTDeleteMutation *mutation =
+ [[FSTDeleteMutation alloc] initWithKey:self.key precondition:[FSTPrecondition none]];
+ return [self.firestore.client writeMutations:@[ mutation ] completion:completion];
+}
+
+- (void)getDocumentWithCompletion:(void (^)(FIRDocumentSnapshot *_Nullable document,
+ NSError *_Nullable error))completion {
+ FSTListenOptions *listenOptions =
+ [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:YES
+ includeDocumentMetadataChanges:YES
+ waitForSyncWhenOnline:YES];
+
+ dispatch_semaphore_t registered = dispatch_semaphore_create(0);
+ __block id<FIRListenerRegistration> listenerRegistration;
+ FIRDocumentSnapshotBlock listener = ^(FIRDocumentSnapshot *snapshot, NSError *error) {
+ if (error) {
+ completion(nil, error);
+ return;
+ }
+
+ // Remove query first before passing event to user to avoid user actions affecting the
+ // now stale query.
+ dispatch_semaphore_wait(registered, DISPATCH_TIME_FOREVER);
+ [listenerRegistration remove];
+
+ if (!snapshot.exists && snapshot.metadata.fromCache) {
+ // TODO(dimond): Reconsider how to raise missing documents when offline.
+ // If we're online and the document doesn't exist then we call the completion with
+ // a document with document.exists set to false. If we're offline however, we call the
+ // completion handler with an error. Two options:
+ // 1) Cache the negative response from the server so we can deliver that even when you're
+ // 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
+ userInfo:@{
+ NSLocalizedDescriptionKey :
+ @"Failed to get document because the client is offline.",
+ }]);
+ } else {
+ completion(snapshot, nil);
+ }
+ };
+
+ listenerRegistration =
+ [self addSnapshotListenerInternalWithOptions:listenOptions listener:listener];
+ dispatch_semaphore_signal(registered);
+}
+
+- (id<FIRListenerRegistration>)addSnapshotListener:(FIRDocumentSnapshotBlock)listener {
+ return [self addSnapshotListenerWithOptions:nil listener:listener];
+}
+
+- (id<FIRListenerRegistration>)addSnapshotListenerWithOptions:
+ (nullable FIRDocumentListenOptions *)options
+ listener:(FIRDocumentSnapshotBlock)listener {
+ return [self addSnapshotListenerInternalWithOptions:[self internalOptions:options]
+ listener:listener];
+}
+
+- (id<FIRListenerRegistration>)
+addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions
+ listener:(FIRDocumentSnapshotBlock)listener {
+ FIRFirestore *firestore = self.firestore;
+ FSTQuery *query = [FSTQuery queryWithPath:self.key.path];
+ FSTDocumentKey *key = self.key;
+
+ FSTViewSnapshotHandler snapshotHandler = ^(FSTViewSnapshot *snapshot, NSError *error) {
+ if (error) {
+ listener(nil, error);
+ return;
+ }
+
+ FSTAssert(snapshot.documents.count <= 1, @"Too many document returned on a document query");
+ FSTDocument *document = [snapshot.documents documentForKey:key];
+
+ FIRDocumentSnapshot *result = [FIRDocumentSnapshot snapshotWithFirestore:firestore
+ documentKey:key
+ document:document
+ fromCache:snapshot.fromCache];
+ listener(result, nil);
+ };
+
+ FSTAsyncQueryListener *asyncListener =
+ [[FSTAsyncQueryListener alloc] initWithDispatchQueue:self.firestore.client.userDispatchQueue
+ snapshotHandler:snapshotHandler];
+
+ FSTQueryListener *internalListener =
+ [firestore.client listenToQuery:query
+ options:internalOptions
+ viewSnapshotHandler:[asyncListener asyncSnapshotHandler]];
+ return [[FSTListenerRegistration alloc] initWithClient:self.firestore.client
+ asyncListener:asyncListener
+ internalListener:internalListener];
+}
+
+/** Converts the public API options object to the internal options object. */
+- (FSTListenOptions *)internalOptions:(nullable FIRDocumentListenOptions *)options {
+ return
+ [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:options.includeMetadataChanges
+ includeDocumentMetadataChanges:options.includeMetadataChanges
+ waitForSyncWhenOnline:NO];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END