aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firestore
diff options
context:
space:
mode:
authorGravatar Sebastian Schmidt <mrschmidt@google.com>2018-06-07 13:57:01 -0700
committerGravatar GitHub <noreply@github.com>2018-06-07 13:57:01 -0700
commitd13a51abace9e1510ae953079f98e7b1390b128b (patch)
treece7fb57b7cdde1c893491e5106217a5e9e5482d2 /Firestore
parent7b2aa01da3df89dbea23b7c73202c6bf3f5813d3 (diff)
Allowing field deletes using merge fields (#1389)
Diffstat (limited to 'Firestore')
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm82
-rw-r--r--Firestore/Source/API/FSTUserDataConverter.mm22
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());