From 1596c0133f78dee9fba2538fa68cad7777ea12d5 Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Tue, 7 Apr 2015 17:14:31 -0700 Subject: [PATCH] Add CBLIS support for many-to-many relationship - Store embeded array of doc ids in the documents for many-to-many relationship. - Bug fix: add relationship name as part of to-many inverse key view name. - Small code clean up. #638 --- Source/API/Extras/CBLIncrementalStore.m | 171 +++++++++++------ Unit-Tests/IncrementalStore_Tests.m | 244 +++++++++++++++++++++--- 2 files changed, 328 insertions(+), 87 deletions(-) diff --git a/Source/API/Extras/CBLIncrementalStore.m b/Source/API/Extras/CBLIncrementalStore.m index 9e9993928..e18fde051 100644 --- a/Source/API/Extras/CBLIncrementalStore.m +++ b/Source/API/Extras/CBLIncrementalStore.m @@ -33,14 +33,11 @@ static NSString* const kCBLISDefaultTypeKey = @"type"; static NSString* const kCBLISOldDefaultTypeKey = @"CBLIS_type"; - static NSString* const kCBLISMetadata_DefaultTypeKey = @"type_key"; - static NSString* const kCBLISCurrentRevisionAttributeName = @"CBLIS_Rev"; static NSString* const kCBLISManagedObjectIDPrefix = @"CBL"; static NSString* const kCBLISMetadataDocumentID = @"CBLIS_metadata"; -static NSString* const kCBLISFetchEntityByPropertyViewNameFormat = @"CBLIS/fetch_%@_by_%@"; -static NSString* const kCBLISFetchEntityToManyViewNameFormat = @"CBLIS/%@_tomany_%@"; +static NSString* const kCBLISToManyViewNameFormat = @"CBLIS/%@_%@_%@"; // Utility functions static BOOL CBLISIsNull(id value); @@ -551,23 +548,37 @@ - (id) newValueForRelationship: (NSRelationshipDescription*)relationship withContext: (NSManagedObjectContext*)context error: (NSError**)outError { if ([relationship isToMany]) { - CBLQueryEnumerator* rows = [self queryToManyRelation: relationship - forParentKeys: @[[objectID couchbaseLiteIDRepresentation]] - prefetch: NO - outError: outError]; - if (!rows) return nil; - NSMutableArray* result = [NSMutableArray arrayWithCapacity: rows.count]; - for (CBLQueryRow* row in rows) { - [result addObject: [self newObjectIDForEntity: relationship.destinationEntity - managedObjectContext: context - couchID: row.documentID]]; + if (relationship.inverseRelationship.toMany) { + // many-to-many + CBLDocument* doc = [self.database documentWithID: [objectID couchbaseLiteIDRepresentation]]; + NSArray* destinationIDs = [doc.properties valueForKey: relationship.name]; + NSMutableArray* result = [NSMutableArray arrayWithCapacity: destinationIDs.count]; + for (NSString* destinationID in destinationIDs) { + [result addObject:[self newObjectIDForEntity: relationship.destinationEntity + referenceObject: destinationID]]; + } + return result; + } else { + // one-to-many + CBLQueryEnumerator* rows = [self queryToManyRelation: relationship + forParentKeys: @[[objectID couchbaseLiteIDRepresentation]] + prefetch: NO + outError: outError]; + if (!rows) return nil; + NSMutableArray* result = [NSMutableArray arrayWithCapacity: rows.count]; + for (CBLQueryRow* row in rows) { + [result addObject: [self newObjectIDForEntity: relationship.destinationEntity + managedObjectContext: context + couchID: row.documentID]]; + } + return result; } - return result; } else { CBLDocument* doc = [self.database documentWithID: [objectID couchbaseLiteIDRepresentation]]; NSString* destinationID = [doc propertyForKey: relationship.name]; if (destinationID) { - return [self newObjectIDForEntity: relationship.destinationEntity referenceObject: destinationID]; + return [self newObjectIDForEntity: relationship.destinationEntity + referenceObject: destinationID]; } else { return [NSNull null]; } @@ -615,7 +626,7 @@ - (NSManagedObjectID*) newObjectIDForEntity: (NSEntityDescription*)entity #pragma mark - Document Type Key -- (NSString *)documentTypeKey { +- (NSString*)documentTypeKey { if (_documentTypeKey) return _documentTypeKey; @@ -648,6 +659,9 @@ - (void) initializeViews { if ([property isKindOfClass:[NSRelationshipDescription class]]) { NSRelationshipDescription* rel = (NSRelationshipDescription*)property; if (rel.isToMany && rel.inverseRelationship) { + if (rel.inverseRelationship.toMany) // skip many-to-many + continue; + NSMutableArray* entityNames = [NSMutableArray arrayWithObject: rel.destinationEntity.name]; for (NSEntityDescription* subentity in rel.destinationEntity.subentities) { @@ -656,13 +670,13 @@ - (void) initializeViews { NSString* viewName = CBLISToManyViewNameForRelationship(rel); NSRelationshipDescription* invRel = rel.inverseRelationship; - NSString* invertRelPropName = invRel.name; + NSString* inverseRelPropName = invRel.name; CBLView* view = [self.database viewNamed: viewName]; [view setMapBlock:^(NSDictionary* doc, CBLMapEmitBlock emit) { NSString* type = [doc objectForKey: [self documentTypeKey]]; if (type && [entityNames containsObject: type] && - [doc objectForKey: invertRelPropName]) { - emit([doc objectForKey: invertRelPropName], nil); + [doc objectForKey: inverseRelPropName]) { + emit([doc objectForKey: inverseRelPropName], nil); } } version: @"1.0"]; } @@ -1322,35 +1336,48 @@ - (id) evaluateExpression: (NSExpression*)expression value = [properties objectForKey: expression.keyPath]; if ([propertyDesc isKindOfClass: [NSAttributeDescription class]]) { if (!value) break; - NSAttributeDescription* attr = (NSAttributeDescription* )propertyDesc; value = [self convertCoreDataValue: value toCouchbaseLiteValueOfType: attr.attributeType]; } else if ([propertyDesc isKindOfClass: [NSRelationshipDescription class]]) { - NSRelationshipDescription* relationDesc = (NSRelationshipDescription*)propertyDesc; - if (!relationDesc.isToMany) { + // Compare whole relationship, return managed object or array of managed objects: + NSRelationshipDescription* relation = (NSRelationshipDescription*)propertyDesc; + if (!relation.isToMany) { if (!value) break; - NSString* childDocId = value; - NSManagedObjectID* objectID = [self newObjectIDForEntity: relationDesc.destinationEntity + NSManagedObjectID* objectID = [self newObjectIDForEntity: relation.destinationEntity referenceObject: childDocId]; value = objectID ? [context existingObjectWithID: objectID error: nil] : nil; } else { - NSString* parentDocId = [properties objectForKey: @"_id"]; - if (parentDocId) { - CBLQueryEnumerator* rows = [self queryToManyRelation: relationDesc - forParentKeys: @[parentDocId] - prefetch: NO - outError: nil]; - if (rows) { - NSMutableArray* objects = [NSMutableArray array]; - for (CBLQueryRow* row in rows) { - NSManagedObjectID* objectID = [self newObjectIDForEntity: relationDesc.destinationEntity - referenceObject: row.documentID]; - if (!objectID) continue; - NSManagedObject* object = [context existingObjectWithID: objectID error: nil]; - if (object) [objects addObject: object]; + if (relation.inverseRelationship.toMany) { + // many-to-many + NSMutableArray* objects = [NSMutableArray array]; + for (NSString* docId in value) { + NSManagedObjectID* objectID = [self newObjectIDForEntity: relation.destinationEntity + referenceObject: docId]; + if (!objectID) continue; + NSManagedObject* object = [context existingObjectWithID: objectID error: nil]; + if (object) [objects addObject: object]; + } + value = objects; + } else { + // one-to-many + NSString* parentDocId = [properties objectForKey: @"_id"]; + if (parentDocId) { + CBLQueryEnumerator* rows = [self queryToManyRelation: relation + forParentKeys: @[parentDocId] + prefetch: NO + outError: nil]; + if (rows) { + NSMutableArray* objects = [NSMutableArray array]; + for (CBLQueryRow* row in rows) { + NSManagedObjectID* objectID = [self newObjectIDForEntity: relation.destinationEntity + referenceObject: row.documentID]; + if (!objectID) continue; + NSManagedObject* object = [context existingObjectWithID: objectID error: nil]; + if (object) [objects addObject: object]; + } + value = objects; } - value = objects; } } } @@ -1375,20 +1402,35 @@ - (id) evaluateExpression: (NSExpression*)expression value = [document.properties objectForKey: destKeyPath]; } } else { - NSString* parentDocId = [properties objectForKey: @"_id"]; - if (parentDocId) { - CBLQueryEnumerator* rows = [self queryToManyRelation: relation - forParentKeys: @[parentDocId] - prefetch: YES - outError: nil]; - if (rows) { - NSMutableArray* values = [NSMutableArray array]; - for (CBLQueryRow* row in rows) { - id propValue = row.documentProperties[destKeyPath]; - if (propValue) - [values addObject: propValue]; + if (relation.inverseRelationship.toMany) { + // many-to-many + value = [properties objectForKey: srcKeyPath]; + NSMutableArray* values = [NSMutableArray array]; + for (NSString* childDocId in value) { + CBLDocument* document = [self.database existingDocumentWithID: childDocId]; + if (document) { + id propValue = [document.properties objectForKey: destKeyPath]; + [values addObject: propValue]; + } + } + value = values; + } else { + // one-to-many + NSString* parentDocId = [properties objectForKey: @"_id"]; + if (parentDocId) { + CBLQueryEnumerator* rows = [self queryToManyRelation: relation + forParentKeys: @[parentDocId] + prefetch: YES + outError: nil]; + if (rows) { + NSMutableArray* values = [NSMutableArray array]; + for (CBLQueryRow* row in rows) { + id propValue = row.documentProperties[destKeyPath]; + if (propValue) + [values addObject: propValue]; + } + value = values; } - value = values; } } } @@ -1603,11 +1645,19 @@ - (id) couchbaseLiteRepresentationOfManagedObject: (NSManagedObject*)object } } } else if ([desc isKindOfClass: [NSRelationshipDescription class]]) { - NSRelationshipDescription* rel = desc; - id relationshipDestination = [object valueForKey: property]; - if (relationshipDestination) { - if (![rel isToMany]) { - NSManagedObjectID* objectID = [relationshipDestination valueForKey: @"objectID"]; + id relValue = [object valueForKey: property]; + if (relValue) { + NSRelationshipDescription* rel = desc; + if ([rel isToMany]) { + if (rel.inverseRelationship.toMany) { + // many-to-many relationship, embed an array of doc ids: + NSMutableArray* subentities = [NSMutableArray array]; + for (NSManagedObject* subentity in relValue) + [subentities addObject: [subentity.objectID couchbaseLiteIDRepresentation]]; + [proxy setObject:subentities forKey:property]; + } + } else { + NSManagedObjectID* objectID = [relValue valueForKey: @"objectID"]; [proxy setObject: [objectID couchbaseLiteIDRepresentation] forKey: property]; } } @@ -2043,8 +2093,9 @@ BOOL CBLISIsNull(id value) { NSString* CBLISToManyViewNameForRelationship(NSRelationshipDescription* relationship) { NSString* entityName = relationship.entity.name; NSString* destinationName = relationship.destinationEntity.name; - return [NSString stringWithFormat: - kCBLISFetchEntityToManyViewNameFormat, entityName, destinationName]; + NSString* relationshipName = relationship.name; + return [NSString stringWithFormat: kCBLISToManyViewNameFormat, + entityName, destinationName, relationshipName]; } /** Returns a readable name for a NSFetchRequestResultType*/ diff --git a/Unit-Tests/IncrementalStore_Tests.m b/Unit-Tests/IncrementalStore_Tests.m index c3972f5d8..2282b0653 100644 --- a/Unit-Tests/IncrementalStore_Tests.m +++ b/Unit-Tests/IncrementalStore_Tests.m @@ -30,6 +30,7 @@ @interface IncrementalStore_Tests : CBLTestCaseWithDB @class Entry; @class Subentry; @class AnotherSubentry; +@class ManySubentry; @class File; @class Article; @class User; @@ -57,6 +58,9 @@ @interface Entry : NSManagedObject // To-Many with ordered relationship (not supported by CBLIS) @property (nonatomic, retain) NSOrderedSet *anotherSubentries; +// Many-to-Many relationship +@property (nonatomic, retain) NSSet *manySubentries; + @end @interface Entry (CoreDataGeneratedAccessors) @@ -89,6 +93,12 @@ - (void)addAnotherSubentriesObject:(NSManagedObject *)value; - (void)removeAnotherSubentriesObject:(NSManagedObject *)value; - (void)addAnotherSubentries:(NSOrderedSet *)values; - (void)removeAnotherSubentries:(NSOrderedSet *)values; + +// manySubentries: +- (void)addManySubentriesObject:(ManySubentry *)value; +- (void)removeManySubentriesObject:(ManySubentry *)value; +- (void)addManySubentries:(NSSet *)values; +- (void)removeManySubentries:(NSSet *)values; @end @interface Subentry : NSManagedObject @@ -103,6 +113,19 @@ @interface AnotherSubentry : NSManagedObject @property (nonatomic, retain) Entry *entry; @end +@interface ManySubentry : NSManagedObject +@property (nonatomic, retain) NSString * text; +@property (nonatomic, retain) NSNumber * number; +@property (nonatomic, retain) NSSet *entries; +@end + +@interface ManySubentry (CoreDataGeneratedAccessors) +- (void)addEntriesObject:(Entry *)value; +- (void)removeEntriesObject:(Entry *)value; +- (void)addEntries:(NSSet *)values; +- (void)removeEntries:(NSSet *)values; +@end + @interface File : NSManagedObject @property (nonatomic, retain) NSString * filename; @property (nonatomic, retain) NSData * data; @@ -141,7 +164,6 @@ @implementation IncrementalStore_Tests CBLIncrementalStore *store; } - - (void) setUp { [super setUp]; @@ -164,10 +186,8 @@ - (void) tearDown { [super tearDown]; } - /** Test case that tests create, request, update and delete of Core Data objects. */ - (void) test_CRUD { - RequireTestCase(API); NSError *error; CBLDatabase *database = store.database; @@ -228,7 +248,6 @@ - (void) test_CRUD { /** Test case that tests the integration between Core Data and CouchbaseLite. */ - (void) test_CBLIntegration { - RequireTestCase(CBLIncrementalStoreCRUD); NSError *error; CBLDatabase *database = store.database; @@ -318,7 +337,6 @@ - (void) test_CBLIntegration { - (void) test_CreateAndUpdate { - RequireTestCase(CBLIncrementalStoreCRUD); NSError *error; Entry *entry = [NSEntityDescription insertNewObjectForEntityForName:@"Entry" @@ -366,9 +384,7 @@ - (void) test_CreateAndUpdate { Assert([entry.decimalNumber isKindOfClass:[NSDecimalNumber class]], @"decimalNumber must be with type NSDecimalNumber"); } - -- (void) test_ToManyRelationship { - RequireTestCase(CBLIncrementalStoreCRUD); +- (void) test_ToMany { NSError *error; // To-Many with inverse relationship: @@ -441,9 +457,126 @@ - (void) test_ToManyRelationship { AssertEq(entry.articles.count, 0u); } +- (void) test_ToManyDeletion { + NSError *error; + Entry *entry = [NSEntityDescription insertNewObjectForEntityForName:@"Entry" + inManagedObjectContext:context]; + entry.created_at = [NSDate new]; + entry.text = @"Test"; + entry.check = @NO; + BOOL success = [context save:&error]; + Assert(success, @"Could not save context: %@", error); + + for (NSUInteger i = 0; i < 3; i++) { + Subentry *sub = [NSEntityDescription insertNewObjectForEntityForName:@"Subentry" + inManagedObjectContext:context]; + sub.text = [NSString stringWithFormat:@"Sub%lu", (unsigned long)i]; + [entry addSubEntriesObject:sub]; + } + + success = [context save: &error]; + Assert(success, @"Could not save context: %@", error); + + // Delete one sub entry: + Subentry* aSubentry = [entry.subEntries anyObject]; + [context deleteObject: aSubentry]; + success = [context save: &error]; + Assert(success, @"Could not save context: %@", error); + + // Check the result: + NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Subentry"]; + NSArray* result = [context executeFetchRequest:fetchRequest error:&error]; + AssertEq(result.count, 2u); + + fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Entry"]; + result = [context executeFetchRequest:fetchRequest error:&error]; + AssertEq(result.count, 1u); + entry = result.firstObject; + AssertEq(entry.subEntries.count, 2u); + + // Delete entry (cascading): + [context deleteObject: entry]; + success = [context save: &error]; + Assert(success, @"Could not save context: %@", error); + + // Check the result: + fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Subentry"]; + result = [context executeFetchRequest:fetchRequest error:&error]; + AssertEq(result.count, 0u); + + fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Entry"]; + result = [context executeFetchRequest:fetchRequest error:&error]; + AssertEq(result.count, 0u); + + // Tear down and re-init and test with fetch request: + context = [CBLIncrementalStore createManagedObjectContextWithModel:model + databaseName:db.name error:&error]; + + // Recheck the result: + fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Subentry"]; + result = [context executeFetchRequest:fetchRequest error:&error]; + AssertEq(result.count, 0u); + + fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Entry"]; + result = [context executeFetchRequest:fetchRequest error:&error]; + AssertEq(result.count, 0u); +} + +- (void) test_ManyToMany { + NSError *error; + NSMutableSet *entries = [NSMutableSet set]; + for (NSUInteger i = 0; i < 3; i++) { + Entry *entry = [NSEntityDescription insertNewObjectForEntityForName:@"Entry" + inManagedObjectContext:context]; + entry.text = [NSString stringWithFormat:@"Entry%lu", i]; + [entries addObject:entry]; + } + + for (NSUInteger i = 0; i < 3; i++) { + ManySubentry *subentry = [NSEntityDescription insertNewObjectForEntityForName:@"ManySubentry" + inManagedObjectContext:context]; + subentry.text = [NSString stringWithFormat:@"Subentry%lu", i]; + [subentry addEntries:entries]; + } + + BOOL success = [context save:&error]; + Assert(success, @"Could not save context: %@", error); + + // Tear down and re-init and test with fetch request: + context = [CBLIncrementalStore createManagedObjectContextWithModel:model + databaseName:db.name error:&error]; + + // Check the result: + NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Entry"]; + NSArray* result = [context executeFetchRequest:fetchRequest error:&error]; + AssertEq(result.count, 3u); + for (Entry *entry in result) { + AssertEq(entry.manySubentries.count, 3u); + NSMutableArray *expected = [NSMutableArray arrayWithArray: + @[@"Subentry0", @"Subentry1", @"Subentry2"]]; + for (ManySubentry *subentry in entry.manySubentries) { + NSString *text = subentry.text; + Assert([expected containsObject:text]); + [expected removeObject:text]; + } + } + + fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"ManySubentry"]; + result = [context executeFetchRequest:fetchRequest error:&error]; + AssertEq(result.count, 3u); + for (ManySubentry *subentry in result) { + AssertEq(subentry.entries.count, 3u); + NSMutableArray *expected = [NSMutableArray arrayWithArray: + @[@"Entry0", @"Entry1", @"Entry2"]]; + for (Entry *entry in subentry.entries) { + NSString *text = entry.text; + Assert([expected containsObject:text]); + [expected removeObject:text]; + } + } +} - (void) test_FetchRequest { - RequireTestCase(CBLIncrementalStoreCRUD); NSError *error; NSUInteger count; NSArray *result; @@ -607,7 +740,6 @@ - (void) test_FetchOffset { } - (void) test_Attachments { - RequireTestCase(CBLIncrementalStoreCRUD); NSError *error; CBLDatabase *database = store.database; @@ -670,7 +802,6 @@ - (void) test_Attachments { } - (void) test_FetchWithPredicates { - RequireTestCase(CBLIncrementalStoreCRUD); NSError *error; NSDictionary *entry1 = @{ @@ -934,6 +1065,23 @@ - (void)test_FetchWithRelationship { entry3.number = @(30); entry3.user = user1; + + // ManySubentry: + for (NSUInteger i = 0; i < 4; i++) { + ManySubentry *subentry = [NSEntityDescription insertNewObjectForEntityForName:@"ManySubentry" + inManagedObjectContext:context]; + subentry.text = [NSString stringWithFormat:@"ManySubentry%lu", i]; + subentry.number = @(30 + i); + + if (i < 2) { + [subentry addEntriesObject:entry1]; + [subentry addEntriesObject:entry2]; + } else { + [subentry addEntriesObject:entry2]; + [subentry addEntriesObject:entry3]; + } + } + BOOL success = [context save:&error]; Assert(success, @"Could not save context: %@", error); @@ -989,8 +1137,37 @@ - (void)test_FetchWithRelationship { if (result.count != 1) return; AssertEqual([result[0] valueForKey:@"number"], entry2.number); }]; -} + // many-to-many + fetchRequest.predicate = [NSPredicate predicateWithFormat:@"ANY manySubentries.number < 32"]; + [self assertFetchRequest: fetchRequest block: ^(NSArray *result, NSFetchRequestResultType resultType) { + AssertEq((int)result.count, 2); + NSArray *numbers = [[result valueForKey:@"number"] sortedArrayUsingSelector:@selector(compare:)]; + AssertEqual(numbers[0], entry1.number); + AssertEqual(numbers[1], entry2.number); + }]; + + fetchRequest.predicate = [NSPredicate predicateWithFormat:@"ANY manySubentries.number > 32"]; + [self assertFetchRequest: fetchRequest block: ^(NSArray *result, NSFetchRequestResultType resultType) { + AssertEq((int)result.count, 2); + NSArray *numbers = [[result valueForKey:@"number"] sortedArrayUsingSelector:@selector(compare:)]; + AssertEqual(numbers[0], entry2.number); + AssertEqual(numbers[1], entry3.number); + }]; + + fetchRequest.predicate = [NSPredicate predicateWithFormat:@"ANY manySubentries.number > 40"]; + [self assertFetchRequest: fetchRequest block: ^(NSArray *result, NSFetchRequestResultType resultType) { + AssertEq((int)result.count, 0); + }]; + + fetchRequest.predicate = [NSPredicate predicateWithFormat:@"number < 40 AND ANY manySubentries.number < 32"]; + [self assertFetchRequest: fetchRequest block: ^(NSArray *result, NSFetchRequestResultType resultType) { + AssertEq((int)result.count, 2); + NSArray *numbers = [[result valueForKey:@"number"] sortedArrayUsingSelector:@selector(compare:)]; + AssertEqual(numbers[0], entry1.number); + AssertEqual(numbers[1], entry2.number); + }]; +} - (void)test_FetchParentChildEntities { Parent *p1 = [NSEntityDescription insertNewObjectForEntityForName:@"Parent" @@ -1084,7 +1261,6 @@ - (void)test_FetchParentChildEntities { }]; } - - (void)test_DocTypeKey { CBLDatabase *database = store.database; @@ -1103,7 +1279,6 @@ - (void)test_DocTypeKey { AssertEqual(doc.properties[@"type"], @"Entry"); } - - (void)test_DocTypeKeyBackwardCompat { // Simulate old version (v.1.0.4 and below). NSError* error; @@ -1140,10 +1315,8 @@ - (void)test_DocTypeKeyBackwardCompat { AssertEqual(doc.properties[@"CBLIS_type"], @"Entry"); } - #pragma mark - UTILITIES - - (void)assertFetchRequest:(NSFetchRequest *)fetchRequest block:(CBLISAssertionBlock)assertionBlock { NSFetchRequestResultType resultTypes[] = {NSManagedObjectResultType, NSDictionaryResultType}; @@ -1156,7 +1329,6 @@ - (void)assertFetchRequest:(NSFetchRequest *)fetchRequest } } - - (void)assertFetchResult:(NSArray *)result key:(NSString *)key expected:(NSArray *)values ordered:(BOOL)ordered { AssertEq(result.count, values.count); @@ -1176,11 +1348,8 @@ - (void)assertFetchResult:(NSArray *)result key:(NSString *)key @end - -#pragma mark - #pragma mark - Test Core Data Model - static NSAttributeDescription *CBLISAttributeDescription(NSString *name, BOOL optional, NSAttributeType type, id defaultValue) { NSAttributeDescription *attribute = [NSAttributeDescription new]; @@ -1227,6 +1396,10 @@ - (void)assertFetchResult:(NSArray *)result key:(NSString *)key [anotherSubentry setName:@"AnotherSubentry"]; [anotherSubentry setManagedObjectClassName:@"AnotherSubentry"]; + NSEntityDescription *manySubentry = [NSEntityDescription new]; + [manySubentry setName:@"ManySubentry"]; + [manySubentry setManagedObjectClassName:@"ManySubentry"]; + NSEntityDescription *article = [NSEntityDescription new]; [article setName:@"Article"]; [article setManagedObjectClassName:@"Article"]; @@ -1244,22 +1417,28 @@ - (void)assertFetchResult:(NSArray *)result key:(NSString *)key [child setManagedObjectClassName:@"Child"]; [parent setSubentities:@[child]]; - NSRelationshipDescription *entryFiles = CBLISRelationshipDescription(@"files", YES, YES, NSCascadeDeleteRule, file); NSRelationshipDescription *entrySubentries = CBLISRelationshipDescription(@"subEntries", YES, YES, NSCascadeDeleteRule, subentry); + NSRelationshipDescription *entryFiles = CBLISRelationshipDescription(@"files", YES, YES, NSCascadeDeleteRule, file); NSRelationshipDescription *entryAnotherSubentries = CBLISRelationshipDescription(@"anotherSubentries", YES, YES, NSCascadeDeleteRule, anotherSubentry); - NSRelationshipDescription *fileEntry = CBLISRelationshipDescription(@"entry", YES, NO, NSNullifyDeleteRule, entry); + NSRelationshipDescription *entryUser = CBLISRelationshipDescription(@"user", YES, NO, NSCascadeDeleteRule, user); + NSRelationshipDescription *entryArticles = CBLISRelationshipDescription(@"articles", YES, YES, NSCascadeDeleteRule, article); + NSRelationshipDescription *entryManySubentries = CBLISRelationshipDescription(@"manySubentries", YES, YES, NSNullifyDeleteRule, manySubentry); + NSRelationshipDescription *subentryEntry = CBLISRelationshipDescription(@"entry", YES, NO, NSNullifyDeleteRule, entry); + NSRelationshipDescription *fileEntry = CBLISRelationshipDescription(@"entry", YES, NO, NSNullifyDeleteRule, entry); NSRelationshipDescription *anotherSubentryEntry = CBLISRelationshipDescription(@"entry", YES, NO, NSNullifyDeleteRule, entry); - NSRelationshipDescription *entryArticles = CBLISRelationshipDescription(@"articles", YES, YES, NSCascadeDeleteRule, article); - NSRelationshipDescription *entryUser = CBLISRelationshipDescription(@"user", YES, NO, NSNullifyDeleteRule, user); + NSRelationshipDescription *manySubentryEntries = CBLISRelationshipDescription(@"entries", YES, YES, NSNullifyDeleteRule, entry); NSRelationshipDescription *userEntry = CBLISRelationshipDescription(@"entry", YES, NO, NSNullifyDeleteRule, entry); - [entryFiles setInverseRelationship:fileEntry]; [entrySubentries setInverseRelationship:subentryEntry]; + [entryFiles setInverseRelationship:fileEntry]; [entryAnotherSubentries setInverseRelationship:anotherSubentryEntry]; + [entryManySubentries setInverseRelationship:manySubentryEntries]; + [fileEntry setInverseRelationship:entryFiles]; [subentryEntry setInverseRelationship:entrySubentries]; [anotherSubentryEntry setInverseRelationship:entryAnotherSubentries]; + [manySubentryEntries setInverseRelationship:entryManySubentries]; [userEntry setInverseRelationship:entryUser]; [entryAnotherSubentries setOrdered:YES]; @@ -1276,6 +1455,7 @@ - (void)assertFetchResult:(NSArray *)result key:(NSString *)key entryFiles, entrySubentries, entryAnotherSubentries, + entryManySubentries, entryArticles, entryUser ]]; @@ -1298,6 +1478,12 @@ - (void)assertFetchResult:(NSArray *)result key:(NSString *)key anotherSubentryEntry ]]; + [manySubentry setProperties:@[ + CBLISAttributeDescription(@"number", YES, NSInteger32AttributeType, @(0)), + CBLISAttributeDescription(@"text", YES, NSStringAttributeType, nil), + manySubentryEntries + ]]; + [article setProperties:@[ CBLISAttributeDescription(@"name", YES, NSStringAttributeType, nil) ]]; @@ -1315,14 +1501,14 @@ - (void)assertFetchResult:(NSArray *)result key:(NSString *)key CBLISAttributeDescription(@"anotherName", YES, NSStringAttributeType, nil) ]]; - [model setEntities:@[entry, file, subentry, anotherSubentry, article, user, parent, child]]; + [model setEntities:@[entry, file, subentry, anotherSubentry, manySubentry, article, user, parent, child]]; return model; } @implementation Entry @dynamic check, created_at, text, text2, number, decimalNumber, doubleNumber; -@dynamic subEntries, files, articles, anotherSubentries; +@dynamic subEntries, files, articles, anotherSubentries, manySubentries; @dynamic user; // Known Core Data Bug: @@ -1342,6 +1528,10 @@ @implementation AnotherSubentry @dynamic text, number, entry; @end +@implementation ManySubentry +@dynamic text, number, entries; +@end + @implementation File @dynamic filename, data, entry; @end