diff options
author | Sebastian Schmidt <mrschmidt@google.com> | 2018-06-07 13:57:01 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-06-07 13:57:01 -0700 |
commit | d13a51abace9e1510ae953079f98e7b1390b128b (patch) | |
tree | ce7fb57b7cdde1c893491e5106217a5e9e5482d2 | |
parent | 7b2aa01da3df89dbea23b7c73202c6bf3f5813d3 (diff) |
Allowing field deletes using merge fields (#1389)
-rw-r--r-- | Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm | 82 | ||||
-rw-r--r-- | Firestore/Source/API/FSTUserDataConverter.mm | 22 |
2 files changed, 101 insertions, 3 deletions
diff --git a/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm b/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm index f8091c0..e048a40 100644 --- a/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm +++ b/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm @@ -162,8 +162,10 @@ NSDictionary<NSString *, id> *initialData = @{ @"updated" : @NO, }; - NSDictionary<NSString *, id> *mergeData = - @{@"time" : [FIRFieldValue fieldValueForServerTimestamp]}; + NSDictionary<NSString *, id> *mergeData = @{ + @"time" : [FIRFieldValue fieldValueForServerTimestamp], + @"nested" : @{@"time" : [FIRFieldValue fieldValueForServerTimestamp]} + }; [self writeDocumentRef:doc data:initialData]; @@ -182,6 +184,7 @@ FIRDocumentSnapshot *document = [self readDocumentForRef:doc]; XCTAssertEqual(document[@"updated"], @NO); XCTAssertTrue([document[@"time"] isKindOfClass:[FIRTimestamp class]]); + XCTAssertTrue([document[@"nested.time"] isKindOfClass:[FIRTimestamp class]]); } - (void)testCanDeleteFieldUsingMerge { @@ -218,6 +221,81 @@ XCTAssertNil(document[@"nested.foo"]); } +- (void)testCanDeleteFieldUsingMergeFields { + FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID]; + + NSDictionary<NSString *, id> *initialData = @{ + @"untouched" : @YES, + @"foo" : @"bar", + @"inner" : @{@"removed" : @YES, @"foo" : @"bar"}, + @"nested" : @{@"untouched" : @YES, @"foo" : @"bar"} + }; + NSDictionary<NSString *, id> *mergeData = @{ + @"foo" : [FIRFieldValue fieldValueForDelete], + @"inner" : @{@"foo" : [FIRFieldValue fieldValueForDelete]}, + @"nested" : @{ + @"untouched" : [FIRFieldValue fieldValueForDelete], + @"foo" : [FIRFieldValue fieldValueForDelete] + } + }; + NSDictionary<NSString *, id> *finalData = + @{ @"untouched" : @YES, + @"inner" : @{}, + @"nested" : @{@"untouched" : @YES} }; + + [self writeDocumentRef:doc data:initialData]; + + XCTestExpectation *completed = + [self expectationWithDescription:@"testCanMergeDataWithAnExistingDocumentUsingSet"]; + + [doc setData:mergeData + mergeFields:@[ @"foo", @"inner", @"nested.foo" ] + completion:^(NSError *error) { + XCTAssertNil(error); + [completed fulfill]; + }]; + + [self awaitExpectations]; + + FIRDocumentSnapshot *document = [self readDocumentForRef:doc]; + XCTAssertEqualObjects([document data], finalData); +} + +- (void)testCanSetServerTimestampsUsingMergeFields { + FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID]; + + NSDictionary<NSString *, id> *initialData = @{ + @"untouched" : @YES, + @"foo" : @"bar", + @"nested" : @{@"untouched" : @YES, @"foo" : @"bar"} + }; + NSDictionary<NSString *, id> *mergeData = @{ + @"foo" : [FIRFieldValue fieldValueForServerTimestamp], + @"inner" : @{@"foo" : [FIRFieldValue fieldValueForServerTimestamp]}, + @"nested" : @{@"foo" : [FIRFieldValue fieldValueForServerTimestamp]} + }; + + [self writeDocumentRef:doc data:initialData]; + + XCTestExpectation *completed = + [self expectationWithDescription:@"testCanMergeDataWithAnExistingDocumentUsingSet"]; + + [doc setData:mergeData + mergeFields:@[ @"foo", @"inner", @"nested.foo" ] + completion:^(NSError *error) { + XCTAssertNil(error); + [completed fulfill]; + }]; + + [self awaitExpectations]; + + FIRDocumentSnapshot *document = [self readDocumentForRef:doc]; + XCTAssertTrue([document exists]); + XCTAssertTrue([document[@"foo"] isKindOfClass:[FIRTimestamp class]]); + XCTAssertTrue([document[@"inner.foo"] isKindOfClass:[FIRTimestamp class]]); + XCTAssertTrue([document[@"nested.foo"] isKindOfClass:[FIRTimestamp class]]); +} + - (void)testMergeReplacesArrays { FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID]; diff --git a/Firestore/Source/API/FSTUserDataConverter.mm b/Firestore/Source/API/FSTUserDataConverter.mm index d73a870..d3e339d 100644 --- a/Firestore/Source/API/FSTUserDataConverter.mm +++ b/Firestore/Source/API/FSTUserDataConverter.mm @@ -218,6 +218,9 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) { /** Returns true for the non-query parse contexts (Set, MergeSet and Update) */ - (BOOL)isWrite; +/** Returns 'YES' if 'fieldPath' was traversed when creating this context. */ +- (BOOL)containsFieldPath:(const FieldPath &)fieldPath; + - (const FieldPath *)path; - (const std::vector<FieldPath> *)fieldMask; @@ -331,6 +334,22 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) { } } +- (BOOL)containsFieldPath:(const FieldPath &)fieldPath { + for (const FieldPath &field : *_fieldMask) { + if (fieldPath.IsPrefixOf(field)) { + return YES; + } + } + + for (const FieldTransform &fieldTransform : *_fieldTransforms) { + if (fieldPath.IsPrefixOf(fieldTransform.path())) { + return YES; + } + } + + return NO; +} + - (void)validatePath { // TODO(b/34871131): Remove nil check once we have proper paths for fields within arrays. if (_path == nullptr) { @@ -444,7 +463,8 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) { @"All elements in mergeFields: must be NSStrings or FIRFieldPaths."); } - if ([updateData valueForPath:path] == nil) { + // Verify that all elements specified in the field mask are part of the parsed context. + if (![context containsFieldPath:path]) { FSTThrowInvalidArgument( @"Field '%s' is specified in your field mask but missing from your input data.", path.CanonicalString().c_str()); |