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, [&] {