From f532c84148968eb6b56d54be2541e7fd2addf87e Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 18 Jun 2024 15:56:21 +0200 Subject: [PATCH] Improved handling of lists and objects --- Realm/Realm/Dynamic/DynamicObjectApi.cs | 10 +++- Realm/Realm/Handles/ObjectHandle.cs | 40 +++++++++++++- .../Database/DynamicAccessTests.cs | 33 +++++++++-- wrappers/src/object_cs.cpp | 55 ++++++++++++++++--- 4 files changed, 121 insertions(+), 17 deletions(-) diff --git a/Realm/Realm/Dynamic/DynamicObjectApi.cs b/Realm/Realm/Dynamic/DynamicObjectApi.cs index f98026d313..6a0959b7d1 100644 --- a/Realm/Realm/Dynamic/DynamicObjectApi.cs +++ b/Realm/Realm/Dynamic/DynamicObjectApi.cs @@ -53,6 +53,12 @@ internal DynamicObjectApi(ManagedAccessor managedAccessor) /// object, casting to is always valid. /// public T Get(string propertyName) + { + return Get(propertyName).As(); + } + + //TODO Add docs + public RealmValue Get(string propertyName) { if (GetProperty(propertyName) is Property property) { @@ -76,11 +82,11 @@ public T Get(string propertyName) $"{_managedAccessor.ObjectSchema.Name}.{propertyName} is {property.GetDotnetTypeName()} and can't be accessed using {nameof(Dynamic)}.{nameof(Get)}. Use {collectionMethodName} instead."); } - return _managedAccessor.GetValue(propertyName).As(); + return _managedAccessor.GetValue(propertyName); } else { - return _managedAccessor.GetValue(propertyName).As(); + return _managedAccessor.GetValue(propertyName); } } diff --git a/Realm/Realm/Handles/ObjectHandle.cs b/Realm/Realm/Handles/ObjectHandle.cs index 5028e150dc..7139b97dab 100644 --- a/Realm/Realm/Handles/ObjectHandle.cs +++ b/Realm/Realm/Handles/ObjectHandle.cs @@ -55,6 +55,9 @@ private static class NativeMethods [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_set_collection_value", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr set_collection_value(ObjectHandle handle, IntPtr propertyIndex, RealmValueType type, out NativeException ex); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_set_collection_additional_property", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr set_collection_additional_property(ObjectHandle handle, StringValue propertyName, RealmValueType type, out NativeException ex); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_create_embedded", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr create_embedded_link(ObjectHandle handle, IntPtr propertyIndex, out NativeException ex); @@ -269,17 +272,50 @@ public void SetValue(string propertyName, Metadata metadata, in RealmValue value } else { + //TODO Eventually merge with the previous case + //TODO Probably this can be improved using Arena arena = new(); var propertyNameNative = StringValue.AllocateFrom(propertyName, arena); + if (value.Type == RealmValueType.Object) + { + switch (value.AsIRealmObject()) + { + case IRealmObject realmObj when !realmObj.IsManaged: + realm.Add(realmObj); + break; + case IEmbeddedObject: + throw new NotSupportedException($"A RealmValue cannot contain an embedded object. Attempted to set {value} to {metadata.Schema.Name}.{propertyName}"); + case IAsymmetricObject: + throw new NotSupportedException($"Asymmetric objects cannot be linked to and cannot be contained in a RealmValue. Attempted to set {value} to {metadata.Schema.Name}.{propertyName}"); + } + } + else if (value.Type.IsCollection()) + { + var collectionPtr = NativeMethods.set_collection_additional_property(this, propertyNameNative, value.Type, out var collNativeException); + collNativeException.ThrowIfNecessary(); + + switch (value.Type) + { + case RealmValueType.List: + CollectionHelpers.PopulateCollection(realm, new ListHandle(Root!, collectionPtr), value); + break; + case RealmValueType.Dictionary: + CollectionHelpers.PopulateCollection(realm, new DictionaryHandle(Root!, collectionPtr), value); + break; + default: + break; + } + + return; + } + var (primitive, handles) = value.ToNative(); NativeMethods.set_additional_property(this, propertyNameNative, primitive, out var nativeException); handles?.Dispose(); nativeException.ThrowIfNecessary(); } - - } public long AddInt64(IntPtr propertyIndex, long value) diff --git a/Tests/Realm.Tests/Database/DynamicAccessTests.cs b/Tests/Realm.Tests/Database/DynamicAccessTests.cs index 07e6d665bf..591a801d1a 100644 --- a/Tests/Realm.Tests/Database/DynamicAccessTests.cs +++ b/Tests/Realm.Tests/Database/DynamicAccessTests.cs @@ -17,6 +17,7 @@ //////////////////////////////////////////////////////////////////////////// using System; +using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using Microsoft.CSharp.RuntimeBinder; @@ -983,21 +984,41 @@ public void GetDictionary_WhenCastToWrongValue_Throws() [Test] public void FlexibleSchema_BaseTest() { - var testObj = _realm.Write(() => + var person = _realm.Write(() => { - return _realm.Add(new IntPropertyObject()); + return _realm.Add(new Person()); }); + var testObj = new Person { FirstName = "Luigi" }; + var testList = new List { 1, "test", true }; + _realm.Write(() => { - testObj.DynamicApi.Set("prop1", "testval"); - testObj.DynamicApi.Set("prop2", 10); + person.DynamicApi.Set("propString", "testval"); + person.DynamicApi.Set("propInt", 10); + person.DynamicApi.Set("propObj", testObj); + person.DynamicApi.Set("propList", testList); + }); - Assert.That(testObj.DynamicApi.Get("prop1"), Is.EqualTo("testval")); - Assert.That(testObj.DynamicApi.Get("prop2"), Is.EqualTo(10)); + Assert.That(person.DynamicApi.Get("propString"), Is.EqualTo("testval")); + Assert.That(person.DynamicApi.Get("propInt"), Is.EqualTo(10)); + Assert.That(person.DynamicApi.Get("propObj"), Is.EqualTo(testObj)); + Assert.That(person.DynamicApi.Get>("propList"), Is.EqualTo(testList)); } + /** To test + * - add and retrieve objects + * - add and retrieve collections + * - set on same property changes + * - set null value + * - retrieve property that does not exist raises exception + * - retrieve additional properties + * - erase a property + * + * + */ + #endregion [Test] diff --git a/wrappers/src/object_cs.cpp b/wrappers/src/object_cs.cpp index b04f996215..554a8af929 100644 --- a/wrappers/src/object_cs.cpp +++ b/wrappers/src/object_cs.cpp @@ -105,7 +105,7 @@ extern "C" { } auto val = object.get_obj().get_any(prop.column_key); - if (val.is_null()) + if (val.is_null()) //TODO I think we don't need this check { *value = to_capi(std::move(val)); return; @@ -128,14 +128,26 @@ extern "C" { }); } - REALM_EXPORT void object_get_additional_property(const Object& object, realm_string_t proeprty_name, realm_value_t* value, NativeException::Marshallable& ex) + REALM_EXPORT void object_get_additional_property(const Object& object, realm_string_t property_name, realm_value_t* value, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { verify_can_get(object); - auto val = object.get_obj().get_any(capi_to_std(proeprty_name)); + auto val = object.get_obj().get_additional_prop(capi_to_std(property_name)); - *value = to_capi(std::move(val)); + Path path = { PathElement(capi_to_std(property_name)) }; + + switch (val.get_type()) { + case type_TypedLink: + *value = to_capi(val.get(), object.realm()); + break; + case type_List: + *value = to_capi(new List(object.realm(), object.get_obj().get_list_ptr(path))); + break; + default: + *value = to_capi(std::move(val)); + break; + } }); } @@ -190,9 +202,7 @@ extern "C" { handle_errors(ex, [&]() { verify_can_set(object); - //Set_additional_prop is private at the moment - - object.get_obj().set_any(capi_to_std(property_name), from_capi(value)); + object.get_obj().set_additional_prop(capi_to_std(property_name), from_capi(value)); }); } @@ -225,6 +235,37 @@ extern "C" { }); } + REALM_EXPORT void* object_set_collection_additional_property(Object& object, + realm_string_t property_name, realm_value_type type, NativeException::Marshallable& ex) + { + return handle_errors(ex, [&]()-> void* { + verify_can_set(object); + + auto prop = capi_to_std(property_name); + + Path path = { PathElement(prop) }; + + switch (type) + { + case realm::binding::realm_value_type::RLM_TYPE_LIST: + { + + object.get_obj().set_collection(prop, CollectionType::List); + //TODO We probably need to ask for methods that do not require to build a path + auto innerList = new List(object.realm(), object.get_obj().get_list_ptr(path)); + innerList->remove_all(); + return innerList; + } + case realm::binding::realm_value_type::RLM_TYPE_DICTIONARY: + { + REALM_TERMINATE("Invalid collection type"); + } + default: + REALM_TERMINATE("Invalid collection type"); + } + }); + } + REALM_EXPORT Results* object_get_backlinks(Object& object, size_t property_ndx, NativeException::Marshallable& ex) { return handle_errors(ex, [&] {