//////////////////////////////////////////////////////////////////////////// // // Copyright 2014 Realm Inc. // // 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 "RLMTestCase.h" #import "RLMObjectSchema_Private.h" #import "RLMSchema_Private.h" #import #import #import #import #pragma mark - Test Objects @interface DefaultObject : RLMObject @property int intCol; @property float floatCol; @property double doubleCol; @property BOOL boolCol; @property NSDate *dateCol; @property NSString *stringCol; @property NSData *binaryCol; @end @implementation DefaultObject + (NSDictionary *)defaultPropertyValues { NSString *binaryString = @"binary"; NSData *binaryData = [binaryString dataUsingEncoding:NSUTF8StringEncoding]; return @{@"intCol": @12, @"floatCol": @88.9f, @"doubleCol": @1002.892, @"boolCol": @YES, @"dateCol": [NSDate dateWithTimeIntervalSince1970:999999], @"stringCol": @"potato", @"binaryCol": binaryData}; } @end @interface DynamicDefaultObject : RLMObject @property int intCol; @property float floatCol; @property double doubleCol; @property NSDate *dateCol; @property NSString *stringCol; @property NSData *binaryCol; @end @implementation DynamicDefaultObject + (BOOL)shouldIncludeInDefaultSchema { return NO; } + (NSDictionary *)defaultPropertyValues { static NSInteger dynamicDefaultSeed = 0; dynamicDefaultSeed++; return @{@"intCol": @(dynamicDefaultSeed), @"floatCol": @((float)dynamicDefaultSeed), @"doubleCol": @((double)dynamicDefaultSeed), @"dateCol": [NSDate dateWithTimeIntervalSince1970:dynamicDefaultSeed], @"stringCol": [[NSUUID UUID] UUIDString], @"binaryCol": [[[NSUUID UUID] UUIDString] dataUsingEncoding:NSUTF8StringEncoding]}; } + (NSString *)primaryKey { return @"intCol"; } @end @class CycleObject; RLM_ARRAY_TYPE(CycleObject) @interface CycleObject : RLMObject @property RLM_GENERIC_ARRAY(CycleObject) *objects; @end @implementation CycleObject @end @interface PrimaryStringObjectWrapper : RLMObject @property PrimaryStringObject *primaryStringObject; @end @implementation PrimaryStringObjectWrapper @end @interface PrimaryNestedObject : RLMObject @property int primaryCol; @property PrimaryStringObject *primaryStringObject; @property PrimaryStringObjectWrapper *primaryStringObjectWrapper; @property StringObject *stringObject; @property RLM_GENERIC_ARRAY(PrimaryIntObject) *primaryIntArray; @property NSString *stringCol; @end @implementation PrimaryNestedObject + (NSString *)primaryKey { return @"primaryCol"; } + (NSDictionary *)defaultPropertyValues { return @{@"stringCol": @"default"}; } @end @interface StringSubclassObject : StringObject @property NSString *stringCol2; @end @implementation StringSubclassObject @end @interface StringObjectNoThrow : StringObject @end @implementation StringObjectNoThrow - (id)valueForUndefinedKey:(__unused NSString *)key { return nil; } @end @interface StringSubclassObjectWithDefaults : StringObjectNoThrow @property NSString *stringCol2; @end @implementation StringSubclassObjectWithDefaults +(NSDictionary *)defaultPropertyValues { return @{@"stringCol2": @"default"}; } @end @interface StringLinkObject : RLMObject @property StringObject *stringObjectCol; @property RLM_GENERIC_ARRAY(StringObject) *stringObjectArrayCol; @end @implementation StringLinkObject @end @interface ReadOnlyPropertyObject () @property (readwrite) int readOnlyPropertyMadeReadWriteInClassExtension; @end @interface DataObject : RLMObject @property NSData *data1; @property NSData *data2; @end @implementation DataObject @end @interface DateObjectNoThrow : DateObject @property NSDate *date2; @end @implementation DateObjectNoThrow - (id)valueForUndefinedKey:(__unused NSString *)key { return nil; } @end @interface DateSubclassObject : DateObjectNoThrow @property NSDate *date3; @end @implementation DateSubclassObject @end @interface DateDefaultsObject : DateObjectNoThrow @property NSDate *date3; @end @implementation DateDefaultsObject + (NSDictionary *)defaultPropertyValues { return @{@"date3": [NSDate date]}; } @end @interface SubclassDateObject : NSObject @property NSDate *dateCol; @property (getter=customGetter) NSDate *date2; @property (setter=customSetter:) NSDate *date3; @end @implementation SubclassDateObject @end #pragma mark - Tests @interface ObjectTests : RLMTestCase @end @implementation ObjectTests - (void)testKeyedSubscripting { EmployeeObject *objs = [[EmployeeObject alloc] initWithValue:@{@"name": @"Test0", @"age": @23, @"hired": @NO}]; XCTAssertEqualObjects(objs[@"name"], @"Test0", @"Name should be Test0"); XCTAssertEqualObjects(objs[@"age"], @23, @"age should be 23"); XCTAssertEqualObjects(objs[@"hired"], @NO, @"hired should be NO"); objs[@"name"] = @"Test1"; XCTAssertEqualObjects(objs.name, @"Test1", @"Name should be Test1"); RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; EmployeeObject *obj0 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Test1", @"age": @24, @"hired": @NO}]; EmployeeObject *obj1 = [EmployeeObject createInRealm:realm withValue:@{@"name": @"Test2", @"age": @25, @"hired": @YES}]; [realm commitWriteTransaction]; XCTAssertEqualObjects(obj0[@"name"], @"Test1", @"Name should be Test1"); XCTAssertEqualObjects(obj1[@"name"], @"Test2", @"Name should be Test1"); [realm beginWriteTransaction]; obj0[@"name"] = @"newName"; [realm commitWriteTransaction]; XCTAssertEqualObjects(obj0[@"name"], @"newName", @"Name should be newName"); [realm beginWriteTransaction]; obj0[@"name"] = nil; [realm commitWriteTransaction]; XCTAssertNil(obj0[@"name"]); } - (void)testCannotUpdatePrimaryKey { PrimaryIntObject *intObj = [[PrimaryIntObject alloc] init]; intObj.intCol = 1; XCTAssertNoThrow(intObj.intCol = 0); PrimaryStringObject *stringObj = [[PrimaryStringObject alloc] init]; stringObj.stringCol = @"a"; XCTAssertNoThrow(stringObj.stringCol = @"b"); RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm addObject:intObj]; RLMAssertThrowsWithReason(intObj.intCol = 1, @"Primary key can't be changed"); RLMAssertThrowsWithReason(intObj[@"intCol"] = @1, @"Primary key can't be changed"); RLMAssertThrowsWithReason([intObj setValue:@1 forKey:@"intCol"], @"Primary key can't be changed"); [realm addObject:stringObj]; RLMAssertThrowsWithReason(stringObj.stringCol = @"a", @"Primary key can't be changed"); RLMAssertThrowsWithReason(stringObj[@"stringCol"] = @"a", @"Primary key can't be changed"); RLMAssertThrowsWithReason([stringObj setValue:@"a" forKey:@"stringCol"], @"Primary key can't be changed"); [realm cancelWriteTransaction]; } - (void)testDataTypes { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; const char bin[4] = { 0, 1, 2, 3 }; NSData *bin1 = [[NSData alloc] initWithBytes:bin length:sizeof bin / 2]; NSData *bin2 = [[NSData alloc] initWithBytes:bin length:sizeof bin]; NSDate *timeNow = [NSDate dateWithTimeIntervalSince1970:1000000]; NSDate *timeZero = [NSDate dateWithTimeIntervalSince1970:0]; AllTypesObject *c = [[AllTypesObject alloc] init]; c.boolCol = NO; c.intCol = 54; c.floatCol = 0.7f; c.doubleCol = 0.8; c.stringCol = @"foo"; c.binaryCol = bin1; c.dateCol = timeZero; c.cBoolCol = false; c.longCol = 99; c.objectCol = [[StringObject alloc] init]; c.objectCol.stringCol = @"c"; [realm addObject:c]; [AllTypesObject createInRealm:realm withValue:@[@YES, @506, @7.7f, @8.8, @"banach", bin2, timeNow, @YES, @(-20), NSNull.null]]; [realm commitWriteTransaction]; AllTypesObject *row1 = [AllTypesObject allObjects][0]; AllTypesObject *row2 = [AllTypesObject allObjects][1]; XCTAssertEqual(row1.boolCol, NO, @"row1.BoolCol"); XCTAssertEqual(row2.boolCol, YES, @"row2.BoolCol"); XCTAssertEqual(row1.intCol, 54, @"row1.IntCol"); XCTAssertEqual(row2.intCol, 506, @"row2.IntCol"); XCTAssertEqual(row1.floatCol, 0.7f, @"row1.FloatCol"); XCTAssertEqual(row2.floatCol, 7.7f, @"row2.FloatCol"); XCTAssertEqual(row1.doubleCol, 0.8, @"row1.DoubleCol"); XCTAssertEqual(row2.doubleCol, 8.8, @"row2.DoubleCol"); XCTAssertTrue([row1.stringCol isEqual:@"foo"], @"row1.StringCol"); XCTAssertTrue([row2.stringCol isEqual:@"banach"], @"row2.StringCol"); XCTAssertTrue([row1.binaryCol isEqual:bin1], @"row1.BinaryCol"); XCTAssertTrue([row2.binaryCol isEqual:bin2], @"row2.BinaryCol"); XCTAssertTrue(([row1.dateCol isEqual:timeZero]), @"row1.DateCol"); XCTAssertTrue(([row2.dateCol isEqual:timeNow]), @"row2.DateCol"); XCTAssertEqual(row1.cBoolCol, false, @"row1.cBoolCol"); XCTAssertEqual(row2.cBoolCol, true, @"row2.cBoolCol"); XCTAssertEqual(row1.longCol, 99L, @"row1.IntCol"); XCTAssertEqual(row2.longCol, -20L, @"row2.IntCol"); XCTAssertTrue([row1.objectCol.stringCol isEqual:@"c"], @"row1.objectCol"); XCTAssertNil(row2.objectCol, @"row2.objectCol"); [realm transactionWithBlock:^{ row1.boolCol = NO; row1.cBoolCol = false; row1.boolCol = (BOOL)6; row1.cBoolCol = (BOOL)6; }]; XCTAssertEqual(row1.boolCol, true); XCTAssertEqual(row1.cBoolCol, true); AllTypesObject *o = [[AllTypesObject alloc] initWithValue:row1]; o.floatCol = NAN; o.doubleCol = NAN; [realm transactionWithBlock:^{ [realm addObject:o]; }]; XCTAssertTrue(isnan(o.floatCol)); XCTAssertTrue(isnan(o.doubleCol)); } - (void)testObjectSubclass { // test className methods XCTAssertEqualObjects(@"StringObject", [StringObject className]); XCTAssertEqualObjects(@"StringSubclassObject", [StringSubclassObject className]); RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [StringObject createInDefaultRealmWithValue:@[@"string"]]; StringSubclassObject *obj = [StringSubclassObject createInDefaultRealmWithValue:@[@"string", @"string2"]]; // ensure property ordering XCTAssertEqualObjects([obj.objectSchema.properties[0] name], @"stringCol"); XCTAssertEqualObjects([obj.objectSchema.properties[1] name], @"stringCol2"); [realm commitWriteTransaction]; // ensure creation in proper table RLMResults *results = StringSubclassObject.allObjects; XCTAssertEqual(1U, results.count); XCTAssertEqual(1U, StringObject.allObjects.count); // ensure exceptions on when using polymorphism [realm beginWriteTransaction]; StringLinkObject *linkObject = [StringLinkObject createInDefaultRealmWithValue:@[NSNull.null, @[]]]; RLMAssertThrowsWithReasonMatching(linkObject.stringObjectCol = obj, @"Can't .*StringSubclassObject.*StringObject"); RLMAssertThrowsWithReasonMatching([linkObject.stringObjectArrayCol addObject:obj], @"Object of type .*StringSubclassObject.*does not match.*StringObject.*"); [realm commitWriteTransaction]; } - (void)testDateDistantFuture { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; DateObject *dateObject = [DateObject createInRealm:realm withValue:@[NSDate.distantFuture]]; [realm commitWriteTransaction]; XCTAssertEqualObjects(NSDate.distantFuture, dateObject.dateCol); } - (void)testDateDistantPast { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; DateObject *dateObject = [DateObject createInRealm:realm withValue:@[NSDate.distantPast]]; [realm commitWriteTransaction]; XCTAssertEqualObjects(NSDate.distantPast, dateObject.dateCol); } - (void)testDate50kYears { NSCalendarUnit units = (NSCalendarUnit)(NSCalendarUnitMonth | NSCalendarUnitYear | NSCalendarUnitDay); NSDateComponents *components = [[NSCalendar currentCalendar] components:units fromDate:NSDate.date]; components.calendar = [NSCalendar currentCalendar]; components.year += 50000; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; DateObject *dateObject = [DateObject createInRealm:realm withValue:@[components.date]]; [realm commitWriteTransaction]; XCTAssertEqualObjects(components.date, dateObject.dateCol); } static void testDatesInRange(NSTimeInterval from, NSTimeInterval to, void (^check)(NSDate *, NSDate *)) { NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:from]; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; DateObject *dateObject = [DateObject createInRealm:realm withValue:@[date]]; while (from < to) @autoreleasepool { check(dateObject.dateCol, date); from = nextafter(from, DBL_MAX); date = [NSDate dateWithTimeIntervalSinceReferenceDate:from]; dateObject.dateCol = date; } [realm commitWriteTransaction]; } - (void)testExactRepresentationOfDatesAroundNow { NSDate *date = [NSDate date]; NSTimeInterval time = date.timeIntervalSinceReferenceDate; testDatesInRange(time - .001, time + .001, ^(NSDate *d1, NSDate *d2) { XCTAssertEqualObjects(d1, d2); }); } - (void)testExactRepresentationOfDatesAroundDistantFuture { NSDate *date = [NSDate distantFuture]; NSTimeInterval time = date.timeIntervalSinceReferenceDate; testDatesInRange(time - .001, time + .001, ^(NSDate *d1, NSDate *d2) { XCTAssertEqualObjects(d1, d2); }); } - (void)testExactRepresentationOfDatesAroundEpoch { NSDate *date = [NSDate dateWithTimeIntervalSince1970:0]; NSTimeInterval time = date.timeIntervalSinceReferenceDate; testDatesInRange(time - .001, time + .001, ^(NSDate *d1, NSDate *d2) { XCTAssertEqualObjects(d1, d2); }); } - (void)testExactRepresentationOfDatesAroundReferenceDate { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; NSDate *zero = [NSDate dateWithTimeIntervalSinceReferenceDate:0]; DateObject *dateObject = [DateObject createInRealm:realm withValue:@[zero]]; XCTAssertEqualObjects(dateObject.dateCol, zero); // Just shy of 1ns should still be zero dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:nextafter(1e-9, -DBL_MAX)]; XCTAssertEqualObjects(dateObject.dateCol, zero); // Very slightly over 1ns (since 1e-9 can't be exactly represented by a double) dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:1e-9]; XCTAssertNotEqualObjects(dateObject.dateCol, zero); // Round toward zero, so -1ns + epsilon is zero dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:nextafter(0, -DBL_MAX)]; XCTAssertEqualObjects(dateObject.dateCol, zero); dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:nextafter(-1e-9, DBL_MAX)]; XCTAssertEqualObjects(dateObject.dateCol, zero); dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:-1e-9]; XCTAssertNotEqualObjects(dateObject.dateCol, zero); [realm commitWriteTransaction]; } - (void)testDatesOutsideOfTimestampRange { NSDate *date = [NSDate date]; NSDate *maxDate = [NSDate dateWithTimeIntervalSince1970:(double)(1ULL << 63) + .999999999]; NSDate *minDate = [NSDate dateWithTimeIntervalSince1970:-(double)(1ULL << 63) - .999999999]; NSDate *justOverMaxDate = [NSDate dateWithTimeIntervalSince1970:nextafter(maxDate.timeIntervalSince1970, DBL_MAX)]; NSDate *justUnderMaxDate = [NSDate dateWithTimeIntervalSince1970:nextafter(maxDate.timeIntervalSince1970, -DBL_MAX)]; NSDate *justOverMinDate = [NSDate dateWithTimeIntervalSince1970:nextafter(minDate.timeIntervalSince1970, DBL_MAX)]; NSDate *justUnderMinDate = [NSDate dateWithTimeIntervalSince1970:nextafter(minDate.timeIntervalSince1970, -DBL_MAX)]; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; DateObject *dateObject = [DateObject createInRealm:realm withValue:@[date]]; dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:0.0/0.0]; XCTAssertEqualObjects(dateObject.dateCol, [NSDate dateWithTimeIntervalSince1970:0]); dateObject.dateCol = maxDate; XCTAssertEqualObjects(dateObject.dateCol, maxDate); dateObject.dateCol = justOverMaxDate; XCTAssertEqualObjects(dateObject.dateCol, maxDate); dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:DBL_MAX]; XCTAssertEqualObjects(dateObject.dateCol, maxDate); dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:1.0/0.0]; XCTAssertEqualObjects(dateObject.dateCol, maxDate); dateObject.dateCol = minDate; XCTAssertEqualObjects(dateObject.dateCol, minDate); dateObject.dateCol = justUnderMinDate; XCTAssertEqualObjects(dateObject.dateCol, minDate); dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:-DBL_MAX]; XCTAssertEqualObjects(dateObject.dateCol, minDate); dateObject.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:-1.0/0.0]; XCTAssertEqualObjects(dateObject.dateCol, minDate); dateObject.dateCol = justUnderMaxDate; XCTAssertEqualObjects(dateObject.dateCol, justUnderMaxDate); dateObject.dateCol = justOverMinDate; XCTAssertEqualObjects(dateObject.dateCol, justOverMinDate); [realm commitWriteTransaction]; } - (void)testDataSizeLimits { RLMRealm *realm = [RLMRealm defaultRealm]; // Allocation must be < 16 MB, with an 8-byte header and the allocation size // 8-byte aligned static const int maxSize = 0xFFFFFF - 15; // Multiple 16 MB blobs should be fine void *buffer = malloc(maxSize); strcpy((char *)buffer + maxSize - sizeof("hello") - 1, "hello"); DataObject *obj = [[DataObject alloc] init]; obj.data1 = obj.data2 = [NSData dataWithBytesNoCopy:buffer length:maxSize freeWhenDone:YES]; [realm beginWriteTransaction]; [realm addObject:obj]; [realm commitWriteTransaction]; XCTAssertEqual(maxSize, obj.data1.length); XCTAssertEqual(maxSize, obj.data2.length); XCTAssertTrue(strcmp((const char *)obj.data1.bytes + obj.data1.length - sizeof("hello") - 1, "hello") == 0); XCTAssertTrue(strcmp((const char *)obj.data2.bytes + obj.data2.length - sizeof("hello") - 1, "hello") == 0); // A blob over 16 MB should throw (and not crash) [realm beginWriteTransaction]; RLMAssertThrowsWithReason(obj.data1 = [NSData dataWithBytesNoCopy:malloc(maxSize + 1) length:maxSize + 1 freeWhenDone:YES], @"Binary too big"); [realm commitWriteTransaction]; } - (void)testStringSizeLimits { RLMRealm *realm = [RLMRealm defaultRealm]; // Allocation must be < 16 MB, with an 8-byte header, trailing NUL, and the // allocation size 8-byte aligned static const int maxSize = 0xFFFFFF - 16; void *buffer = calloc(maxSize, 1); strcpy((char *)buffer + maxSize - sizeof("hello") - 1, "hello"); NSString *str = [[NSString alloc] initWithBytesNoCopy:buffer length:maxSize encoding:NSUTF8StringEncoding freeWhenDone:YES]; StringObject *obj = [[StringObject alloc] init]; obj.stringCol = str; [realm beginWriteTransaction]; [realm addObject:obj]; [realm commitWriteTransaction]; XCTAssertEqualObjects(str, obj.stringCol); // A blob over 16 MB should throw (and not crash) [realm beginWriteTransaction]; XCTAssertThrows(obj.stringCol = [[NSString alloc] initWithBytesNoCopy:calloc(maxSize + 1, 1) length:maxSize + 1 encoding:NSUTF8StringEncoding freeWhenDone:YES]); [realm commitWriteTransaction]; } - (void)testAddingObjectNotInSchemaThrows { RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.objectClasses = @[StringObject.class]; RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; [realm beginWriteTransaction]; RLMAssertThrowsWithReasonMatching([realm addObject:[[IntObject alloc] initWithValue:@[@1]]], @"Object type 'IntObject' is not managed by the Realm.*custom `objectClasses`"); RLMAssertThrowsWithReasonMatching([IntObject createInRealm:realm withValue:@[@1]], @"Object type 'IntObject' is not managed by the Realm.*custom `objectClasses`"); XCTAssertNoThrow([realm addObject:[[StringObject alloc] initWithValue:@[@"A"]]]); XCTAssertNoThrow([StringObject createInRealm:realm withValue:@[@"A"]]); [realm cancelWriteTransaction]; } static void addProperty(Class cls, const char *name, const char *type, size_t size, size_t align, id getter) { objc_property_attribute_t objectColAttrs[] = { {"T", type}, {"V", name}, }; class_addIvar(cls, name, size, align, type); class_addProperty(cls, name, objectColAttrs, sizeof(objectColAttrs) / sizeof(objc_property_attribute_t)); char encoding[4] = " @:"; encoding[0] = *type; class_addMethod(cls, sel_registerName(name), imp_implementationWithBlock(getter), encoding); } - (void)testObjectSubclassAddedAtRuntime { Class objectClass = objc_allocateClassPair(RLMObject.class, "RuntimeGeneratedObject", 0); addProperty(objectClass, "objectCol", "@\"RuntimeGeneratedObject\"", sizeof(id), alignof(id), ^(__unused id obj) { return nil; }); addProperty(objectClass, "intCol", "i", sizeof(int), alignof(int), ^int(__unused id obj) { return 0; }); objc_registerClassPair(objectClass); XCTAssertEqualObjects([objectClass className], @"RuntimeGeneratedObject"); RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.objectClasses = @[objectClass]; XCTAssertEqualObjects([objectClass className], @"RuntimeGeneratedObject"); RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; [realm beginWriteTransaction]; id object = [objectClass createInRealm:realm withValue:@{@"objectCol": [[objectClass alloc] init], @"intCol": @17}]; RLMObjectSchema *schema = [object objectSchema]; XCTAssertNotNil(schema[@"objectCol"]); XCTAssertNotNil(schema[@"intCol"]); XCTAssert([[object objectCol] isKindOfClass:objectClass]); XCTAssertEqual([object intCol], 17); [realm commitWriteTransaction]; } #pragma mark - Default Property Values - (NSDictionary *)defaultValuesDictionary { return @{@"intCol" : @98, @"floatCol" : @231.0f, @"doubleCol": @123732.9231, @"boolCol" : @NO, @"dateCol" : [NSDate dateWithTimeIntervalSince1970:454321], @"stringCol": @"Westeros", @"binaryCol": [@"inputData" dataUsingEncoding:NSUTF8StringEncoding]}; } - (void)testDefaultValuesFromNoValuePresent { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; NSDictionary *inputValues = [self defaultValuesDictionary]; NSArray *keys = [inputValues allKeys]; // To ensure iteration order is stable for (NSString *key in keys) { NSMutableDictionary *dict = [inputValues mutableCopy]; [dict removeObjectForKey:key]; [DefaultObject createInRealm:realm withValue:dict]; } [realm commitWriteTransaction]; // Test allObject for DefaultObject NSDictionary *defaultValues = [DefaultObject defaultPropertyValues]; RLMResults *allObjects = [DefaultObject allObjectsInRealm:realm]; for (NSUInteger i = 0; i < keys.count; ++i) { DefaultObject *object = allObjects[i]; for (NSUInteger j = 0; j < keys.count; ++j) { NSString *key = keys[j]; if (i == j) { XCTAssertEqualObjects(object[key], defaultValues[key]); } else { XCTAssertEqualObjects(object[key], inputValues[key]); } } } } - (void)testDefaultValuesFromNSNull { RLMRealm *realm = [RLMRealm defaultRealm]; NSDictionary *defaultValues = [DefaultObject defaultPropertyValues]; NSDictionary *inputValues = [self defaultValuesDictionary]; NSArray *keys = [inputValues allKeys]; // To ensure iteration order is stable for (NSString *key in keys) { NSMutableDictionary *dict = [inputValues mutableCopy]; dict[key] = NSNull.null; RLMProperty *prop = realm.schema[@"DefaultObject"][key]; if (prop.optional) { [realm beginWriteTransaction]; [DefaultObject createInRealm:realm withValue:dict]; [realm commitWriteTransaction]; DefaultObject *object = DefaultObject.allObjects.lastObject; for (NSUInteger j = 0; j < keys.count; ++j) { NSString *key2 = keys[j]; if ([key isEqualToString:key2]) { XCTAssertEqualObjects(object[key2], prop.optional ? nil : defaultValues[key2]); } else { XCTAssertEqualObjects(object[key2], inputValues[key2]); } } } else { [realm beginWriteTransaction]; RLMAssertThrowsWithReason([DefaultObject createInRealm:realm withValue:dict], @"Invalid value '' of type 'NSNull' for "); [realm commitWriteTransaction]; } } } - (void)testDefaultNSNumberPropertyValues { void (^assertDefaults)(NumberObject *) = ^(NumberObject *no) { XCTAssertEqualObjects(no.intObj, @1); XCTAssertEqualObjects(no.floatObj, @2.2f); XCTAssertEqualObjects(no.doubleObj, @3.3); XCTAssertEqualObjects(no.boolObj, @NO); }; assertDefaults([[NumberDefaultsObject alloc] init]); RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; assertDefaults([NumberDefaultsObject createInRealm:realm withValue:@{}]); [realm cancelWriteTransaction]; } - (void)testDynamicDefaultPropertyValues { void (^assertDifferentPropertyValues)(DynamicDefaultObject *, DynamicDefaultObject *) = ^(DynamicDefaultObject *obj1, DynamicDefaultObject *obj2) { XCTAssertNotEqual(obj1.intCol, obj2.intCol); XCTAssertNotEqual(obj1.floatCol, obj2.floatCol); XCTAssertNotEqual(obj1.doubleCol, obj2.doubleCol); XCTAssertNotEqualWithAccuracy(obj1.dateCol.timeIntervalSinceReferenceDate, obj2.dateCol.timeIntervalSinceReferenceDate, 0.01f); XCTAssertNotEqualObjects(obj1.stringCol, obj2.stringCol); XCTAssertNotEqualObjects(obj1.binaryCol, obj2.binaryCol); }; assertDifferentPropertyValues([[DynamicDefaultObject alloc] init], [[DynamicDefaultObject alloc] init]); RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration]; configuration.objectClasses = @[[DynamicDefaultObject class]]; RLMRealm *realm = [RLMRealm realmWithConfiguration:configuration error:nil]; [realm beginWriteTransaction]; assertDifferentPropertyValues([DynamicDefaultObject createInRealm:realm withValue:@{}], [DynamicDefaultObject createInRealm:realm withValue:@{}]); [realm cancelWriteTransaction]; } #pragma mark - Ignored Properties - (void)testCanUseIgnoredProperty { NSURL *url = [NSURL URLWithString:@"http://realm.io"]; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; IgnoredURLObject *obj = [IgnoredURLObject new]; obj.name = @"Realm"; obj.url = url; [realm addObject:obj]; XCTAssertEqual(obj.url, url, @"ignored properties should still be assignable and gettable inside a write block"); [realm commitWriteTransaction]; XCTAssertEqual(obj.url, url, @"ignored properties should still be assignable and gettable outside a write block"); IgnoredURLObject *obj2 = [[IgnoredURLObject objectsWithPredicate:nil] firstObject]; XCTAssertNotNil(obj2, @"object with ignored property should still be stored and accessible through the realm"); XCTAssertEqualObjects(obj2.name, obj.name, @"managed property should be the same"); XCTAssertNil(obj2.url, @"ignored property should be nil when getting from realm"); } - (void)testCreateInRealmValidationForDictionary { RLMRealm *realm = [RLMRealm defaultRealm]; const char bin[4] = { 0, 1, 2, 3 }; NSData *bin1 = [[NSData alloc] initWithBytes:bin length:sizeof bin / 2]; NSDate *timeNow = [NSDate dateWithTimeIntervalSince1970:1000000]; NSDictionary * const dictValidAllTypes = @{@"boolCol" : @NO, @"intCol" : @54, @"floatCol" : @0.7f, @"doubleCol": @0.8, @"stringCol": @"foo", @"binaryCol": bin1, @"dateCol" : timeNow, @"cBoolCol" : @NO, @"longCol" : @(99), @"objectCol": NSNull.null}; [realm beginWriteTransaction]; // Test NSDictonary XCTAssertNoThrow(([AllTypesObject createInRealm:realm withValue:dictValidAllTypes]), @"Creating object with valid value types should not throw exception"); for (NSString *keyToInvalidate in dictValidAllTypes.allKeys) { NSMutableDictionary *invalidInput = [dictValidAllTypes mutableCopy]; id obj = @"invalid"; if ([keyToInvalidate isEqualToString:@"stringCol"]) { obj = @1; } invalidInput[keyToInvalidate] = obj; RLMAssertThrowsWithReasonMatching([AllTypesObject createInRealm:realm withValue:invalidInput], @"Invalid value '.*'"); } [realm commitWriteTransaction]; } - (void)testCreateInRealmValidationForArray { RLMRealm *realm = [RLMRealm defaultRealm]; // add test/link object to realm [realm beginWriteTransaction]; StringObject *to = [StringObject createInRealm:realm withValue:@[@"c"]]; [realm commitWriteTransaction]; const char bin[4] = { 0, 1, 2, 3 }; NSData *bin1 = [[NSData alloc] initWithBytes:bin length:sizeof bin / 2]; NSDate *timeNow = [NSDate dateWithTimeIntervalSince1970:1000000]; NSArray *const arrayValidAllTypes = @[@NO, @54, @0.7f, @0.8, @"foo", bin1, timeNow, @NO, @(99), to]; [realm beginWriteTransaction]; // Test NSArray XCTAssertNoThrow(([AllTypesObject createInRealm:realm withValue:arrayValidAllTypes]), @"Creating object with valid value types should not throw exception"); const NSInteger stringColIndex = 4; for (NSUInteger i = 0; i < arrayValidAllTypes.count; i++) { NSMutableArray *invalidInput = [arrayValidAllTypes mutableCopy]; id obj = @"invalid"; if (i == stringColIndex) { obj = @1; } invalidInput[i] = obj; RLMAssertThrowsWithReasonMatching([AllTypesObject createInRealm:realm withValue:invalidInput], @"Invalid value '.*'"); } [realm commitWriteTransaction]; } - (void)testCreateInRealmReusesExistingObjects { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; DogObject *dog = [DogObject createInDefaultRealmWithValue:@[@"Fido", @5]]; OwnerObject *owner = [OwnerObject createInDefaultRealmWithValue:@[@"name", dog]]; XCTAssertTrue([owner.dog isEqualToObject:dog]); XCTAssertEqual(1U, DogObject.allObjects.count); DogArrayObject *dogArray = [DogArrayObject createInDefaultRealmWithValue:@[@[dog]]]; XCTAssertTrue([dogArray.dogs[0] isEqualToObject:dog]); XCTAssertEqual(1U, DogObject.allObjects.count); [realm commitWriteTransaction]; } - (void)testCreateInRealmReusesExistingNestedObjectsByPrimaryKey { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; PrimaryEmployeeObject *eo = [PrimaryEmployeeObject createInRealm:realm withValue:@[@"Samuel", @19, @NO]]; PrimaryCompanyObject *co = [PrimaryCompanyObject createInRealm:realm withValue:@[@"Realm", @[eo], eo, @[eo]]]; [realm commitWriteTransaction]; [realm beginWriteTransaction]; [PrimaryCompanyObject createOrUpdateInRealm:realm withValue:@{ @"name": @"Realm", @"intern": @{@"name":@"Samuel", @"hired":@YES}, }]; [realm commitWriteTransaction]; XCTAssertEqual(1U, co.employees.count); XCTAssertEqual(1U, [PrimaryEmployeeObject allObjectsInRealm:realm].count); XCTAssertEqualObjects(@"Samuel", eo.name); XCTAssertEqual(YES, eo.hired); XCTAssertEqual(19, eo.age); [realm beginWriteTransaction]; [PrimaryCompanyObject createOrUpdateInRealm:realm withValue:@{ @"name": @"Realm", @"employees": @[@{@"name":@"Samuel", @"hired":@NO}], @"intern": @{@"name":@"Samuel", @"age":@20}, }]; [realm commitWriteTransaction]; XCTAssertEqual(1U, co.employees.count); XCTAssertEqual(1U, [PrimaryEmployeeObject allObjectsInRealm:realm].count); XCTAssertEqualObjects(@"Samuel", eo.name); XCTAssertEqual(NO, eo.hired); XCTAssertEqual(20, eo.age); [realm beginWriteTransaction]; [PrimaryCompanyObject createOrUpdateInRealm:realm withValue:@{@"name": @"Realm", @"wrappedIntern": @[eo]}]; [realm commitWriteTransaction]; XCTAssertEqual(1U, [[PrimaryEmployeeObject allObjectsInRealm:realm] count]); } - (void)testCreateInRealmCopiesFromOtherRealm { RLMRealm *realm1 = [RLMRealm defaultRealm]; RLMRealm *realm2 = [self realmWithTestPath]; [realm1 beginWriteTransaction]; [realm2 beginWriteTransaction]; DogObject *dog = [DogObject createInDefaultRealmWithValue:@[@"Fido", @5]]; OwnerObject *owner = [OwnerObject createInRealm:realm2 withValue:@[@"name", dog]]; XCTAssertFalse([owner.dog isEqualToObject:dog]); XCTAssertEqual(1U, DogObject.allObjects.count); XCTAssertEqual(1U, [DogObject allObjectsInRealm:realm2].count); DogArrayObject *dogArray = [DogArrayObject createInRealm:realm2 withValue:@[@[dog]]]; XCTAssertFalse([dogArray.dogs[0] isEqualToObject:dog]); XCTAssertEqual(1U, DogObject.allObjects.count); XCTAssertEqual(2U, [DogObject allObjectsInRealm:realm2].count); [realm1 commitWriteTransaction]; [realm2 commitWriteTransaction]; } - (void)testCreateInRealmWithOtherObjects { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; DateObjectNoThrow *object = [DateObjectNoThrow createInDefaultRealmWithValue:@[NSDate.date, NSDate.date]]; // create subclass with instance of base class with/without default objects XCTAssertNoThrow([DateSubclassObject createInDefaultRealmWithValue:object]); XCTAssertNoThrow([DateObjectNoThrow createInDefaultRealmWithValue:object]); // create using non-realm object with custom getter SubclassDateObject *obj = [SubclassDateObject new]; obj.dateCol = [NSDate dateWithTimeIntervalSinceReferenceDate:1000]; obj.date2 = [NSDate dateWithTimeIntervalSinceReferenceDate:2000]; obj.date3 = [NSDate dateWithTimeIntervalSinceReferenceDate:3000]; [DateDefaultsObject createInDefaultRealmWithValue:obj]; XCTAssertEqual(2U, DateObjectNoThrow.allObjects.count); [realm commitWriteTransaction]; } - (void)testObjectDescription { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; // Init object before adding to realm EmployeeObject *soInit = [[EmployeeObject alloc] init]; soInit.name = @"Peter"; soInit.age = 30; soInit.hired = YES; [realm addObject:soInit]; // description asserts block void (^descriptionAsserts)(NSString *) = ^(NSString *description) { XCTAssertTrue([description rangeOfString:@"name"].location != NSNotFound, @"column names should be displayed when calling \"description\" on RLMObject subclasses"); XCTAssertTrue([description rangeOfString:@"Peter"].location != NSNotFound, @"column values should be displayed when calling \"description\" on RLMObject subclasses"); XCTAssertTrue([description rangeOfString:@"age"].location != NSNotFound, @"column names should be displayed when calling \"description\" on RLMObject subclasses"); XCTAssertTrue([description rangeOfString:[@30 description]].location != NSNotFound, @"column values should be displayed when calling \"description\" on RLMObject subclasses"); XCTAssertTrue([description rangeOfString:@"hired"].location != NSNotFound, @"column names should be displayed when calling \"description\" on RLMObject subclasses"); XCTAssertTrue([description rangeOfString:[@YES description]].location != NSNotFound, @"column values should be displayed when calling \"description\" on RLMObject subclasses"); }; // Test description in write block descriptionAsserts(soInit.description); [realm commitWriteTransaction]; // Test description in read block NSString *objDescription = [[[EmployeeObject objectsWithPredicate:nil] firstObject] description]; descriptionAsserts(objDescription); soInit = [[EmployeeObject alloc] init]; soInit.age = 20; XCTAssert([soInit.description rangeOfString:@"(null)"].location != NSNotFound); } - (void)testObjectCycleDescription { CycleObject *obj = [[CycleObject alloc] init]; [RLMRealm.defaultRealm transactionWithBlock:^{ [RLMRealm.defaultRealm addObject:obj]; [obj.objects addObject:obj]; }]; XCTAssertNoThrow(obj.description); } - (void)testDataObjectDescription { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; char longData[200]; [DataObject createInRealm:realm withValue:@[[NSData dataWithBytes:&longData length:200], [NSData dataWithBytes:&longData length:2]]]; [realm commitWriteTransaction]; DataObject *obj = [DataObject allObjectsInRealm:realm].firstObject; XCTAssertTrue([obj.description rangeOfString:@"200 total bytes"].location != NSNotFound); XCTAssertTrue([obj.description rangeOfString:@"2 total bytes"].location != NSNotFound); } - (void)testDeletedObjectDescription { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; EmployeeObject *obj = [EmployeeObject createInRealm:realm withValue:@[@"Peter", @30, @YES]]; [realm deleteObject:obj]; [realm commitWriteTransaction]; XCTAssertNoThrow(obj.description); } - (void)testManagedObjectUnknownKey { IntObject *obj = [[IntObject alloc] init]; RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [realm addObject:obj]; [realm commitWriteTransaction]; RLMAssertThrowsWithReason([obj objectForKeyedSubscript:@""], @"Invalid property name '' for class 'IntObject'"); RLMAssertThrowsWithReason([obj setObject:@0 forKeyedSubscript:@""], @"Invalid property name '' for class 'IntObject'"); } - (void)testUnmanagedRealmObjectUnknownKey { IntObject *obj = [[IntObject alloc] init]; XCTAssertThrows([obj objectForKeyedSubscript:@""]); XCTAssertThrows([obj setObject:@0 forKeyedSubscript:@""]); } - (void)testEquality { IntObject *obj = [[IntObject alloc] init]; IntObject *otherObj = [[IntObject alloc] init]; RLMRealm *realm = [RLMRealm defaultRealm]; RLMRealm *otherRealm = [self realmWithTestPath]; XCTAssertFalse([obj isEqual:[NSObject new]], @"Comparing an RLMObject to a non-RLMObject should be false."); XCTAssertFalse([obj isEqualToObject:(RLMObject *)[NSObject new]], @"Comparing an RLMObject to a non-RLMObject should be false."); XCTAssertTrue([obj isEqual:obj], @"Same instance."); XCTAssertTrue([obj isEqualToObject:obj], @"Same instance."); XCTAssertFalse([obj isEqualToObject:otherObj], @"Comparison outside of realm."); [realm beginWriteTransaction]; [realm addObject:obj]; [realm commitWriteTransaction]; XCTAssertFalse([obj isEqualToObject:otherObj], @"One in realm, the other is not."); XCTAssertTrue([obj isEqualToObject:[IntObject allObjects][0]], @"Same table and index."); [otherRealm beginWriteTransaction]; [otherRealm addObject:otherObj]; [otherRealm commitWriteTransaction]; XCTAssertFalse([obj isEqualToObject:otherObj], @"Different realms."); [realm beginWriteTransaction]; [realm addObject:[[IntObject alloc] init]]; [realm addObject:[[BoolObject alloc] init]]; [realm commitWriteTransaction]; XCTAssertFalse([obj isEqualToObject:[IntObject allObjects][1]], @"Same table, different index."); XCTAssertFalse([obj isEqualToObject:[BoolObject allObjects][0]], @"Different tables."); } - (void)testCrossThreadAccess { IntObject *obj = [[IntObject alloc] init]; // Unmanaged object can be accessed from other threads [self dispatchAsyncAndWait:^{ XCTAssertNoThrow(obj.intCol = 5); }]; [RLMRealm.defaultRealm beginWriteTransaction]; [RLMRealm.defaultRealm addObject:obj]; [RLMRealm.defaultRealm commitWriteTransaction]; [self dispatchAsyncAndWait:^{ RLMAssertThrowsWithReason(obj.intCol, @"incorrect thread"); }]; } - (void)testIsDeleted { StringObject *obj1 = [[StringObject alloc] initWithValue:@[@"a"]]; XCTAssertEqual(obj1.invalidated, NO); RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; [realm addObject:obj1]; StringObject *obj2 = [StringObject createInRealm:realm withValue:@[@"b"]]; XCTAssertEqual([obj1 isInvalidated], NO); XCTAssertEqual(obj2.invalidated, NO); [realm commitWriteTransaction]; // delete [realm beginWriteTransaction]; // Delete directly [realm deleteObject:obj1]; // Delete as result of query since then obj2's realm could point to a different instance [realm deleteObject:[[StringObject allObjectsInRealm:realm] firstObject]]; XCTAssertEqual(obj1.invalidated, YES); XCTAssertEqual(obj2.invalidated, YES); RLMAssertThrowsWithReason([realm addObject:obj1], @"deleted or invalidated"); NSArray *propObject = @[@"", @[obj2], @[]]; RLMAssertThrowsWithReason([ArrayPropertyObject createInRealm:realm withValue:propObject], @"deleted or invalidated"); [realm commitWriteTransaction]; XCTAssertEqual(obj1.invalidated, YES); XCTAssertNil(obj1.realm, @"Realm should be nil after deletion"); } - (void)testPrimaryKey { [[RLMRealm defaultRealm] beginWriteTransaction]; [PrimaryStringObject createInDefaultRealmWithValue:(@[@"string", @1])]; [PrimaryStringObject createInDefaultRealmWithValue:(@[@"string2", @1])]; RLMAssertThrowsWithReason([PrimaryStringObject createInDefaultRealmWithValue:(@[@"string", @1])], @"existing primary key value"); [PrimaryIntObject createInDefaultRealmWithValue:(@[@1])]; [PrimaryIntObject createInDefaultRealmWithValue:(@{@"intCol": @2})]; RLMAssertThrowsWithReason([PrimaryIntObject createInDefaultRealmWithValue:(@[@1])], @"existing primary key value"); [PrimaryInt64Object createInDefaultRealmWithValue:(@[@(1LL << 40)])]; [PrimaryInt64Object createInDefaultRealmWithValue:(@[@(1LL << 41)])]; RLMAssertThrowsWithReason([PrimaryInt64Object createInDefaultRealmWithValue:(@[@(1LL << 40)])], @"existing primary key value"); [PrimaryNullableIntObject createInDefaultRealmWithValue:@[@1]]; [PrimaryNullableIntObject createInDefaultRealmWithValue:(@{@"optIntCol": @2, @"value": @0})]; [PrimaryNullableIntObject createInDefaultRealmWithValue:@[NSNull.null]]; RLMAssertThrowsWithReason([PrimaryNullableIntObject createInDefaultRealmWithValue:(@[@1, @0])], @"existing primary key value"); RLMAssertThrowsWithReason([PrimaryNullableIntObject createInDefaultRealmWithValue:(@[NSNull.null, @0])], @"existing primary key value"); [[RLMRealm defaultRealm] commitWriteTransaction]; } - (void)testCreateOrUpdate { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [PrimaryNullableStringObject createOrUpdateInDefaultRealmWithValue:@[@"string", @1]]; RLMResults *objects = [PrimaryNullableStringObject allObjects]; XCTAssertEqual([objects count], 1U, @"Should have 1 object"); XCTAssertEqual([(PrimaryNullableStringObject *)objects[0] intCol], 1, @"Value should be 1"); [PrimaryNullableStringObject createOrUpdateInRealm:realm withValue:@{@"stringCol": @"string2", @"intCol": @2}]; XCTAssertEqual([objects count], 2U, @"Should have 2 objects"); [PrimaryNullableStringObject createOrUpdateInRealm:realm withValue:@{@"intCol": @5}]; [PrimaryNullableStringObject createOrUpdateInRealm:realm withValue:@{@"intCol": @7}]; XCTAssertEqual([PrimaryNullableStringObject objectInRealm:realm forPrimaryKey:NSNull.null].intCol, 7); [PrimaryNullableStringObject createOrUpdateInRealm:realm withValue:@{@"stringCol": NSNull.null, @"intCol": @11}]; XCTAssertEqual([PrimaryNullableStringObject objectInRealm:realm forPrimaryKey:nil].intCol, 11); // upsert with new secondary property [PrimaryNullableStringObject createOrUpdateInDefaultRealmWithValue:@[@"string", @3]]; XCTAssertEqual([objects count], 3U, @"Should have 3 objects"); XCTAssertEqual([(PrimaryNullableStringObject *)objects[0] intCol], 3, @"Value should be 3"); [realm commitWriteTransaction]; } - (void)testCreateOrUpdateNestedObjects { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; [PrimaryNestedObject createOrUpdateInDefaultRealmWithValue:@[@0, @[@"string", @1], @[@[@"string", @1]], @[@"string"], @[@[@1]], @""]]; XCTAssertEqual([[PrimaryNestedObject allObjects] count], 1U, @"Should have 1 object"); XCTAssertEqual([[PrimaryStringObject allObjects] count], 1U, @"Should have 1 object"); XCTAssertEqual([[PrimaryIntObject allObjects] count], 1U, @"Should have 1 object"); // update parent and nested object [PrimaryNestedObject createOrUpdateInDefaultRealmWithValue:@{@"primaryCol": @0, @"primaryStringObject": @[@"string", @2], @"primaryStringObjectWrapper": @[@[@"string", @2]], @"stringObject": @[@"string2"]}]; XCTAssertEqual([[PrimaryNestedObject allObjects] count], 1U, @"Should have 1 object"); XCTAssertEqual([[PrimaryStringObject allObjects] count], 1U, @"Should have 1 object"); XCTAssertEqual([PrimaryStringObject.allObjects.lastObject intCol], 2, @"intCol should be 2"); XCTAssertEqualObjects([PrimaryNestedObject.allObjects.lastObject stringCol], @"", @"stringCol should not have been updated"); XCTAssertEqual(1U, [PrimaryNestedObject.allObjects.lastObject primaryIntArray].count, @"intArray should not have been overwritten"); XCTAssertEqual([[StringObject allObjects] count], 2U, @"Should have 2 objects"); // test partial update nulling out object/array properties [PrimaryNestedObject createOrUpdateInDefaultRealmWithValue:@{@"primaryCol": @0, @"stringCol": @"updated", @"stringObject": NSNull.null, @"primaryIntArray": NSNull.null}]; PrimaryNestedObject *obj = PrimaryNestedObject.allObjects.lastObject; XCTAssertEqual(2, obj.primaryStringObject.intCol, @"primaryStringObject should not have changed"); XCTAssertEqualObjects(obj.stringCol, @"updated", @"stringCol should have been updated"); XCTAssertEqual(0U, obj.primaryIntArray.count, @"intArray should not have been emptied"); XCTAssertNil(obj.stringObject, @"stringObject should be nil"); // inserting new object should update nested obj = [PrimaryNestedObject createOrUpdateInDefaultRealmWithValue:@[@1, @[@"string", @3], @[@[@"string", @3]], @[@"string"], @[], @""]]; XCTAssertEqual([[PrimaryNestedObject allObjects] count], 2U, @"Should have 2 objects"); XCTAssertEqual([[PrimaryStringObject allObjects] count], 1U, @"Should have 1 object"); XCTAssertEqual([(PrimaryStringObject *)[[PrimaryStringObject allObjects] lastObject] intCol], 3, @"intCol should be 3"); // test addOrUpdateObject obj.primaryStringObject = [PrimaryStringObject createInDefaultRealmWithValue:@[@"string2", @1]]; PrimaryNestedObject *obj1 = [[PrimaryNestedObject alloc] initWithValue:@[@1, @[@"string2", @4], @[@[@"string2", @4]], @[@"string"], @[@[@1], @[@2]], @""]]; [realm addOrUpdateObject:obj1]; XCTAssertEqual([[PrimaryNestedObject allObjects] count], 2U, @"Should have 2 objects"); XCTAssertEqual([[PrimaryStringObject allObjects] count], 2U, @"Should have 2 objects"); XCTAssertEqual([[PrimaryIntObject allObjects] count], 2U, @"Should have 2 objects"); XCTAssertEqual([(PrimaryStringObject *)[[PrimaryStringObject allObjects] lastObject] intCol], 4, @"intCol should be 4"); [realm commitWriteTransaction]; } - (void)testCreateOrUpdateWithReorderedColumns { @autoreleasepool { // Create a Realm file with the properties in reverse order RLMObjectSchema *objectSchema = [RLMObjectSchema schemaForObjectClass:PrimaryStringObject.class]; objectSchema.properties = @[objectSchema.properties[1], objectSchema.properties[0]]; RLMSchema *schema = [RLMSchema new]; schema.objectSchema = @[objectSchema]; RLMRealm *realm = [self realmWithTestPathAndSchema:schema]; [realm beginWriteTransaction]; [PrimaryStringObject createOrUpdateInRealm:realm withValue:@[@5, @"a"]]; [realm commitWriteTransaction]; } RLMRealm *realm = [self realmWithTestPath]; [realm beginWriteTransaction]; XCTAssertEqual([PrimaryStringObject objectInRealm:realm forPrimaryKey:@"a"].intCol, 5); // Values in array are used in property declaration order, not table column order [PrimaryStringObject createOrUpdateInRealm:realm withValue:@[@"a", @6]]; XCTAssertEqual([PrimaryStringObject objectInRealm:realm forPrimaryKey:@"a"].intCol, 6); [PrimaryStringObject createOrUpdateInRealm:realm withValue:@{@"stringCol": @"a", @"intCol": @7}]; XCTAssertEqual([PrimaryStringObject objectInRealm:realm forPrimaryKey:@"a"].intCol, 7); [realm commitWriteTransaction]; } - (void)testObjectInSet { [[RLMRealm defaultRealm] beginWriteTransaction]; // set object with primary and non primary keys as they both override isEqual and hash PrimaryStringObject *obj = [PrimaryStringObject createInDefaultRealmWithValue:(@[@"string2", @1])]; StringObject *strObj = [StringObject createInDefaultRealmWithValue:@[@"string"]]; NSMutableSet *dict = [NSMutableSet set]; [dict addObject:obj]; [dict addObject:strObj]; // primary key objects should match even with duplicate instances of the same object XCTAssertTrue([dict containsObject:obj]); XCTAssertTrue([dict containsObject:[[PrimaryStringObject allObjects] firstObject]]); // non-primary key objects should only match when comparing identical instances XCTAssertTrue([dict containsObject:strObj]); XCTAssertFalse([dict containsObject:[[StringObject allObjects] firstObject]]); [[RLMRealm defaultRealm] commitWriteTransaction]; } - (void)testObjectForKey { [RLMRealm.defaultRealm beginWriteTransaction]; PrimaryStringObject *strObj = [PrimaryStringObject createInDefaultRealmWithValue:@[@"key", @0]]; PrimaryNullableStringObject *nullStrObj = [PrimaryNullableStringObject createInDefaultRealmWithValue:@[NSNull.null, @0]]; PrimaryIntObject *intObj = [PrimaryIntObject createInDefaultRealmWithValue:@[@0]]; PrimaryNullableIntObject *nonNullIntObj = [PrimaryNullableIntObject createInDefaultRealmWithValue:@[@0]]; PrimaryNullableIntObject *nullIntObj = [PrimaryNullableIntObject createInDefaultRealmWithValue:@[NSNull.null]]; [RLMRealm.defaultRealm commitWriteTransaction]; // no PK RLMAssertThrowsWithReason([StringObject objectForPrimaryKey:@""], @"does not have a primary key"); RLMAssertThrowsWithReason([IntObject objectForPrimaryKey:@0], @"does not have a primary key"); RLMAssertThrowsWithReason([StringObject objectForPrimaryKey:NSNull.null], @"does not have a primary key"); RLMAssertThrowsWithReason([StringObject objectForPrimaryKey:nil], @"does not have a primary key"); RLMAssertThrowsWithReason([IntObject objectForPrimaryKey:nil], @"does not have a primary key"); // wrong PK type RLMAssertThrowsWithReasonMatching([PrimaryStringObject objectForPrimaryKey:@0], @"Invalid value '0' of type '.*Number.*' for 'string' property 'PrimaryStringObject.stringCol'."); RLMAssertThrowsWithReasonMatching([PrimaryStringObject objectForPrimaryKey:@[]], @"of type '.*Array.*' for 'string' property 'PrimaryStringObject.stringCol'."); RLMAssertThrowsWithReasonMatching([PrimaryIntObject objectForPrimaryKey:@""], @"Invalid value '' of type '.*String.*' for 'int' property 'PrimaryIntObject.intCol'."); RLMAssertThrowsWithReason([PrimaryIntObject objectForPrimaryKey:NSNull.null], @"Invalid value '' of type 'NSNull' for 'int' property 'PrimaryIntObject.intCol'."); RLMAssertThrowsWithReason([PrimaryIntObject objectForPrimaryKey:nil], @"Invalid value '(null)' of type '(null)' for 'int' property 'PrimaryIntObject.intCol'."); RLMAssertThrowsWithReason([PrimaryStringObject objectForPrimaryKey:NSNull.null], @"Invalid value '' of type 'NSNull' for 'string' property 'PrimaryStringObject.stringCol'."); RLMAssertThrowsWithReason([PrimaryStringObject objectForPrimaryKey:nil], @"Invalid value '(null)' of type '(null)' for 'string' property 'PrimaryStringObject.stringCol'."); // no object with key XCTAssertNil([PrimaryStringObject objectForPrimaryKey:@"bad key"]); XCTAssertNil([PrimaryIntObject objectForPrimaryKey:@1]); // object with key exists XCTAssertEqualObjects(strObj, [PrimaryStringObject objectForPrimaryKey:@"key"]); XCTAssertEqualObjects(nullStrObj, [PrimaryNullableStringObject objectForPrimaryKey:NSNull.null]); XCTAssertEqualObjects(nullStrObj, [PrimaryNullableStringObject objectForPrimaryKey:nil]); XCTAssertEqualObjects(intObj, [PrimaryIntObject objectForPrimaryKey:@0]); XCTAssertEqualObjects(nonNullIntObj, [PrimaryNullableIntObject objectForPrimaryKey:@0]); XCTAssertEqualObjects(nullIntObj, [PrimaryNullableIntObject objectForPrimaryKey:NSNull.null]); XCTAssertEqualObjects(nullIntObj, [PrimaryNullableIntObject objectForPrimaryKey:nil]); // nil realm throws RLMAssertThrowsWithReason([PrimaryIntObject objectInRealm:self.nonLiteralNil forPrimaryKey:@0], @"Realm must not be nil"); } - (void)testClassExtension { RLMRealm *realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; BaseClassStringObject *bObject = [[BaseClassStringObject alloc ] init]; bObject.intCol = 1; bObject.stringCol = @"stringVal"; [realm addObject:bObject]; [realm commitWriteTransaction]; BaseClassStringObject *objectFromRealm = [BaseClassStringObject allObjects][0]; XCTAssertEqual(1, objectFromRealm.intCol); XCTAssertEqualObjects(@"stringVal", objectFromRealm.stringCol); } @end