diff --git a/lib/rpc.js b/lib/rpc.js index 675489da..46075bfc 100644 --- a/lib/rpc.js +++ b/lib/rpc.js @@ -90,6 +90,10 @@ function serialize(realmId, value) { return {type: objectTypes.FUNCTION}; } + if (typeof value === 'undefined') { + return {type: 'undefined'}; + } + if (!value || typeof value != 'object') { return {value: value}; } diff --git a/src/RJSObject.mm b/src/RJSObject.mm index 2405e1ca..c6bd1224 100644 --- a/src/RJSObject.mm +++ b/src/RJSObject.mm @@ -100,7 +100,7 @@ template<> JSValueRef RJSAccessor::default_value_for_property(JSContextRef ctx, } template<> bool RJSAccessor::is_null(JSContextRef ctx, JSValueRef &val) { - return JSValueIsUndefined(ctx, val) || JSValueIsNull(ctx, val); + return JSValueIsNull(ctx, val); } template<> JSValueRef RJSAccessor::null_value(JSContextRef ctx) { return JSValueMakeNull(ctx); diff --git a/src/RJSSchema.mm b/src/RJSSchema.mm index da01f7f0..50c504cd 100644 --- a/src/RJSSchema.mm +++ b/src/RJSSchema.mm @@ -49,10 +49,20 @@ static inline Property RJSParseProperty(JSContextRef ctx, JSObjectRef propertyOb static JSStringRef nameString = JSStringCreateWithUTF8CString("name"); static JSStringRef typeString = JSStringCreateWithUTF8CString("type"); static JSStringRef objectTypeString = JSStringCreateWithUTF8CString("objectType"); + static JSStringRef optionalString = JSStringCreateWithUTF8CString("optional"); Property prop; prop.name = RJSValidatedStringProperty(ctx, propertyObject, nameString); + prop.is_nullable = false; + JSValueRef optionalValue = JSObjectGetProperty(ctx, propertyObject, optionalString, NULL); + if (!JSValueIsUndefined(ctx, optionalValue)) { + if (!JSValueIsBoolean(ctx, optionalValue)) { + throw std::runtime_error("'optional' designation expected to be of type boolean"); + } + prop.is_nullable = JSValueToBoolean(ctx, optionalValue); + } + std::string type = RJSValidatedStringProperty(ctx, propertyObject, typeString); if (type == "PropTypesBOOL") { prop.type = PropertyTypeBool; @@ -75,19 +85,14 @@ static inline Property RJSParseProperty(JSContextRef ctx, JSObjectRef propertyOb else if (type == "PropTypesDATA") { prop.type = PropertyTypeData; } - else if (type == "PropTypesOBJECT") { - prop.type = PropertyTypeObject; - prop.object_type = RJSValidatedStringProperty(ctx, propertyObject, objectTypeString); - prop.is_nullable = true; - } else if (type == "PropTypesLIST") { prop.type = PropertyTypeArray; prop.object_type = RJSValidatedStringProperty(ctx, propertyObject, objectTypeString); } else { prop.type = PropertyTypeObject; - prop.object_type = type; prop.is_nullable = true; + prop.object_type = type == "PropTypesOBJECT" ? RJSValidatedStringProperty(ctx, propertyObject, objectTypeString) : type; } return prop; } diff --git a/src/RealmRPC.cpp b/src/RealmRPC.cpp index 32392e24..45ce0782 100644 --- a/src/RealmRPC.cpp +++ b/src/RealmRPC.cpp @@ -315,6 +315,10 @@ JSValueRef RPCServer::deserialize_json_value(const json dict) } return date; } + else if (type_string == "undefined") { + return JSValueMakeUndefined(m_context); + } + assert(0); } if (value.is_null()) { @@ -353,5 +357,5 @@ JSValueRef RPCServer::deserialize_json_value(const json dict) return js_object; } - return JSValueMakeUndefined(m_context); + assert(0); } diff --git a/src/object-store/object_accessor.hpp b/src/object-store/object_accessor.hpp index 25d79dfd..cd45e8f7 100644 --- a/src/object-store/object_accessor.hpp +++ b/src/object-store/object_accessor.hpp @@ -156,6 +156,11 @@ namespace realm { } size_t column = property.table_column; + if (property.is_nullable && Accessor::is_null(ctx, value)) { + m_row.set_null(column); + return; + } + switch (property.type) { case PropertyTypeBool: m_row.set_bool(column, Accessor::to_bool(ctx, value)); @@ -209,6 +214,10 @@ namespace realm { using Accessor = NativeAccessor; size_t column = property.table_column; + if (property.is_nullable && m_row.is_null(column)) { + return Accessor::null_value(ctx); + } + switch (property.type) { case PropertyTypeBool: return Accessor::from_bool(ctx, m_row.get_bool(column)); diff --git a/src/object-store/object_store.cpp b/src/object-store/object_store.cpp index c282e20b..1949c285 100644 --- a/src/object-store/object_store.cpp +++ b/src/object-store/object_store.cpp @@ -555,11 +555,7 @@ InvalidNullabilityException::InvalidNullabilityException(std::string const& obje m_what = "'Object' property '" + property.name + "' must be nullable."; } else { -#if REALM_NULL_STRINGS == 1 m_what = "Array or Mixed property '" + property.name + "' cannot be nullable"; -#else - m_what = "Only 'Object' property types are nullable"; -#endif } } diff --git a/src/object-store/schema.cpp b/src/object-store/schema.cpp index bf7a6181..911dc4e5 100644 --- a/src/object-store/schema.cpp +++ b/src/object-store/schema.cpp @@ -71,11 +71,7 @@ void Schema::validate() const // check nullablity if (prop.is_nullable) { -#if REALM_NULL_STRINGS == 1 if (prop.type == PropertyTypeArray || prop.type == PropertyTypeAny) { -#else - if (prop.type != PropertyTypeObject) { -#endif exceptions.emplace_back(InvalidNullabilityException(object.name, prop)); } } diff --git a/tests/ObjectTests.js b/tests/ObjectTests.js index 47aab7ab..e055f95f 100644 --- a/tests/ObjectTests.js +++ b/tests/ObjectTests.js @@ -47,6 +47,34 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(object.nonexistent, undefined); }, + testNullableBasicTypesPropertyGetters: function() { + var nullValues = [null, null, null, null, null, null, null]; + var basicTypesValues = [true, 1, 1.1, 1.11, 'string', new Date(1), 'DATA']; + + var realm = new Realm({schema: [schemas.NullableBasicTypes]}); + var nullObject = null; + var object = null; + realm.write(function() { + nullObject = realm.create('NullableBasicTypesObject', nullValues); + object = realm.create('NullableBasicTypesObject', basicTypesValues); + }); + + for (var i = 0; i < schemas.BasicTypes.properties.length; i++) { + var prop = schemas.BasicTypes.properties[i]; + TestCase.assertEqual(nullObject[prop.name], null); + + if (prop.type == Realm.Types.FLOAT) { + TestCase.assertEqualWithTolerance(object[prop.name], basicTypesValues[i], 0.000001); + } + else if (prop.type == Realm.Types.DATE) { + TestCase.assertEqual(object[prop.name].getTime(), basicTypesValues[i].getTime()); + } + else { + TestCase.assertEqual(object[prop.name], basicTypesValues[i]); + } + } + + }, testBasicTypesPropertySetters: function() { var basicTypesValues = [true, 1, 1.1, 1.11, 'string', new Date(1), 'DATA']; var realm = new Realm({schema: [schemas.BasicTypes]}); @@ -129,6 +157,57 @@ module.exports = BaseTest.extend({ TestCase.assertEqual(obj.boolCol, false, 'bool value changed outside transaction'); }, + testNullableBasicTypesPropertySetters: function() { + var basicTypesValues = [true, 1, 1.1, 1.11, 'string', new Date(1), 'DATA']; + var realm = new Realm({schema: [schemas.NullableBasicTypes]}); + var obj = null; + + realm.write(function() { + obj = realm.create('NullableBasicTypesObject', basicTypesValues); + for (var prop of schemas.NullableBasicTypes.properties) { + obj[prop.name] = null; + } + }); + + for (var prop of schemas.NullableBasicTypes.properties) { + TestCase.assertEqual(obj[prop.name], null); + } + + realm.write(function() { + TestCase.assertThrows(function() { + obj.boolCol = 'cat'; + }); + TestCase.assertThrows(function() { + obj.intCol = 'dog'; + }); + + TestCase.assertThrows(function() { + obj.boolCol = undefined; + }); + TestCase.assertThrows(function() { + obj.intCol = undefined; + }); + TestCase.assertThrows(function() { + obj.floatCol = undefined; + }); + TestCase.assertThrows(function() { + obj.doubleCol = undefined; + }); + TestCase.assertThrows(function() { + obj.stringCol = undefined; + }); + TestCase.assertThrows(function() { + obj.dateCol = undefined; + }); + TestCase.assertThrows(function() { + obj.dataCol = undefined; + }); + }); + + TestCase.assertThrows(function() { + obj.boolCol = null; + }, 'can only set property values in a write transaction'); + }, testLinkTypesPropertyGetters: function() { var realm = new Realm({schema: [schemas.LinkTypes, schemas.TestObject]}); var obj = null; diff --git a/tests/schemas.js b/tests/schemas.js index 407bf171..718a1ff4 100644 --- a/tests/schemas.js +++ b/tests/schemas.js @@ -53,6 +53,19 @@ exports.BasicTypes = { ] }; +exports.NullableBasicTypes = { + name: 'NullableBasicTypesObject', + properties: [ + {name: 'boolCol', type: Realm.Types.BOOL, optional: true}, + {name: 'intCol', type: Realm.Types.INT, optional: true}, + {name: 'floatCol', type: Realm.Types.FLOAT, optional: true}, + {name: 'doubleCol', type: Realm.Types.DOUBLE, optional: true}, + {name: 'stringCol', type: Realm.Types.STRING, optional: true}, + {name: 'dateCol', type: Realm.Types.DATE, optional: true}, + {name: 'dataCol', type: Realm.Types.DATA, optional: true}, + ] +}; + exports.LinkTypes = { name: 'LinkTypesObject', properties: [