From d2182267c0c16386c8b4fc78af4535384db6e836 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Wed, 27 Sep 2017 16:51:43 +0200 Subject: [PATCH 1/4] Allow null PKs for strings --- Realm/Realm/Handles/SharedRealmHandle.cs | 56 +++++++----------------- Realm/Realm/Handles/TableHandle.cs | 11 +++-- Realm/Realm/Helpers/Operator.cs | 12 ++--- Tests/Tests.Shared/AddOrUpdateTests.cs | 47 +++++++++++--------- Tests/Tests.Shared/PrimaryKeyTests.cs | 48 ++++++++------------ Tests/Tests.Shared/TestObjects.cs | 2 + wrappers/src/shared_realm_cs.cpp | 12 +++-- 7 files changed, 85 insertions(+), 103 deletions(-) diff --git a/Realm/Realm/Handles/SharedRealmHandle.cs b/Realm/Realm/Handles/SharedRealmHandle.cs index d0eaceef89..9870f034e2 100644 --- a/Realm/Realm/Handles/SharedRealmHandle.cs +++ b/Realm/Realm/Handles/SharedRealmHandle.cs @@ -94,19 +94,15 @@ public static extern IntPtr open(Native.Configuration configuration, public static extern IntPtr create_object(SharedRealmHandle sharedRealm, TableHandle table, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_create_object_int_unique", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr create_object_unique(SharedRealmHandle sharedRealm, TableHandle table, long key, + public static extern IntPtr create_object_unique(SharedRealmHandle sharedRealm, TableHandle table, long key, [MarshalAs(UnmanagedType.I1)] bool has_value, [MarshalAs(UnmanagedType.I1)] bool is_nullable, [MarshalAs(UnmanagedType.I1)] bool update, [MarshalAs(UnmanagedType.I1)] out bool is_new, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_create_object_string_unique", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr create_object_unique(SharedRealmHandle sharedRealm, TableHandle table, - [MarshalAs(UnmanagedType.LPWStr)] string value, IntPtr valueLen, - [MarshalAs(UnmanagedType.I1)] bool update, - [MarshalAs(UnmanagedType.I1)] out bool is_new, out NativeException ex); - - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_create_object_null_unique", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr create_object_unique(SharedRealmHandle sharedRealm, TableHandle table, + [MarshalAs(UnmanagedType.LPWStr)] string value, IntPtr valueLen, [MarshalAs(UnmanagedType.I1)] bool has_value, + [MarshalAs(UnmanagedType.I1)] bool is_nullable, [MarshalAs(UnmanagedType.I1)] bool update, [MarshalAs(UnmanagedType.I1)] out bool is_new, out NativeException ex); @@ -264,55 +260,35 @@ public IntPtr CreateObject(TableHandle table) public IntPtr CreateObjectWithPrimaryKey(Property pkProperty, object primaryKey, TableHandle table, string parentType, bool update, out bool isNew) { + if (primaryKey == null && !pkProperty.Type.IsNullable()) + { + throw new ArgumentException($"{parentType}'s primary key is defined as non-nullable, but the value passed is null"); + } + switch (pkProperty.Type.UnderlyingType()) { case PropertyType.String: - if (primaryKey == null) - { - throw new ArgumentNullException(nameof(primaryKey), "Object identifiers cannot be null"); - } - - if (primaryKey is string stringKey) - { - return CreateObjectWithPrimaryKey(table, stringKey, update, out isNew); - } - - throw new ArgumentException($"{parentType}'s primary key is defined as string, but the value passed is {primaryKey.GetType().Name}"); + var stringKey = (string)primaryKey; + return CreateObjectWithPrimaryKey(table, stringKey, pkProperty.Type.IsNullable(), update, out isNew); case PropertyType.Int: - if (primaryKey == null) - { - if (!pkProperty.Type.IsNullable()) - { - throw new ArgumentException($"{parentType}'s primary key is defined as non-nullable, but the value passed is null"); - } - - return CreateObjectWithPrimaryKey(table, update, out isNew); - } - - var longKey = Convert.ToInt64(primaryKey); + var longKey = primaryKey == null ? (long?)null : Convert.ToInt64(primaryKey); return CreateObjectWithPrimaryKey(table, longKey, pkProperty.Type.IsNullable(), update, out isNew); default: throw new NotSupportedException($"Unexpected primary key of type: {pkProperty.Type}"); } } - private IntPtr CreateObjectWithPrimaryKey(TableHandle table, long key, bool isNullable, bool update, out bool isNew) - { - var result = NativeMethods.create_object_unique(this, table, key, isNullable, update, out isNew, out var ex); - ex.ThrowIfNecessary(); - return result; - } - - private IntPtr CreateObjectWithPrimaryKey(TableHandle table, string key, bool update, out bool isNew) + private IntPtr CreateObjectWithPrimaryKey(TableHandle table, long? key, bool isNullable, bool update, out bool isNew) { - var result = NativeMethods.create_object_unique(this, table, key, (IntPtr)key.Length, update, out isNew, out var ex); + var result = NativeMethods.create_object_unique(this, table, key ?? 0, key.HasValue, isNullable, update, out isNew, out var ex); ex.ThrowIfNecessary(); return result; } - private IntPtr CreateObjectWithPrimaryKey(TableHandle table, bool update, out bool isNew) + private IntPtr CreateObjectWithPrimaryKey(TableHandle table, string key, bool isNullable, bool update, out bool isNew) { - var result = NativeMethods.create_object_unique(this, table, update, out isNew, out var ex); + var value = key ?? string.Empty; + var result = NativeMethods.create_object_unique(this, table, value, (IntPtr)value.Length, key != null, isNullable, update, out isNew, out var ex); ex.ThrowIfNecessary(); return result; } diff --git a/Realm/Realm/Handles/TableHandle.cs b/Realm/Realm/Handles/TableHandle.cs index da5ea9648c..5f3c43e13e 100644 --- a/Realm/Realm/Handles/TableHandle.cs +++ b/Realm/Realm/Handles/TableHandle.cs @@ -108,12 +108,17 @@ public IntPtr CreateSortedResults(SharedRealmHandle sharedRealmHandle, SortDescr public IntPtr Find(SharedRealmHandle realmHandle, string id) { + NativeException nativeException; + IntPtr result; if (id == null) { - throw new ArgumentNullException(nameof(id)); + result = NativeMethods.object_for_null_primarykey(this, realmHandle, out nativeException); + } + else + { + result = NativeMethods.object_for_string_primarykey(this, realmHandle, id, (IntPtr)id.Length, out nativeException); } - var result = NativeMethods.object_for_string_primarykey(this, realmHandle, id, (IntPtr)id.Length, out var nativeException); nativeException.ThrowIfNecessary(); return result; } @@ -154,4 +159,4 @@ public IntPtr Find(SharedRealmHandle realmHandle, long? id) if (ret ==0 && !myHandle.IsNull() && myHandle != invalidHandle) mySafeHandle.SetHandle(myHandle); }// End CER -*/ \ No newline at end of file +*/ diff --git a/Realm/Realm/Helpers/Operator.cs b/Realm/Realm/Helpers/Operator.cs index 2469e398cb..3b02873f31 100644 --- a/Realm/Realm/Helpers/Operator.cs +++ b/Realm/Realm/Helpers/Operator.cs @@ -76,13 +76,13 @@ private static Func CreateConvert() Expression convertFrom = input; var typeOfT = typeof(T); var isTNullable = false; - if (ReflectionExtensions.IsClosedGeneric(typeOfT, typeof(Nullable<>), out var arguments)) + if (typeOfT.IsClosedGeneric(typeof(Nullable<>), out var arguments)) { typeOfT = arguments.Single(); isTNullable = true; } - if (ReflectionExtensions.IsClosedGeneric(typeOfT, typeof(RealmInteger<>), out arguments)) + if (typeOfT.IsClosedGeneric(typeof(RealmInteger<>), out arguments)) { var intermediateType = arguments.Single(); if (isTNullable) @@ -95,15 +95,15 @@ private static Func CreateConvert() var typeOfU = typeof(U); var isUNullable = false; - if (typeOfU.IsConstructedGenericType && typeOfU.GetGenericTypeDefinition() == typeof(Nullable<>)) + if (typeOfU.IsClosedGeneric(typeof(Nullable<>), out arguments)) { - typeOfU = typeOfU.GenericTypeArguments.Single(); + typeOfU = arguments.Single(); isUNullable = true; } - if (typeOfU.IsConstructedGenericType && typeOfU.GetGenericTypeDefinition() == typeof(RealmInteger<>)) + if (typeOfU.IsClosedGeneric(typeof(RealmInteger<>), out arguments)) { - var intermediateType = typeOfU.GenericTypeArguments.Single(); + var intermediateType = arguments.Single(); if (isUNullable) { intermediateType = typeof(Nullable<>).MakeGenericType(intermediateType); diff --git a/Tests/Tests.Shared/AddOrUpdateTests.cs b/Tests/Tests.Shared/AddOrUpdateTests.cs index 3c5e009ccc..b0317dffcf 100644 --- a/Tests/Tests.Shared/AddOrUpdateTests.cs +++ b/Tests/Tests.Shared/AddOrUpdateTests.cs @@ -663,33 +663,49 @@ public void AddOrUpdate_WhenChildHasNoPKAndGrandchildHasPK_ShouldAddChildUpdateG } [Test] - public void AddOrUpdate_WhenPKIsNull_ShouldUpdate() + public void AddOrUpdate_WhenPKIsIntAndNull_ShouldUpdate() { var first = new NullablePrimaryKeyObject { StringValue = "first" }; - _realm.Write(() => - { - _realm.Add(first, update: true); - }); + _realm.Write(() => _realm.Add(first, update: true)); var second = new NullablePrimaryKeyObject { StringValue = "second" }; - _realm.Write(() => - { - _realm.Add(second, update: true); - }); + _realm.Write(() => _realm.Add(second, update: true)); Assert.That(first.StringValue, Is.EqualTo("second")); Assert.That(second.StringValue, Is.EqualTo("second")); Assert.That(_realm.All().Count(), Is.EqualTo(1)); } + [Test] + public void AddOrUpdate_WhenPKIsStringAndNull_ShouldUpdate() + { + var first = new PrimaryKeyStringObject + { + Value = "first" + }; + + _realm.Write(() => _realm.Add(first, update: true)); + + var second = new PrimaryKeyStringObject + { + Value = "second" + }; + + _realm.Write(() => _realm.Add(second, update: true)); + + Assert.That(first.Value, Is.EqualTo("second")); + Assert.That(second.Value, Is.EqualTo("second")); + Assert.That(_realm.All().Count(), Is.EqualTo(1)); + } + [Test] public void Add_ShouldReturnPassedInObject() { @@ -786,6 +802,7 @@ public void AddOrUpdate_ShouldReturnManaged() [TestCase(typeof(PrimaryKeyNullableInt16Object))] [TestCase(typeof(PrimaryKeyNullableInt32Object))] [TestCase(typeof(PrimaryKeyNullableInt64Object))] + [TestCase(typeof(PrimaryKeyStringObject))] public void Add_WhenPKIsDefaultAndDuplicate_ShouldThrow(Type type) { Assert.That(() => @@ -798,18 +815,6 @@ public void Add_WhenPKIsDefaultAndDuplicate_ShouldThrow(Type type) }, Throws.TypeOf()); } - [Test] - public void Add_WhenPKIsStringAndNull_ShouldThrow() - { - Assert.That(() => - { - _realm.Write(() => - { - _realm.Add(new PrimaryKeyStringObject()); - }); - }, Throws.TypeOf()); - } - [Test] public void Add_WhenPKIsNotDefaultButDuplicate_ShouldThrow() { diff --git a/Tests/Tests.Shared/PrimaryKeyTests.cs b/Tests/Tests.Shared/PrimaryKeyTests.cs index 5696638fcc..40f23d7a93 100644 --- a/Tests/Tests.Shared/PrimaryKeyTests.cs +++ b/Tests/Tests.Shared/PrimaryKeyTests.cs @@ -49,6 +49,7 @@ public class PrimaryKeyTests : RealmInstanceTest [TestCase(typeof(PrimaryKeyNullableInt64Object), 42000042L, true)] [TestCase(typeof(PrimaryKeyNullableInt64Object), null, true)] [TestCase(typeof(PrimaryKeyStringObject), "key", false)] + [TestCase(typeof(PrimaryKeyStringObject), null, false)] public void FindByPrimaryKeyDynamicTests(Type type, object primaryKeyValue, bool isIntegerPK) { var obj = (RealmObject)Activator.CreateInstance(type); @@ -79,6 +80,7 @@ public void FindByPrimaryKeyDynamicTests(Type type, object primaryKeyValue, bool [TestCase(typeof(PrimaryKeyNullableInt64Object), 42000042L, true)] [TestCase(typeof(PrimaryKeyNullableInt64Object), null, true)] [TestCase(typeof(PrimaryKeyStringObject), "key", false)] + [TestCase(typeof(PrimaryKeyStringObject), null, false)] public void FailToFindByPrimaryKeyDynamicTests(Type type, object primaryKeyValue, bool isIntegerPK) { var foundObj = FindByPKDynamic(type, primaryKeyValue, isIntegerPK); @@ -101,19 +103,14 @@ public void FailToFindByPrimaryKeyDynamicTests(Type type, object primaryKeyValue [TestCase(typeof(PrimaryKeyNullableInt64Object), 42000042L)] [TestCase(typeof(PrimaryKeyNullableInt64Object), null)] [TestCase(typeof(PrimaryKeyStringObject), "key")] + [TestCase(typeof(PrimaryKeyStringObject), null)] public void CreateObject_WhenPKExists_ShouldFail(Type type, object primaryKeyValue) { - _realm.Write(() => - { - _realm.CreateObject(type.Name, primaryKeyValue); - }); + _realm.Write(() => _realm.CreateObject(type.Name, primaryKeyValue)); Assert.That(() => { - _realm.Write(() => - { - _realm.CreateObject(type.Name, primaryKeyValue); - }); + _realm.Write(() => _realm.CreateObject(type.Name, primaryKeyValue)); }, Throws.TypeOf()); } @@ -133,25 +130,20 @@ public void CreateObject_WhenPKExists_ShouldFail(Type type, object primaryKeyVal [TestCase(typeof(PrimaryKeyNullableInt64Object), 42000042L)] [TestCase(typeof(PrimaryKeyNullableInt64Object), null)] [TestCase(typeof(PrimaryKeyStringObject), "key")] + [TestCase(typeof(PrimaryKeyStringObject), null)] public void ManageObject_WhenPKExists_ShouldFail(Type type, object primaryKeyValue) { var pkProperty = type.GetProperties().Single(p => p.GetCustomAttribute() != null); var first = (RealmObject)Activator.CreateInstance(type); pkProperty.SetValue(first, primaryKeyValue); - _realm.Write(() => - { - _realm.Add(first); - }); + _realm.Write(() => _realm.Add(first)); Assert.That(() => { var second = (RealmObject)Activator.CreateInstance(type); pkProperty.SetValue(second, primaryKeyValue); - _realm.Write(() => - { - _realm.Add(second); - }); + _realm.Write(() => _realm.Add(second)); }, Throws.TypeOf()); } @@ -191,6 +183,7 @@ private RealmObject FindByPKDynamic(Type type, object primaryKeyValue, bool isIn [TestCase(typeof(PrimaryKeyNullableInt64Object), 42000042L, true)] [TestCase(typeof(PrimaryKeyNullableInt64Object), null, true)] [TestCase(typeof(PrimaryKeyStringObject), "key", false)] + [TestCase(typeof(PrimaryKeyStringObject), null, false)] public void FindByPrimaryKeyGenericTests(Type type, object primaryKeyValue, bool isIntegerPK) { var obj = (RealmObject)Activator.CreateInstance(type); @@ -221,6 +214,7 @@ public void FindByPrimaryKeyGenericTests(Type type, object primaryKeyValue, bool [TestCase(typeof(PrimaryKeyNullableInt64Object), 42000042L, true)] [TestCase(typeof(PrimaryKeyNullableInt64Object), null, true)] [TestCase(typeof(PrimaryKeyStringObject), "key", false)] + [TestCase(typeof(PrimaryKeyStringObject), null, false)] public void FailToFindByPrimaryKeyGenericTests(Type type, object primaryKeyValue, bool isIntegerPK) { var foundObj = FindByPKGeneric(type, primaryKeyValue, isIntegerPK); @@ -307,18 +301,6 @@ public void PrimaryKeyStringObjectIsUnique() }, Throws.TypeOf()); } - [Test] - public void NullPrimaryKeyStringObjectThrows() - { - Assert.That(() => - { - _realm.Write(() => - { - _realm.Add(new PrimaryKeyStringObject { StringProperty = null }); - }); - }, Throws.TypeOf()); - } - [Test] public void NullAndNotNullIntPKsWorkTogether() { @@ -329,10 +311,18 @@ public void NullAndNotNullIntPKsWorkTogether() }); Assert.That(_realm.All().Count, Is.EqualTo(2)); + + _realm.Write(() => + { + _realm.Add(new PrimaryKeyStringObject { StringProperty = "123" }); + _realm.Add(new PrimaryKeyStringObject { StringProperty = null }); + }); + + Assert.That(_realm.All().Count, Is.EqualTo(2)); } [Test] - public void PrimaryKeyFailsIfClassNotinRealm() + public void PrimaryKeyFailsIfClassNotInRealm() { var conf = RealmConfiguration.DefaultConfiguration.ConfigWithPath(Path.GetTempFileName()); conf.ObjectClasses = new[] { typeof(Person) }; diff --git a/Tests/Tests.Shared/TestObjects.cs b/Tests/Tests.Shared/TestObjects.cs index 9f82d23de8..6d6d33c535 100644 --- a/Tests/Tests.Shared/TestObjects.cs +++ b/Tests/Tests.Shared/TestObjects.cs @@ -205,6 +205,8 @@ public class PrimaryKeyStringObject : RealmObject { [PrimaryKey] public string StringProperty { get; set; } + + public string Value { get; set; } } [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass")] diff --git a/wrappers/src/shared_realm_cs.cpp b/wrappers/src/shared_realm_cs.cpp index a5f6178475..035be018a0 100644 --- a/wrappers/src/shared_realm_cs.cpp +++ b/wrappers/src/shared_realm_cs.cpp @@ -332,22 +332,26 @@ REALM_EXPORT Object* shared_realm_create_object(SharedRealm* realm, Table* table }); } -REALM_EXPORT Object* shared_realm_create_object_int_unique(const SharedRealm& realm, Table& table, int64_t key, bool is_nullable, bool try_update, bool& is_new, NativeException::Marshallable& ex) +REALM_EXPORT Object* shared_realm_create_object_int_unique(const SharedRealm& realm, Table& table, int64_t key, bool has_value, bool is_nullable, bool try_update, bool& is_new, NativeException::Marshallable& ex) { return handle_errors(ex, [&]() { if (is_nullable) { - return create_object_unique(realm, table, util::some(key), try_update, is_new); + return create_object_unique(realm, table, has_value ? util::some(key) : null(), try_update, is_new); } else { return create_object_unique(realm, table, key, try_update, is_new); } }); } -REALM_EXPORT Object* shared_realm_create_object_string_unique(const SharedRealm& realm, Table& table, uint16_t* key_buf, size_t key_len, bool try_update, bool& is_new, NativeException::Marshallable& ex) +REALM_EXPORT Object* shared_realm_create_object_string_unique(const SharedRealm& realm, Table& table, uint16_t* key_buf, size_t key_len, bool has_value, bool is_nullable, bool try_update, bool& is_new, NativeException::Marshallable& ex) { return handle_errors(ex, [&]() { Utf16StringAccessor key(key_buf, key_len); - return create_object_unique(realm, table, StringData(key), try_update, is_new); + if (is_nullable) { + return create_object_unique(realm, table, has_value ? StringData(key) : StringData(), try_update, is_new); + } else { + return create_object_unique(realm, table, StringData(key), try_update, is_new); + } }); } From 8d77441621969c56bfe22750e0d9da3430b9d43d Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Fri, 29 Sep 2017 15:39:35 +0200 Subject: [PATCH 2/4] Simplify the string PK invocation a little --- Realm/Realm/Handles/SharedRealmHandle.cs | 10 ++++------ wrappers/src/shared_realm_cs.cpp | 19 ++++++------------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/Realm/Realm/Handles/SharedRealmHandle.cs b/Realm/Realm/Handles/SharedRealmHandle.cs index 9870f034e2..22952118bf 100644 --- a/Realm/Realm/Handles/SharedRealmHandle.cs +++ b/Realm/Realm/Handles/SharedRealmHandle.cs @@ -101,8 +101,7 @@ public static extern IntPtr create_object_unique(SharedRealmHandle sharedRealm, [DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_create_object_string_unique", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr create_object_unique(SharedRealmHandle sharedRealm, TableHandle table, - [MarshalAs(UnmanagedType.LPWStr)] string value, IntPtr valueLen, [MarshalAs(UnmanagedType.I1)] bool has_value, - [MarshalAs(UnmanagedType.I1)] bool is_nullable, + [MarshalAs(UnmanagedType.LPWStr)] string value, IntPtr valueLen, [MarshalAs(UnmanagedType.I1)] bool update, [MarshalAs(UnmanagedType.I1)] out bool is_new, out NativeException ex); @@ -269,7 +268,7 @@ public IntPtr CreateObjectWithPrimaryKey(Property pkProperty, object primaryKey, { case PropertyType.String: var stringKey = (string)primaryKey; - return CreateObjectWithPrimaryKey(table, stringKey, pkProperty.Type.IsNullable(), update, out isNew); + return CreateObjectWithPrimaryKey(table, stringKey, update, out isNew); case PropertyType.Int: var longKey = primaryKey == null ? (long?)null : Convert.ToInt64(primaryKey); return CreateObjectWithPrimaryKey(table, longKey, pkProperty.Type.IsNullable(), update, out isNew); @@ -285,10 +284,9 @@ private IntPtr CreateObjectWithPrimaryKey(TableHandle table, long? key, bool isN return result; } - private IntPtr CreateObjectWithPrimaryKey(TableHandle table, string key, bool isNullable, bool update, out bool isNew) + private IntPtr CreateObjectWithPrimaryKey(TableHandle table, string key, bool update, out bool isNew) { - var value = key ?? string.Empty; - var result = NativeMethods.create_object_unique(this, table, value, (IntPtr)value.Length, key != null, isNullable, update, out isNew, out var ex); + var result = NativeMethods.create_object_unique(this, table, key, (IntPtr)(key?.Length ?? 0), update, out isNew, out var ex); ex.ThrowIfNecessary(); return result; } diff --git a/wrappers/src/shared_realm_cs.cpp b/wrappers/src/shared_realm_cs.cpp index 035be018a0..26611d2e6b 100644 --- a/wrappers/src/shared_realm_cs.cpp +++ b/wrappers/src/shared_realm_cs.cpp @@ -343,25 +343,18 @@ REALM_EXPORT Object* shared_realm_create_object_int_unique(const SharedRealm& re }); } -REALM_EXPORT Object* shared_realm_create_object_string_unique(const SharedRealm& realm, Table& table, uint16_t* key_buf, size_t key_len, bool has_value, bool is_nullable, bool try_update, bool& is_new, NativeException::Marshallable& ex) +REALM_EXPORT Object* shared_realm_create_object_string_unique(const SharedRealm& realm, Table& table, uint16_t* key_buf, size_t key_len, bool try_update, bool& is_new, NativeException::Marshallable& ex) { return handle_errors(ex, [&]() { - Utf16StringAccessor key(key_buf, key_len); - if (is_nullable) { - return create_object_unique(realm, table, has_value ? StringData(key) : StringData(), try_update, is_new); - } else { - return create_object_unique(realm, table, StringData(key), try_update, is_new); + if (key_buf == nullptr) { + return create_object_unique(realm, table, StringData(), try_update, is_new); } + + Utf16StringAccessor key(key_buf, key_len); + return create_object_unique(realm, table, StringData(key), try_update, is_new); }); } -REALM_EXPORT Object* shared_realm_create_object_null_unique(const SharedRealm& realm, Table& table, bool try_update, bool& is_new, NativeException::Marshallable& ex) -{ - return handle_errors(ex, [&]() { - return create_object_unique>(realm, table, null(), try_update, is_new); - }); -} - REALM_EXPORT void shared_realm_get_schema(const SharedRealm& realm, void* managed_callback, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { From 7bb42fb9487cacf6c38f3559fa77482a0c5cc4c6 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Fri, 29 Sep 2017 18:06:44 +0200 Subject: [PATCH 3/4] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac4a462e21..637be0724f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ that allows you to open the old Realm file in a dynamic mode and migrate any req account data associated with that user. ([#1573](https://github.com/realm/realm-dotnet/pull/1573)) - Introduced a new method - `User.LogOutAsync` to replace the now-deprecated synchronous call. ([#1574](https://github.com/realm/realm-dotnet/pull/1574)) - Exposed `BacklinksCount` property on `RealmObject` that returns the number of objects that refer to the current object via a to-one or a to-many relationship. ([#1578](https://github.com/realm/realm-dotnet/pull/1578)) +- String primary keys now support `null` as a value. ([#1579](https://github.com/realm/realm-dotnet/pull/1579)) ### Bug fixes - `Realm.GetInstance` will now advance the Realm to the latest version, so you no longer have to call `Refresh` manually after that. ([#1523](https://github.com/realm/realm-dotnet/pull/1523)) From cbf4862ce8a0ea4a70d9133fa36f27c320c89239 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Sat, 30 Sep 2017 15:30:05 +0200 Subject: [PATCH 4/4] a few extra tests --- Tests/Tests.Shared/PrimaryKeyTests.cs | 24 ++++++++++++++++++++++++ Tests/Tests.Shared/TestObjects.cs | 10 ++++++++++ 2 files changed, 34 insertions(+) diff --git a/Tests/Tests.Shared/PrimaryKeyTests.cs b/Tests/Tests.Shared/PrimaryKeyTests.cs index 40f23d7a93..4d64eecbd0 100644 --- a/Tests/Tests.Shared/PrimaryKeyTests.cs +++ b/Tests/Tests.Shared/PrimaryKeyTests.cs @@ -50,6 +50,9 @@ public class PrimaryKeyTests : RealmInstanceTest [TestCase(typeof(PrimaryKeyNullableInt64Object), null, true)] [TestCase(typeof(PrimaryKeyStringObject), "key", false)] [TestCase(typeof(PrimaryKeyStringObject), null, false)] + [TestCase(typeof(PrimaryKeyStringObject), "", false)] + [TestCase(typeof(RequiredPrimaryKeyStringObject), "key", false)] + [TestCase(typeof(RequiredPrimaryKeyStringObject), "", false)] public void FindByPrimaryKeyDynamicTests(Type type, object primaryKeyValue, bool isIntegerPK) { var obj = (RealmObject)Activator.CreateInstance(type); @@ -81,6 +84,9 @@ public void FindByPrimaryKeyDynamicTests(Type type, object primaryKeyValue, bool [TestCase(typeof(PrimaryKeyNullableInt64Object), null, true)] [TestCase(typeof(PrimaryKeyStringObject), "key", false)] [TestCase(typeof(PrimaryKeyStringObject), null, false)] + [TestCase(typeof(PrimaryKeyStringObject), "", false)] + [TestCase(typeof(RequiredPrimaryKeyStringObject), "key", false)] + [TestCase(typeof(RequiredPrimaryKeyStringObject), "", false)] public void FailToFindByPrimaryKeyDynamicTests(Type type, object primaryKeyValue, bool isIntegerPK) { var foundObj = FindByPKDynamic(type, primaryKeyValue, isIntegerPK); @@ -104,6 +110,9 @@ public void FailToFindByPrimaryKeyDynamicTests(Type type, object primaryKeyValue [TestCase(typeof(PrimaryKeyNullableInt64Object), null)] [TestCase(typeof(PrimaryKeyStringObject), "key")] [TestCase(typeof(PrimaryKeyStringObject), null)] + [TestCase(typeof(PrimaryKeyStringObject), "")] + [TestCase(typeof(RequiredPrimaryKeyStringObject), "key")] + [TestCase(typeof(RequiredPrimaryKeyStringObject), "")] public void CreateObject_WhenPKExists_ShouldFail(Type type, object primaryKeyValue) { _realm.Write(() => _realm.CreateObject(type.Name, primaryKeyValue)); @@ -131,6 +140,9 @@ public void CreateObject_WhenPKExists_ShouldFail(Type type, object primaryKeyVal [TestCase(typeof(PrimaryKeyNullableInt64Object), null)] [TestCase(typeof(PrimaryKeyStringObject), "key")] [TestCase(typeof(PrimaryKeyStringObject), null)] + [TestCase(typeof(PrimaryKeyStringObject), "")] + [TestCase(typeof(RequiredPrimaryKeyStringObject), "key")] + [TestCase(typeof(RequiredPrimaryKeyStringObject), "")] public void ManageObject_WhenPKExists_ShouldFail(Type type, object primaryKeyValue) { var pkProperty = type.GetProperties().Single(p => p.GetCustomAttribute() != null); @@ -184,6 +196,9 @@ private RealmObject FindByPKDynamic(Type type, object primaryKeyValue, bool isIn [TestCase(typeof(PrimaryKeyNullableInt64Object), null, true)] [TestCase(typeof(PrimaryKeyStringObject), "key", false)] [TestCase(typeof(PrimaryKeyStringObject), null, false)] + [TestCase(typeof(PrimaryKeyStringObject), "", false)] + [TestCase(typeof(RequiredPrimaryKeyStringObject), "key", false)] + [TestCase(typeof(RequiredPrimaryKeyStringObject), "", false)] public void FindByPrimaryKeyGenericTests(Type type, object primaryKeyValue, bool isIntegerPK) { var obj = (RealmObject)Activator.CreateInstance(type); @@ -215,6 +230,9 @@ public void FindByPrimaryKeyGenericTests(Type type, object primaryKeyValue, bool [TestCase(typeof(PrimaryKeyNullableInt64Object), null, true)] [TestCase(typeof(PrimaryKeyStringObject), "key", false)] [TestCase(typeof(PrimaryKeyStringObject), null, false)] + [TestCase(typeof(PrimaryKeyStringObject), "", false)] + [TestCase(typeof(RequiredPrimaryKeyStringObject), "key", false)] + [TestCase(typeof(RequiredPrimaryKeyStringObject), "", false)] public void FailToFindByPrimaryKeyGenericTests(Type type, object primaryKeyValue, bool isIntegerPK) { var foundObj = FindByPKGeneric(type, primaryKeyValue, isIntegerPK); @@ -338,5 +356,11 @@ public void PrimaryKeyFailsIfClassNotInRealm() Realm.DeleteRealm(conf); } } + + [Test] + public void StringPrimaryKey_WhenRequiredDoesntAllowNull() + { + Assert.That(() => _realm.Write(() => _realm.Add(new RequiredPrimaryKeyStringObject())), Throws.TypeOf()); + } } } \ No newline at end of file diff --git a/Tests/Tests.Shared/TestObjects.cs b/Tests/Tests.Shared/TestObjects.cs index 6d6d33c535..b3621538a9 100644 --- a/Tests/Tests.Shared/TestObjects.cs +++ b/Tests/Tests.Shared/TestObjects.cs @@ -209,6 +209,16 @@ public class PrimaryKeyStringObject : RealmObject public string Value { get; set; } } + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass")] + public class RequiredPrimaryKeyStringObject : RealmObject + { + [PrimaryKey] + [Required] + public string StringProperty { get; set; } + + public string Value { get; set; } + } + [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass")] public class PrimaryKeyNullableCharObject : RealmObject {