From 23159538c39de2527e2bcef424887f41c95d6c7a Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Thu, 2 Mar 2017 23:28:14 -0800 Subject: [PATCH] Fix bugs related to Subdocument and Blob * Fixed Subdocument bug when replacing with date value in document.properties. * Fixed CBLBlob detection in CBLDocument. * Added more unit tests. #1626 --- Objective-C/CBLDocument.mm | 21 ++++-- Objective-C/CBLProperties.mm | 16 ++--- Objective-C/Tests/SubdocumentTest.m | 104 +++++++++++++++++++++++++++- Swift/Tests/SubdocumentTest.swift | 78 ++++++++++++++++++++- 4 files changed, 203 insertions(+), 16 deletions(-) diff --git a/Objective-C/CBLDocument.mm b/Objective-C/CBLDocument.mm index 710aeabc3..9e74e89ec 100644 --- a/Objective-C/CBLDocument.mm +++ b/Objective-C/CBLDocument.mm @@ -22,6 +22,7 @@ #import "CBLJSON.h" #import "CBLSharedKeys.hh" #import "CBLStringBytes.h" +#import "CBLSubdocument.h" NSString* const kCBLDocumentChangeNotification = @"CBLDocumentChangeNotification"; @@ -241,8 +242,10 @@ - (void) setC4Doc: (nullable C4Document*)doc { // The next three functions search recursively for a property "_cbltype":"blob". static bool objectContainsBlob(__unsafe_unretained id value) { - if ([value isKindOfClass: [NSDictionary class]]) - return dictContainsBlob(value); + if ([value isKindOfClass: [CBLBlob class]]) + return true; + else if ([value isKindOfClass: [CBLSubdocument class]]) + return subdocContainsBlob(value); else if ([value isKindOfClass: [NSArray class]]) return arrayContainsBlob(value); else @@ -256,9 +259,15 @@ static bool arrayContainsBlob(__unsafe_unretained NSArray* array) { return false; } -static bool dictContainsBlob(__unsafe_unretained NSDictionary* dict) { - if ([dict[@"_cbltype"] isEqual: @"blob"]) - return true; +static bool subdocContainsBlob(__unsafe_unretained CBLSubdocument* subdoc) { + __block bool containsBlob = false; + [subdoc.properties enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) { + *stop = containsBlob = objectContainsBlob(value); + }]; + return containsBlob; +} + +static bool containsBlob(__unsafe_unretained NSDictionary* dict) { __block bool containsBlob = false; [dict enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) { *stop = containsBlob = objectContainsBlob(value); @@ -283,7 +292,7 @@ - (BOOL) saveInto: (C4Document **)outDoc }; if (deletion) put.revFlags = kRevDeleted; - if (dictContainsBlob(propertiesToSave)) + if (containsBlob(propertiesToSave)) put.revFlags |= kRevHasAttachments; if (propertiesToSave.count > 0) { // Encode properties to Fleece data: diff --git a/Objective-C/CBLProperties.mm b/Objective-C/CBLProperties.mm index 9eae66f15..c580e3cf8 100644 --- a/Objective-C/CBLProperties.mm +++ b/Objective-C/CBLProperties.mm @@ -457,18 +457,18 @@ - (id) convertValue: (nullable id)value oldValue: (nullable id)oldValue forKey: if ($equal(value, oldValue)) return value; // nothing to convert - if ([value isKindOfClass: [NSDate class]]) - return [CBLJSON JSONObjectWithDate: value]; - else if ([value isKindOfClass: [CBLSubdocument class]]) + if ([value isKindOfClass: [CBLSubdocument class]]) return [self convertSubdoc: value oldValue: oldValue forKey: key]; else if ([value isKindOfClass: [NSDictionary class]]) return [self convertDict: value oldValue: oldValue forKey: key]; else if ([value isKindOfClass: [NSArray class]]) return [self convertArray: value oldVale: oldValue forKey: key]; - else { - [self invalidateIfSubdocument: oldValue]; - return value; - } + else if ([value isKindOfClass: [NSDate class]]) + value = [CBLJSON JSONObjectWithDate: value]; + + [self invalidateIfSubdocument: oldValue]; + + return value; } @@ -529,7 +529,7 @@ - (id) convertArray: (NSArray*)array oldVale: (nullable id)oldValue forKey: (NSS id nValue = array[i]; id oValue = [oldArray count] > i ? oldArray[i] : nil; - // FIXME: Array can be nested, using a simple arraySet is not enough + // FIXME: Array can be nested, using a simple arraySet is not enough. if ([oValue isKindOfClass: [CBLSubdocument class]] && [arraySet containsObject: oValue]) { // Prevent the subdocument to be invalidated so the subdocument can be reordered: oValue = nil; diff --git a/Objective-C/Tests/SubdocumentTest.m b/Objective-C/Tests/SubdocumentTest.m index 9f221edce..d5bc05256 100644 --- a/Objective-C/Tests/SubdocumentTest.m +++ b/Objective-C/Tests/SubdocumentTest.m @@ -396,7 +396,7 @@ - (void) testSetDocumentPropertiesNil { } -- (void) testReplaceSubdocumentWithNonDict { +- (void) testReplaceWithNonDict { CBLSubdocument* address = [CBLSubdocument subdocument]; address[@"street"] = @"1 Star Way."; AssertEqualObjects(address[@"street"], @"1 Star Way."); @@ -413,6 +413,108 @@ - (void) testReplaceSubdocumentWithNonDict { } +- (void) testReplaceWithNewSubdocument { + CBLSubdocument* address = [CBLSubdocument subdocument]; + address[@"street"] = @"1 Star Way."; + AssertEqualObjects(address[@"street"], @"1 Star Way."); + AssertEqualObjects(address.properties, (@{@"street": @"1 Star Way."})); + + doc[@"address"] = address; + AssertEqualObjects(doc[@"address"], address); + AssertEqualObjects(address.document, doc); + + CBLSubdocument* nuAddress = [CBLSubdocument subdocument]; + address[@"street"] = @"123 Space Dr."; + doc[@"address"] = nuAddress; + + AssertEqualObjects(doc[@"address"], nuAddress); + AssertNil(address.document); + AssertNil(address.properties); +} + + +- (void) testReplaceWithNewDocProperties { + doc.properties = @{ @"name": @"Jason", + @"address": @{ + @"street": @"1 Star Way.", + @"phones": @{@"mobile": @"650-123-4567"} + }, + @"work": @{@"company": @"Couchbase"}, + @"subscription": @{@"type": @"silver"}, + @"expiration": @{@"date": @"2017-03-03T07:13:46.536Z"}, + @"references": @[@{@"name": @"Scott"}, @{@"name": @"Sam"}] + }; + + CBLSubdocument* address = doc[@"address"]; + CBLSubdocument* phones = address[@"phones"]; + AssertNotNil(address); + AssertNotNil(phones); + + CBLSubdocument* work = doc[@"work"]; + AssertNotNil(work); + + CBLSubdocument* subscription = doc[@"subscription"]; + AssertNotNil(subscription); + + CBLSubdocument* expiration = doc[@"expiration"]; + AssertNotNil(expiration); + + NSArray* references = doc[@"references"]; + AssertEqual([references count], 2u); + CBLSubdocument* r1 = references[0]; + CBLSubdocument* r2 = references[1]; + AssertNotNil(r1); + AssertNotNil(r2); + + CBLSubdocument* nuSubscription = [CBLSubdocument subdocument]; + nuSubscription[@"type"] = @"platinum"; + + NSDate* date = [NSDate date]; + doc.properties = @{ @"name": @"Jason", + @"address": @"1 Star Way.", + @"work": @{ @"company": @"Couchbase", @"position": @"Engineer" }, + @"subscription": nuSubscription, + @"expiration": date, + @"references": @[@{@"name": @"Smith"}] + }; + + AssertEqualObjects(doc[@"address"], @"1 Star Way."); + AssertNil(address.document); + AssertNil(address.parent); + AssertNil(address.properties); + + AssertNil(phones.document); + AssertNil(phones.parent); + AssertNil(phones.properties); + + AssertEqual(doc[@"work"], work); + AssertEqualObjects(work[@"company"], @"Couchbase"); + AssertEqualObjects(work[@"position"], @"Engineer"); + + AssertEqualObjects(doc[@"subscription"], nuSubscription); + AssertEqualObjects(nuSubscription[@"type"], @"platinum"); + AssertNil(subscription.document); + AssertNil(subscription.parent); + AssertNil(subscription.properties); + + AssertEqualObjects([CBLJSON JSONObjectWithDate: [doc dateForKey: @"expiration"]], + [CBLJSON JSONObjectWithDate: date]); + AssertNil(expiration.document); + AssertNil(expiration.parent); + AssertNil(expiration.properties); + + references = doc[@"references"]; + AssertEqual([references count], 1u); + AssertEqualObjects(references[0], r1); + AssertEqualObjects(r1.document, doc); + AssertEqualObjects(r1.parent, doc); + AssertEqualObjects(r1[@"name"], @"Smith"); + AssertNil(r2.document); + AssertNil(r2.parent); + AssertNil(r2.properties); +} + + - (void) testDeleteDocument { doc.properties = @{ @"name": @"Jason", @"address": @{ diff --git a/Swift/Tests/SubdocumentTest.swift b/Swift/Tests/SubdocumentTest.swift index 4c8b0e718..4272339da 100644 --- a/Swift/Tests/SubdocumentTest.swift +++ b/Swift/Tests/SubdocumentTest.swift @@ -335,7 +335,7 @@ class SubdocumentTest: CBLTestCase { XCTAssertNil(r2.property("name")) } - func testReplaceSubdocumentWithNonDict() { + func testReplaceWithNonDict() { let address = Subdocument() address["street"] = "1 Star Way." @@ -349,6 +349,82 @@ class SubdocumentTest: CBLTestCase { XCTAssertNil(address.properties) } + func testReplaceWithNewSubdocument() { + let address = Subdocument() + address["street"] = "1 Star Way." + + doc["address"] = address + XCTAssert(doc.property("address") as? Subdocument === address) + XCTAssert(address.document === doc) + + let nuAddress = Subdocument() + nuAddress["street"] = "123 Space Dr." + doc["address"] = nuAddress + + XCTAssert(doc.property("address") as? Subdocument === nuAddress) + XCTAssertNil(address.document) + XCTAssertNil(address.properties) + } + + func testReplaceWithNewDocProperties() { + doc.properties = ["name": "Jason", + "address": [ + "street": "1 Star Way.", + "phones":["mobile": "650-123-4567"]], + "work": ["company": "Couchbase"], + "subscription": ["type": "silver"], + "expiration": ["date": "2017-03-03T07:13:46.536Z"], + "references": [["name": "Scott"], ["name": "Sam"]]] + + let address: Subdocument = doc["address"]! + let phones: Subdocument = address["phones"]! + let work: Subdocument = doc["work"]! + let subscription: Subdocument = doc["subscription"]! + let expiration: Subdocument = doc["expiration"]! + + var references: [Any] = doc["references"]! + let r1 = references[0] as! Subdocument + let r2 = references[1] as! Subdocument + + let nuSubscription = Subdocument() + nuSubscription["type"] = "platinum" + + let date = Date() + doc.properties = ["name": "Jason", + "address": "1 Star Way.", + "work": ["company": "Couchbase", "position": "Engineer"], + "subscription": nuSubscription, + "expiration": date, + "references": [["name": "Smith"]]] + + XCTAssertEqual(doc["address"], "1 Star Way.") + XCTAssertNil(address.document) + XCTAssertNil(address.properties) + + XCTAssertNil(phones.document) + XCTAssertNil(phones.properties) + + XCTAssert(doc.property("work") as? Subdocument === work) + XCTAssertEqual(work["company"], "Couchbase") + XCTAssertEqual(work["position"], "Engineer") + + XCTAssert(doc.property("subscription") as? Subdocument === nuSubscription) + XCTAssertNil(subscription.document) + XCTAssertNil(subscription.properties) + + XCTAssertNotNil(doc.property("expiration")) + XCTAssertNil(doc.property("expiration") as? Subdocument) + XCTAssertNil(expiration.document) + XCTAssertNil(expiration.properties) + + references = doc["references"]! + XCTAssertEqual(references.count, 1) + XCTAssert(references[0] as! Subdocument === r1) + XCTAssertEqual(r1["name"], "Smith") + XCTAssertNil(r2.document) + XCTAssertNil(r2.properties) + } + func testDeleteDocument() throws { doc.properties = ["name": "Jason", "address": [