diff --git a/CHANGELOG.md b/CHANGELOG.md index 582528c71d..18d2844e8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,24 @@ var obj = new MyObject(); obj.Denominations.Add("quarter", 0.25d); ``` +* Add support for `RealmValue` data type. This new type can represent any valid Realm data type, including objects. Collections (lists, sets and dictionaries) of `RealmValue` are also supported, but 'RealmValue' cannot contain collections. Please note that a property of type `RealmValue` cannot be nullable, but can contain null, represented by the value `RealmValue.Null`. (PR [#2252](https://github.com/realm/realm-dotnet/pull/2252)) + + ```csharp + public class MyObject : RealmObject + { + public RealmValue MyValue { get; set; } + } + + var obj = new MyObject(); + obj.MyValue = RealmValue.Null; + obj.MyValue = 1; + obj.MyValue = "abc"; + + if (obj.Type == RealmValueType.String) + { + var myString = obj.MyValue.AsString(); + } + ``` * Added support for value substitution in string based queries. This enables expressions following [this syntax](https://github.com/realm/realm-js/blob/master/docs/tutorials/query-language.md): `realm.All().Filter("field1 = $0 && field2 = $1", 123, "some-string-value")`. (Issue [#1822](https://github.com/realm/realm-dotnet/issues/1822)) * Reduced the size of the native binaries by ~5%. (PR [#2239](https://github.com/realm/realm-dotnet/pull/2239)) * Added a new class - `Logger`, which allows you to override the default logger implementation (previously writing to `stdout` or `stderr`) with a custom one by setting diff --git a/Realm/Realm.Weaver/Extensions/PropertyDefinitionExtensions.cs b/Realm/Realm.Weaver/Extensions/PropertyDefinitionExtensions.cs index 2e98f68bea..6768c68cf3 100644 --- a/Realm/Realm.Weaver/Extensions/PropertyDefinitionExtensions.cs +++ b/Realm/Realm.Weaver/Extensions/PropertyDefinitionExtensions.cs @@ -154,6 +154,11 @@ internal static bool IsString(this PropertyDefinition property) return property.PropertyType.FullName == StringTypeName; } + internal static bool IsRealmValue(this PropertyDefinition property) + { + return property.PropertyType.FullName == RealmValueTypeName; + } + internal static bool IsDescendantOf(this PropertyDefinition property, TypeReference other) { return property.PropertyType.Resolve().BaseType.IsSameAs(other); diff --git a/Realm/Realm.Weaver/RealmWeaver.cs b/Realm/Realm.Weaver/RealmWeaver.cs index cfb319b8de..43b3012025 100644 --- a/Realm/Realm.Weaver/RealmWeaver.cs +++ b/Realm/Realm.Weaver/RealmWeaver.cs @@ -49,6 +49,7 @@ internal partial class Weaver internal const string ObjectIdTypeName = "MongoDB.Bson.ObjectId"; internal const string DateTimeOffsetTypeName = "System.DateTimeOffset"; internal const string GuidTypeName = "System.Guid"; + internal const string RealmValueTypeName = "Realms.RealmValue"; internal const string NullableCharTypeName = "System.Nullable`1"; internal const string NullableByteTypeName = "System.Nullable`1"; internal const string NullableInt16TypeName = "System.Nullable`1"; @@ -63,7 +64,7 @@ internal partial class Weaver internal const string NullableObjectIdTypeName = "System.Nullable`1"; internal const string NullableGuidTypeName = "System.Nullable`1"; - private static readonly HashSet _primitiveValueTypes = new HashSet + private static readonly HashSet _realmValueTypes = new HashSet { CharTypeName, SingleTypeName, @@ -72,8 +73,8 @@ internal partial class Weaver DecimalTypeName, Decimal128TypeName, ObjectIdTypeName, - GuidTypeName, DateTimeOffsetTypeName, + GuidTypeName, NullableCharTypeName, NullableSingleTypeName, NullableDoubleTypeName, @@ -100,7 +101,8 @@ internal partial class Weaver $"System.Nullable`1>", $"System.Nullable`1>", ByteArrayTypeName, - StringTypeName + StringTypeName, + RealmValueTypeName, }; private static readonly IEnumerable _primaryKeyTypes = new[] @@ -372,7 +374,7 @@ private WeavePropertyResult WeaveProperty(PropertyDefinition prop, TypeDefinitio return WeavePropertyResult.Error($"{type.Name}.{prop.Name} has [Backlink] applied, but is not IQueryable."); } - if (_primitiveValueTypes.Contains(prop.PropertyType.FullName)) + if (_realmValueTypes.Contains(prop.PropertyType.FullName)) { if (prop.SetMethod == null) { @@ -389,7 +391,7 @@ private WeavePropertyResult WeaveProperty(PropertyDefinition prop, TypeDefinitio var genericArguments = ((GenericInstanceType)prop.PropertyType).GenericArguments; var elementType = genericArguments.Last(); if (!elementType.Resolve().IsValidRealmObjectBaseInheritor(_references) && - !_primitiveValueTypes.Contains(elementType.FullName)) + !_realmValueTypes.Contains(elementType.FullName)) { return WeavePropertyResult.Error($"{type.Name}.{prop.Name} is an {collectionType} but its generic type is {elementType.Name} which is not supported by Realm."); } @@ -540,13 +542,16 @@ private void ReplaceGetter(PropertyDefinition prop, string columnName, MethodRef convertType = _references.RealmObjectBase; } - var convertMethod = new MethodReference("op_Explicit", convertType, _references.RealmValue) + if (!prop.IsRealmValue()) { - Parameters = { new ParameterDefinition(_references.RealmValue) }, - HasThis = false - }; + var convertMethod = new MethodReference("op_Explicit", convertType, _references.RealmValue) + { + Parameters = { new ParameterDefinition(_references.RealmValue) }, + HasThis = false + }; - il.InsertBefore(start, il.Create(OpCodes.Call, convertMethod)); + il.InsertBefore(start, il.Create(OpCodes.Call, convertMethod)); + } // This only happens when we have a relationship - explicitly cast. if (convertType != prop.PropertyType) @@ -718,19 +723,22 @@ private void ReplaceSetter(PropertyDefinition prop, FieldReference backingField, il.Append(il.Create(OpCodes.Ldstr, columnName)); il.Append(il.Create(OpCodes.Ldarg_1)); - var convertType = prop.PropertyType; - if (prop.ContainsRealmObject(_references) || prop.ContainsEmbeddedObject(_references)) + if (!prop.IsRealmValue()) { - convertType = _references.RealmObjectBase; - } + var convertType = prop.PropertyType; + if (prop.ContainsRealmObject(_references) || prop.ContainsEmbeddedObject(_references)) + { + convertType = _references.RealmObjectBase; + } - var convertMethod = new MethodReference("op_Implicit", _references.RealmValue, _references.RealmValue) - { - Parameters = { new ParameterDefinition(convertType) }, - HasThis = false - }; + var convertMethod = new MethodReference("op_Implicit", _references.RealmValue, _references.RealmValue) + { + Parameters = { new ParameterDefinition(convertType) }, + HasThis = false + }; - il.Append(il.Create(OpCodes.Call, convertMethod)); + il.Append(il.Create(OpCodes.Call, convertMethod)); + } il.Append(il.Create(OpCodes.Call, setValueReference)); il.Append(il.Create(OpCodes.Ret)); @@ -762,7 +770,7 @@ private TypeDefinition WeaveRealmObjectHelper(TypeDefinition realmObjectType, Me *foreach* non-list woven property in castInstance's schema *if* castInstace.field is a RealmObject descendant castInstance.Realm.Add(castInstance.field, update); - castInstance.Field = castInstance.field; + castInstance.Property = castInstance.Field; *else if* property is PK *do nothing* *else if* property is [Required] or nullable @@ -845,6 +853,7 @@ private TypeDefinition WeaveRealmObjectHelper(TypeDefinition realmObjectType, Me property.PropertyType.IsRealmInteger(out _, out _) || // structs are not implicitly falsy/truthy so the IL is significantly different; we can optimize this case in the future property.IsDecimal() || property.IsDecimal128() || + property.IsRealmValue() || property.IsObjectId() || property.IsGuid(); diff --git a/Realm/Realm/DatabaseTypes/RealmCollectionBase.cs b/Realm/Realm/DatabaseTypes/RealmCollectionBase.cs index a4b33db080..ceb00e4df7 100644 --- a/Realm/Realm/DatabaseTypes/RealmCollectionBase.cs +++ b/Realm/Realm/DatabaseTypes/RealmCollectionBase.cs @@ -39,7 +39,6 @@ public abstract class RealmCollectionBase IThreadConfined, IMetadataObject { - protected static readonly RealmValueType _argumentType = typeof(T).ToPropertyType(out _).ToRealmValueType(); protected static readonly bool _isEmbedded = typeof(T).IsEmbeddedObject() || (typeof(T).IsClosedGeneric(typeof(KeyValuePair<,>), out var typeArgs) && typeArgs.Last().IsEmbeddedObject()); private readonly List> _callbacks = new List>(); diff --git a/Realm/Realm/DatabaseTypes/RealmDictionary.cs b/Realm/Realm/DatabaseTypes/RealmDictionary.cs index 3f7c5ec331..1b986c61e6 100644 --- a/Realm/Realm/DatabaseTypes/RealmDictionary.cs +++ b/Realm/Realm/DatabaseTypes/RealmDictionary.cs @@ -145,7 +145,7 @@ public bool Remove(KeyValuePair item) public bool TryGetValue(string key, out TValue value) { - if (key != null && _dictionaryHandle.TryGet(key, Metadata, Realm, out var realmValue)) + if (key != null && _dictionaryHandle.TryGet(key, Realm, out var realmValue)) { value = realmValue.As(); return true; @@ -224,7 +224,7 @@ private static string ValidateKey(string key) internal override CollectionHandleBase GetOrCreateHandle() => _dictionaryHandle; - protected override KeyValuePair GetValueAtIndex(int index) => _dictionaryHandle.GetValueAtIndex(index, Metadata, Realm); + protected override KeyValuePair GetValueAtIndex(int index) => _dictionaryHandle.GetValueAtIndex(index, Realm); void INotifiable.NotifyCallbacks(DictionaryHandle.DictionaryChangeSet? changes, NativeException? exception) { diff --git a/Realm/Realm/DatabaseTypes/RealmList.cs b/Realm/Realm/DatabaseTypes/RealmList.cs index 89f9fc8a5c..edd9b34791 100644 --- a/Realm/Realm/DatabaseTypes/RealmList.cs +++ b/Realm/Realm/DatabaseTypes/RealmList.cs @@ -169,7 +169,7 @@ public void Move(int sourceIndex, int targetIndex) internal override RealmCollectionBase CreateCollection(Realm realm, CollectionHandleBase handle) => new RealmList(realm, (ListHandle)handle, Metadata); - protected override T GetValueAtIndex(int index) => _listHandle.GetValueAtIndex(index, Metadata, Realm).As(); + protected override T GetValueAtIndex(int index) => _listHandle.GetValueAtIndex(index, Realm).As(); DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression expression) => new MetaRealmList(expression, this); } diff --git a/Realm/Realm/DatabaseTypes/RealmSet.cs b/Realm/Realm/DatabaseTypes/RealmSet.cs index 254bf4a864..ce5c42da3d 100644 --- a/Realm/Realm/DatabaseTypes/RealmSet.cs +++ b/Realm/Realm/DatabaseTypes/RealmSet.cs @@ -50,7 +50,7 @@ public bool Add(T value) { var realmValue = Operator.Convert(value); - if (_argumentType == RealmValueType.Object) + if (realmValue.Type == RealmValueType.Object) { var robj = realmValue.AsRealmObject(); if (!robj.IsManaged) @@ -96,7 +96,7 @@ public override bool Contains(T value) internal override CollectionHandleBase GetOrCreateHandle() => _setHandle; - protected override T GetValueAtIndex(int index) => _setHandle.GetValueAtIndex(index, Metadata, Realm).As(); + protected override T GetValueAtIndex(int index) => _setHandle.GetValueAtIndex(index, Realm).As(); #region Set methods diff --git a/Realm/Realm/DatabaseTypes/RealmValue.cs b/Realm/Realm/DatabaseTypes/RealmValue.cs index b49cbda988..4e56a761cf 100644 --- a/Realm/Realm/DatabaseTypes/RealmValue.cs +++ b/Realm/Realm/DatabaseTypes/RealmValue.cs @@ -18,6 +18,7 @@ using System; using System.Buffers; +using System.Linq; using System.Runtime.InteropServices; using System.Text; using MongoDB.Bson; @@ -72,7 +73,7 @@ namespace Realms /// The of the current value in the database. public RealmValueType Type { get; } - internal RealmValue(PrimitiveValue primitive, ObjectHandle handle = default, IntPtr propertyIndex = default) : this() + internal RealmValue(PrimitiveValue primitive, Realm realm = null, ObjectHandle handle = default, IntPtr propertyIndex = default) : this() { Type = primitive.Type; _objectHandle = handle; @@ -87,32 +88,38 @@ internal RealmValue(PrimitiveValue primitive, ObjectHandle handle = default, Int _stringValue = primitive.AsString(); break; case RealmValueType.Object: - throw new NotSupportedException("Use RealmValue(RealmObject) instead."); + Argument.NotNull(realm, nameof(realm)); + _objectValue = primitive.AsObject(realm); + break; default: _primitiveValue = primitive; break; } } - internal RealmValue(byte[] data) : this() + private RealmValue(byte[] data) : this() { Type = data == null ? RealmValueType.Null : RealmValueType.Data; _dataValue = data; } - internal RealmValue(string value) : this() + private RealmValue(string value) : this() { Type = value == null ? RealmValueType.Null : RealmValueType.String; _stringValue = value; } - internal RealmValue(RealmObjectBase obj) : this() + private RealmValue(RealmObjectBase obj) : this() { Type = obj == null ? RealmValueType.Null : RealmValueType.Object; _objectValue = obj; } - internal static RealmValue Null() => new RealmValue(PrimitiveValue.Null()); + /// + /// Gets a RealmValue representing null. + /// + /// A new RealmValue instance of type . + public static RealmValue Null => new RealmValue(PrimitiveValue.Null()); private static RealmValue Bool(bool value) => new RealmValue(PrimitiveValue.Bool(value)); @@ -183,6 +190,11 @@ internal static RealmValue Create(T value, RealmValueType type) var handle = GCHandle.Alloc(_dataValue, GCHandleType.Pinned); return (PrimitiveValue.Data(handle.AddrOfPinnedObject(), _dataValue?.Length ?? 0), new HandlesToCleanup(handle)); case RealmValueType.Object: + if (!AsRealmObject().IsManaged) + { + throw new InvalidOperationException("Can't convert unmanaged object to native"); + } + return (PrimitiveValue.Object(_objectValue?.ObjectHandle), null); default: return (_primitiveValue, null); @@ -618,6 +630,11 @@ public T AsRealmObject() /// The underlying value converted to . public T As() { + if (typeof(T) == typeof(RealmValue)) + { + return Operator.Convert(this); + } + if (Type == RealmValueType.Int) { return Operator.Convert(AsInt64()); @@ -655,6 +672,32 @@ public object AsAny() }; } + /// + /// Gets the name of the type of the object contained in . + /// If it does not contain an object, it will return null. + /// + /// + /// The name of the type stored in if an object, null otherwise. + /// + public string ObjectType + { + get + { + if (Type != RealmValueType.Object) + { + return null; + } + + var obj = AsRealmObject(); + if (obj.IsManaged) + { + return obj.ObjectSchema.Name; + } + + return obj.GetType().Name; + } + } + /// /// Returns the string representation of this . /// @@ -685,7 +728,7 @@ public override int GetHashCode() RealmValueType.Int => AsInt64().GetHashCode(), RealmValueType.Bool => AsBool().GetHashCode(), RealmValueType.String => AsString().GetHashCode(), - RealmValueType.Data => AsData().GetHashCode(), + RealmValueType.Data => AsData().Length, RealmValueType.Date => AsDate().GetHashCode(), RealmValueType.Float => AsFloat().GetHashCode(), RealmValueType.Double => AsDouble().GetHashCode(), @@ -800,31 +843,31 @@ public override int GetHashCode() public static implicit operator RealmValue(Guid val) => Guid(val); - public static implicit operator RealmValue(char? val) => val == null ? Null() : Int(val.Value); + public static implicit operator RealmValue(char? val) => val == null ? Null : Int(val.Value); - public static implicit operator RealmValue(byte? val) => val == null ? Null() : Int(val.Value); + public static implicit operator RealmValue(byte? val) => val == null ? Null : Int(val.Value); - public static implicit operator RealmValue(short? val) => val == null ? Null() : Int(val.Value); + public static implicit operator RealmValue(short? val) => val == null ? Null : Int(val.Value); - public static implicit operator RealmValue(int? val) => val == null ? Null() : Int(val.Value); + public static implicit operator RealmValue(int? val) => val == null ? Null : Int(val.Value); - public static implicit operator RealmValue(long? val) => val == null ? Null() : Int(val.Value); + public static implicit operator RealmValue(long? val) => val == null ? Null : Int(val.Value); - public static implicit operator RealmValue(float? val) => val == null ? Null() : Float(val.Value); + public static implicit operator RealmValue(float? val) => val == null ? Null : Float(val.Value); - public static implicit operator RealmValue(double? val) => val == null ? Null() : Double(val.Value); + public static implicit operator RealmValue(double? val) => val == null ? Null : Double(val.Value); - public static implicit operator RealmValue(bool? val) => val == null ? Null() : Bool(val.Value); + public static implicit operator RealmValue(bool? val) => val == null ? Null : Bool(val.Value); - public static implicit operator RealmValue(DateTimeOffset? val) => val == null ? Null() : Date(val.Value); + public static implicit operator RealmValue(DateTimeOffset? val) => val == null ? Null : Date(val.Value); - public static implicit operator RealmValue(decimal? val) => val == null ? Null() : Decimal(val.Value); + public static implicit operator RealmValue(decimal? val) => val == null ? Null : Decimal(val.Value); - public static implicit operator RealmValue(Decimal128? val) => val == null ? Null() : Decimal(val.Value); + public static implicit operator RealmValue(Decimal128? val) => val == null ? Null : Decimal(val.Value); - public static implicit operator RealmValue(ObjectId? val) => val == null ? Null() : ObjectId(val.Value); + public static implicit operator RealmValue(ObjectId? val) => val == null ? Null : ObjectId(val.Value); - public static implicit operator RealmValue(Guid? val) => val == null ? Null() : Guid(val.Value); + public static implicit operator RealmValue(Guid? val) => val == null ? Null : Guid(val.Value); public static implicit operator RealmValue(RealmInteger val) => Int(val); @@ -834,13 +877,13 @@ public override int GetHashCode() public static implicit operator RealmValue(RealmInteger val) => Int(val); - public static implicit operator RealmValue(RealmInteger? val) => val == null ? Null() : Int(val.Value); + public static implicit operator RealmValue(RealmInteger? val) => val == null ? Null : Int(val.Value); - public static implicit operator RealmValue(RealmInteger? val) => val == null ? Null() : Int(val.Value); + public static implicit operator RealmValue(RealmInteger? val) => val == null ? Null : Int(val.Value); - public static implicit operator RealmValue(RealmInteger? val) => val == null ? Null() : Int(val.Value); + public static implicit operator RealmValue(RealmInteger? val) => val == null ? Null : Int(val.Value); - public static implicit operator RealmValue(RealmInteger? val) => val == null ? Null() : Int(val.Value); + public static implicit operator RealmValue(RealmInteger? val) => val == null ? Null : Int(val.Value); public static implicit operator RealmValue(byte[] val) => Data(val); @@ -852,7 +895,7 @@ private void EnsureType(string target, RealmValueType type) { if (Type != type) { - throw new InvalidOperationException($"Can't cast to {target} since the underlying value is {Type}"); + throw new InvalidCastException($"Can't cast to {target} since the underlying value is {Type}"); } } @@ -894,13 +937,15 @@ public bool Equals(RealmValue other) RealmValueType.Int => AsInt64() == other.AsInt64(), RealmValueType.Bool => AsBool() == other.AsBool(), RealmValueType.String => AsString() == other.AsString(), - RealmValueType.Data => AsData() == other.AsData(), + RealmValueType.Data => AsData().SequenceEqual(other.AsData()), RealmValueType.Date => AsDate() == other.AsDate(), RealmValueType.Float => AsFloat() == other.AsFloat(), RealmValueType.Double => AsDouble() == other.AsDouble(), RealmValueType.Decimal128 => AsDecimal128() == other.AsDecimal128(), RealmValueType.ObjectId => AsObjectId() == other.AsObjectId(), + RealmValueType.Guid => AsGuid() == other.AsGuid(), RealmValueType.Object => AsRealmObject().Equals(other.AsRealmObject()), + RealmValueType.Null => true, _ => false, }; } diff --git a/Realm/Realm/Dynamic/MetaRealmObject.cs b/Realm/Realm/Dynamic/MetaRealmObject.cs index 751ff858b2..1d1845bd13 100644 --- a/Realm/Realm/Dynamic/MetaRealmObject.cs +++ b/Realm/Realm/Dynamic/MetaRealmObject.cs @@ -198,14 +198,13 @@ public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicM var self = GetLimitedSelf(); var valueExpression = value.Expression; - valueExpression = Expression.Call(CreateRealmValueMethod.MakeGenericMethod(valueExpression.Type), new[] { valueExpression, Expression.Constant(property.Type.ToRealmValueType()) }); - var setter = property.IsPrimaryKey ? GetSetMethod(DummyHandle.SetValueUnique) : GetSetMethod(DummyHandle.SetValue); - if (valueExpression.Type != typeof(RealmValue)) { - valueExpression = Expression.Convert(valueExpression, typeof(RealmValue)); + valueExpression = Expression.Call(CreateRealmValueMethod.MakeGenericMethod(valueExpression.Type), new[] { valueExpression, Expression.Constant(property.Type.ToRealmValueType()) }); } + var setter = property.IsPrimaryKey ? GetSetMethod(DummyHandle.SetValueUnique) : GetSetMethod(DummyHandle.SetValue); + arguments.Add(valueExpression); if (!property.IsPrimaryKey) diff --git a/Realm/Realm/Extensions/CollectionExtensions.cs b/Realm/Realm/Extensions/CollectionExtensions.cs index 3eba8565b4..b99250804c 100644 --- a/Realm/Realm/Extensions/CollectionExtensions.cs +++ b/Realm/Realm/Extensions/CollectionExtensions.cs @@ -178,6 +178,16 @@ public static void Move(this IList list, int from, int to) } else { + if (from < 0 || from >= list.Count) + { + throw new ArgumentOutOfRangeException(nameof(from)); + } + + if (to < 0 || to >= list.Count) + { + throw new ArgumentOutOfRangeException(nameof(to)); + } + var item = list[from]; list.RemoveAt(from); list.Insert(to, item); diff --git a/Realm/Realm/Handles/CollectionHandleBase.cs b/Realm/Realm/Handles/CollectionHandleBase.cs index 6a112c8631..507b6899fe 100644 --- a/Realm/Realm/Handles/CollectionHandleBase.cs +++ b/Realm/Realm/Handles/CollectionHandleBase.cs @@ -17,7 +17,6 @@ //////////////////////////////////////////////////////////////////////////// using System; -using Realms.Native; namespace Realms { @@ -54,21 +53,5 @@ public ResultsHandle Snapshot() public abstract CollectionHandleBase Freeze(SharedRealmHandle frozenRealmHandle); public abstract void Clear(); - - protected RealmValue ToRealmValue(PrimitiveValue primitive, RealmObjectBase.Metadata metadata, Realm realm) - { - if (primitive.Type != RealmValueType.Object) - { - return new RealmValue(primitive); - } - - var objectHandle = primitive.AsObject(Root); - if (metadata == null) - { - throw new NotImplementedException("Mixed objects are not supported yet."); - } - - return new RealmValue(realm.MakeObject(metadata, objectHandle)); - } } -} \ No newline at end of file +} diff --git a/Realm/Realm/Handles/DictionaryHandle.cs b/Realm/Realm/Handles/DictionaryHandle.cs index 17daa8c2dc..0ac5cf76cd 100644 --- a/Realm/Realm/Handles/DictionaryHandle.cs +++ b/Realm/Realm/Handles/DictionaryHandle.cs @@ -184,7 +184,7 @@ public override CollectionHandleBase Freeze(SharedRealmHandle frozenRealmHandle) return new DictionaryHandle(frozenRealmHandle, result); } - public bool TryGet(string key, RealmObjectBase.Metadata metadata, Realm realm, out RealmValue value) + public bool TryGet(string key, Realm realm, out RealmValue value) { RealmValue keyValue = key; var (primitiveKey, keyHandles) = keyValue.ToNative(); @@ -198,30 +198,15 @@ public bool TryGet(string key, RealmObjectBase.Metadata metadata, Realm realm, o return false; } - if (result.Type != RealmValueType.Object) - { - value = new RealmValue(result); - } - else - { - var objectHandle = result.AsObject(Root); - - if (metadata == null) - { - throw new NotImplementedException("Mixed objects are not supported yet."); - } - - value = new RealmValue(realm.MakeObject(metadata, objectHandle)); - } - + value = new RealmValue(result, realm); return true; } - public KeyValuePair GetValueAtIndex(int index, RealmObjectBase.Metadata metadata, Realm realm) + public KeyValuePair GetValueAtIndex(int index, Realm realm) { NativeMethods.get_at_index(this, (IntPtr)index, out var key, out var primitiveValue, out var ex); ex.ThrowIfNecessary(); - var value = ToRealmValue(primitiveValue, metadata, realm); + var value = new RealmValue(primitiveValue, realm); return new KeyValuePair(key.AsString(), value.As()); } diff --git a/Realm/Realm/Handles/ListHandle.cs b/Realm/Realm/Handles/ListHandle.cs index 1cb5b5f217..3fcb97ad6c 100644 --- a/Realm/Realm/Handles/ListHandle.cs +++ b/Realm/Realm/Handles/ListHandle.cs @@ -112,11 +112,11 @@ protected override void Unbind() NativeMethods.destroy(handle); } - public RealmValue GetValueAtIndex(int index, RealmObjectBase.Metadata metadata, Realm realm) + public RealmValue GetValueAtIndex(int index, Realm realm) { NativeMethods.get_value(this, (IntPtr)index, out var result, out var ex); ex.ThrowIfNecessary(); - return ToRealmValue(result, metadata, realm); + return new RealmValue(result, realm); } public unsafe void Add(in RealmValue value) diff --git a/Realm/Realm/Handles/ObjectHandle.cs b/Realm/Realm/Handles/ObjectHandle.cs index 39df0e856b..bc2853b59b 100644 --- a/Realm/Realm/Handles/ObjectHandle.cs +++ b/Realm/Realm/Handles/ObjectHandle.cs @@ -20,6 +20,7 @@ using System.Runtime.InteropServices; using Realms.Exceptions; using Realms.Native; +using Realms.Schema; namespace Realms { @@ -90,6 +91,9 @@ private static class NativeMethods [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_freeze", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr freeze(ObjectHandle handle, SharedRealmHandle frozen_realm, out NativeException ex); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "object_get_schema", CallingConvention = CallingConvention.Cdecl)] + public static extern void get_schema(ObjectHandle objectHandle, IntPtr callback, out NativeException ex); + #pragma warning restore SA1121 // Use built-in type alias #pragma warning restore IDE1006 // Naming Styles } @@ -163,14 +167,20 @@ public RealmValue GetValue(string propertyName, RealmObjectBase.Metadata metadat NativeMethods.get_value(this, propertyIndex, out var result, out var nativeException); nativeException.ThrowIfNecessary(); - if (result.Type != RealmValueType.Object) - { - return new RealmValue(result, this, propertyIndex); - } + return new RealmValue(result, realm, this, propertyIndex); + } + + public RealmSchema GetSchema() + { + RealmSchema result = null; + Action callback = (nativeSmallSchema) => result = RealmSchema.CreateFromObjectStoreSchema(nativeSmallSchema); + + // The callbackHandle will get freed in SharedRealmHandle.GetNativeSchema. + var callbackHandle = GCHandle.Alloc(callback); + NativeMethods.get_schema(this, GCHandle.ToIntPtr(callbackHandle), out var nativeException); + nativeException.ThrowIfNecessary(); - var objectHandle = result.AsObject(Root); - metadata.Schema.TryFindProperty(propertyName, out var property); - return new RealmValue(realm.MakeObject(realm.Metadata[property.ObjectType], objectHandle)); + return result; } public void SetValue(IntPtr propertyIndex, in RealmValue value, Realm realm) @@ -212,7 +222,9 @@ public void SetValueUnique(IntPtr propertyIndex, in RealmValue value) { NativeMethods.get_value(this, propertyIndex, out var result, out var nativeException); nativeException.ThrowIfNecessary(); - var currentValue = new RealmValue(result, this, propertyIndex); + + // Objects can't be PKs, so realm: null is fine. + var currentValue = new RealmValue(result, realm: null, this, propertyIndex); if (!currentValue.Equals(value)) { diff --git a/Realm/Realm/Handles/QueryHandle.cs b/Realm/Realm/Handles/QueryHandle.cs index af8addcccc..03a8ec5fd7 100644 --- a/Realm/Realm/Handles/QueryHandle.cs +++ b/Realm/Realm/Handles/QueryHandle.cs @@ -106,6 +106,12 @@ public static extern void string_like(QueryHandle queryPtr, SharedRealmHandle re [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_create_results", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr create_results(QueryHandle queryPtr, SharedRealmHandle sharedRealm, SortDescriptorHandle sortDescriptor, out NativeException ex); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_realm_value_type_equal", CallingConvention = CallingConvention.Cdecl)] + public static extern void realm_value_type_equal(QueryHandle queryPtr, SharedRealmHandle realm, IntPtr property_ndx, RealmValueType realm_value_type, out NativeException ex); + + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "query_realm_value_type_not_equal", CallingConvention = CallingConvention.Cdecl)] + public static extern void realm_value_type_not_equal(QueryHandle queryPtr, SharedRealmHandle realm, IntPtr property_ndx, RealmValueType realm_value_type, out NativeException ex); + #pragma warning restore IDE1006 // Naming Styles } @@ -250,6 +256,18 @@ public void NullNotEqual(SharedRealmHandle realm, IntPtr propertyIndex) nativeException.ThrowIfNecessary(); } + public unsafe void RealmValueTypeEqual(SharedRealmHandle realm, IntPtr propertyIndex, RealmValueType type) + { + NativeMethods.realm_value_type_equal(this, realm, propertyIndex, type, out var nativeException); + nativeException.ThrowIfNecessary(); + } + + public unsafe void RealmValueTypeNotEqual(SharedRealmHandle realm, IntPtr propertyIndex, RealmValueType type) + { + NativeMethods.realm_value_type_not_equal(this, realm, propertyIndex, type, out var nativeException); + nativeException.ThrowIfNecessary(); + } + public void Not() { NativeMethods.not(this, out var nativeException); diff --git a/Realm/Realm/Handles/ResultsHandle.cs b/Realm/Realm/Handles/ResultsHandle.cs index c1823ce49a..afc4d8d7ba 100644 --- a/Realm/Realm/Handles/ResultsHandle.cs +++ b/Realm/Realm/Handles/ResultsHandle.cs @@ -18,6 +18,7 @@ using System; using System.Runtime.InteropServices; +using Realms.Exceptions; using Realms.Native; namespace Realms @@ -66,8 +67,8 @@ public static extern IntPtr get_filtered_results(ResultsHandle results, [MarshalAs(UnmanagedType.LPArray), In] PrimitiveValue[] arguments, IntPtr args_count, out NativeException ex); - [DllImport(InteropConfig.DLL_NAME, EntryPoint = "results_find_object", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr find_object(ResultsHandle results, ObjectHandle objectHandle, out NativeException ex); + [DllImport(InteropConfig.DLL_NAME, EntryPoint = "results_find_value", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr find_value(ResultsHandle results, PrimitiveValue value, out NativeException ex); [DllImport(InteropConfig.DLL_NAME, EntryPoint = "results_get_descriptor_ordering", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr get_sort_descriptor(ResultsHandle results, out NativeException ex); @@ -112,11 +113,11 @@ protected override void Unbind() NativeMethods.destroy(handle); } - public RealmValue GetValueAtIndex(int index, RealmObjectBase.Metadata metadata, Realm realm) + public RealmValue GetValueAtIndex(int index, Realm realm) { NativeMethods.get_value(this, (IntPtr)index, out var result, out var ex); ex.ThrowIfNecessary(); - return ToRealmValue(result, metadata, realm); + return new RealmValue(result, realm); } public override int Count() @@ -193,7 +194,13 @@ public override ResultsHandle GetFilteredResults(string query, RealmValue[] argu var handles = new RealmValue.HandlesToCleanup?[arguments.Length]; for (var i = 0; i < arguments.Length; i++) { - (primitiveValues[i], handles[i]) = arguments[i].ToNative(); + var argument = arguments[i]; + if (argument.Type == RealmValueType.Object && !argument.AsRealmObject().IsManaged) + { + throw new RealmException("Can't use unmanaged object as argument of Filter"); + } + + (primitiveValues[i], handles[i]) = argument.ToNative(); } var ptr = NativeMethods.get_filtered_results(this, query, (IntPtr)query.Length, primitiveValues, (IntPtr)primitiveValues.Length, out var ex); @@ -206,9 +213,11 @@ public override ResultsHandle GetFilteredResults(string query, RealmValue[] argu return new ResultsHandle(this, ptr); } - public int Find(ObjectHandle objectHandle) + public unsafe int Find(in RealmValue value) { - var result = NativeMethods.find_object(this, objectHandle, out var nativeException); + var (primitive, handles) = value.ToNative(); + var result = NativeMethods.find_value(this, primitive, out var nativeException); + handles?.Dispose(); nativeException.ThrowIfNecessary(); return (int)result; } diff --git a/Realm/Realm/Handles/SetHandle.cs b/Realm/Realm/Handles/SetHandle.cs index 5b7596858f..b6e87bd90a 100644 --- a/Realm/Realm/Handles/SetHandle.cs +++ b/Realm/Realm/Handles/SetHandle.cs @@ -175,11 +175,11 @@ public override CollectionHandleBase Freeze(SharedRealmHandle frozenRealmHandle) return new SetHandle(frozenRealmHandle, result); } - public RealmValue GetValueAtIndex(int index, RealmObjectBase.Metadata metadata, Realm realm) + public RealmValue GetValueAtIndex(int index, Realm realm) { NativeMethods.get_value(this, (IntPtr)index, out var result, out var ex); ex.ThrowIfNecessary(); - return ToRealmValue(result, metadata, realm); + return new RealmValue(result, realm); } public unsafe bool Add(in RealmValue value) diff --git a/Realm/Realm/Helpers/Argument.cs b/Realm/Realm/Helpers/Argument.cs index 77732cdf68..41c3f82c57 100644 --- a/Realm/Realm/Helpers/Argument.cs +++ b/Realm/Realm/Helpers/Argument.cs @@ -17,11 +17,14 @@ //////////////////////////////////////////////////////////////////////////// using System; +using Realms.Logging; namespace Realms.Helpers { internal static class Argument { + private const string OpenIssueText = "Please create a new issue at http://github.com/realm/realm-dotnet/issues/new."; + public static void NotNullOrEmpty(string value, string paramName) { if (string.IsNullOrEmpty(value)) @@ -64,5 +67,14 @@ public static void NotNull(object value, string paramName) throw new ArgumentNullException(paramName); } } + + public static void AssertDebug(string message) + { + Logger.LogDefault(LogLevel.Error, $"{message} {OpenIssueText}"); + +#if DEBUG + throw new Exception(message); +#endif + } } } diff --git a/Realm/Realm/Helpers/Operator.cs b/Realm/Realm/Helpers/Operator.cs index 8de82d67a8..f7086bd4b7 100644 --- a/Realm/Realm/Helpers/Operator.cs +++ b/Realm/Realm/Helpers/Operator.cs @@ -397,7 +397,7 @@ public static TResult Convert(TFrom value) */ if (value is null) { - return Convert(RealmValue.Null()); + return Convert(RealmValue.Null); } /* This is another special case where `value` is inheritable from RealmObjectBase. There's diff --git a/Realm/Realm/Helpers/Operator.tt b/Realm/Realm/Helpers/Operator.tt index 5138345d76..3b2f33d5d6 100644 --- a/Realm/Realm/Helpers/Operator.tt +++ b/Realm/Realm/Helpers/Operator.tt @@ -110,7 +110,7 @@ namespace Realms.Helpers */ if (value is null) { - return Convert(RealmValue.Null()); + return Convert(RealmValue.Null); } /* This is another special case where `value` is inheritable from RealmObjectBase. There's diff --git a/Realm/Realm/Linq/RealmResults.cs b/Realm/Realm/Linq/RealmResults.cs index 1f924c231f..4227d15f9d 100644 --- a/Realm/Realm/Linq/RealmResults.cs +++ b/Realm/Realm/Linq/RealmResults.cs @@ -76,24 +76,20 @@ internal override CollectionHandleBase GetOrCreateHandle() return qv.MakeResultsForQuery(); } - protected override T GetValueAtIndex(int index) => ResultsHandle.GetValueAtIndex(index, Metadata, Realm).As(); + protected override T GetValueAtIndex(int index) => ResultsHandle.GetValueAtIndex(index, Realm).As(); public override int IndexOf(T value) { Argument.NotNull(value, nameof(value)); - if (_argumentType != RealmValueType.Object) - { - throw new NotSupportedException("IndexOf on non-object results is not supported."); - } + var realmValue = Operator.Convert(value); - var obj = value as RealmObjectBase; - if (!obj.IsManaged) + if (realmValue.Type == RealmValueType.Object && !realmValue.AsRealmObject().IsManaged) { throw new ArgumentException("Value does not belong to a realm", nameof(value)); } - return ResultsHandle.Find(obj.ObjectHandle); + return ResultsHandle.Find(realmValue); } void ICollection.Add(T item) => throw new NotSupportedException("Adding elements to the Results collection is not supported."); diff --git a/Realm/Realm/Linq/RealmResultsVisitor.cs b/Realm/Realm/Linq/RealmResultsVisitor.cs index ab38bfaa76..6f79c2d303 100644 --- a/Realm/Realm/Linq/RealmResultsVisitor.cs +++ b/Realm/Realm/Linq/RealmResultsVisitor.cs @@ -560,8 +560,6 @@ protected override Expression VisitBinary(BinaryExpression node) memberExpression = leftExpression as MemberExpression; } - var leftName = GetColumnName(memberExpression, node.NodeType); - if (!TryExtractConstantValue(node.Right, out object rightValue)) { throw new NotSupportedException($"The rhs of the binary operator '{rightExpression.NodeType}' should be a constant or closure variable expression. \nUnable to process '{node.Right}'."); @@ -572,6 +570,22 @@ protected override Expression VisitBinary(BinaryExpression node) throw new NotSupportedException($"The rhs of the binary operator '{rightExpression.NodeType}' should be a managed RealmObjectBase. \nUnable to process '{node.Right}'."); } + string leftName = null; + + if (IsRealmValueTypeExpression(memberExpression, out leftName)) + { + if (node.NodeType != ExpressionType.Equal && node.NodeType != ExpressionType.NotEqual) + { + throw new NotSupportedException($"Only expressions of type Equal and NotEqual can be used with RealmValueType."); + } + + rightValue = (RealmValueType)(int)rightValue; + } + else + { + leftName = GetColumnName(memberExpression, node.NodeType); + } + switch (node.NodeType) { case ExpressionType.Equal: @@ -611,7 +625,7 @@ private Expression GetObjectAtIndex(int index, ResultsHandle rh, string methodNa { try { - var val = rh.GetValueAtIndex(index, _metadata, _realm); + var val = rh.GetValueAtIndex(index, _realm); return Expression.Constant(val.AsRealmObject()); } catch (ArgumentOutOfRangeException ex) @@ -640,11 +654,15 @@ private void AddQueryEqual(QueryHandle queryHandle, string columnName, object va switch (value) { case null: + case RealmValue rv when rv.Type == RealmValueType.Null: queryHandle.NullEqual(_realm.SharedRealmHandle, propertyIndex); break; case string stringValue: queryHandle.StringEqual(_realm.SharedRealmHandle, propertyIndex, stringValue, caseSensitive: true); break; + case RealmValueType realmValueType: + queryHandle.RealmValueTypeEqual(_realm.SharedRealmHandle, propertyIndex, realmValueType); + break; default: // The other types aren't handled by the switch because of potential compiler applied conversions AddQueryForConvertibleTypes(_realm.SharedRealmHandle, propertyIndex, value, columnType, queryHandle.ValueEqual); @@ -658,11 +676,15 @@ private void AddQueryNotEqual(QueryHandle queryHandle, string columnName, object switch (value) { case null: + case RealmValue rv when rv.Type == RealmValueType.Null: queryHandle.NullNotEqual(_realm.SharedRealmHandle, propertyIndex); break; case string stringValue: queryHandle.StringNotEqual(_realm.SharedRealmHandle, propertyIndex, stringValue, caseSensitive: true); break; + case RealmValueType realmValueType: + queryHandle.RealmValueTypeNotEqual(_realm.SharedRealmHandle, propertyIndex, realmValueType); + break; default: // The other types aren't handled by the switch because of potential compiler applied conversions AddQueryForConvertibleTypes(_realm.SharedRealmHandle, propertyIndex, value, columnType, queryHandle.ValueNotEqual); @@ -797,6 +819,10 @@ private static void AddQueryForConvertibleTypes(SharedRealmHandle realm, IntPtr { action(realm, propertyIndex, (RealmObjectBase)value); } + else if (columnType == typeof(RealmValue)) + { + action(realm, propertyIndex, (RealmValue)value); + } else { throw new NotImplementedException(); @@ -839,6 +865,24 @@ private string GetColumnName(MemberExpression memberExpression, ExpressionType? return name; } + private bool IsRealmValueTypeExpression(MemberExpression memberExpression, out string leftName) + { + leftName = null; + + if (memberExpression?.Type != typeof(RealmValueType)) + { + return false; + } + + if (memberExpression.Expression is MemberExpression innerExpression) + { + leftName = GetColumnName(innerExpression, memberExpression.NodeType); + return innerExpression.Type == typeof(RealmValue); + } + + return false; + } + // strange as it may seem, this is also called for the LHS when simply iterating All() protected override Expression VisitConstant(ConstantExpression node) { diff --git a/Realm/Realm/Logging/Logger.cs b/Realm/Realm/Logging/Logger.cs index b3c69edee6..ab30d64b54 100644 --- a/Realm/Realm/Logging/Logger.cs +++ b/Realm/Realm/Logging/Logger.cs @@ -110,7 +110,9 @@ protected Logger() /// The message to log. public void Log(LogLevel level, string message) { +#pragma warning disable CS0618 // Type or member is obsolete if (level < (_logLevel ?? LogLevel)) +#pragma warning restore CS0618 // Type or member is obsolete { return; } diff --git a/Realm/Realm/Native/PrimitiveValue.cs b/Realm/Realm/Native/PrimitiveValue.cs index 7f63de3c54..0b217ae598 100644 --- a/Realm/Realm/Native/PrimitiveValue.cs +++ b/Realm/Realm/Native/PrimitiveValue.cs @@ -254,7 +254,21 @@ public byte[] AsBinary() return bytes; } - public ObjectHandle AsObject(RealmHandle root) => new ObjectHandle(root, link_value.object_ptr); + public RealmObjectBase AsObject(Realm realm) + { + var handle = new ObjectHandle(realm.SharedRealmHandle, link_value.object_ptr); + + // If Metadata doesn't contain the schema for this object, it's likely because + // the value is Mixed and the object type was added by a newer version of the + // app via Sync. In this case, we need to look up the object schema from disk + if (!realm.Metadata.TryGetValue(link_value.table_key, out var objectMetadata)) + { + var onDiskSchema = handle.GetSchema(); + objectMetadata = realm.MergeSchema(onDiskSchema)[link_value.table_key]; + } + + return realm.MakeObject(objectMetadata, handle); + } [StructLayout(LayoutKind.Sequential)] private unsafe struct StringValue @@ -274,6 +288,7 @@ private unsafe struct BinaryValue private struct LinkValue { public IntPtr object_ptr; + public TableKey table_key; } [StructLayout(LayoutKind.Sequential)] diff --git a/Realm/Realm/Realm.cs b/Realm/Realm/Realm.cs index 60bfdf4da4..db462c960e 100644 --- a/Realm/Realm/Realm.cs +++ b/Realm/Realm/Realm.cs @@ -481,6 +481,12 @@ internal RealmObjectBase MakeObject(RealmObjectBase.Metadata metadata, ObjectHan return ret; } + internal RealmMetadata MergeSchema(RealmSchema schema) + { + Metadata.Add(schema.Select(CreateRealmObjectMetadata)); + return Metadata; + } + /// /// This will start managing a which has been created as a standalone object. /// @@ -1391,11 +1397,7 @@ public RealmMetadata(IEnumerable objectsMetadata) stringToRealmObjectMetadataDict = new Dictionary(); tableKeyToRealmObjectMetadataDict = new Dictionary(); - foreach (var objectMetadata in objectsMetadata) - { - stringToRealmObjectMetadataDict[objectMetadata.Schema.Name] = objectMetadata; - tableKeyToRealmObjectMetadataDict[objectMetadata.TableKey] = objectMetadata; - } + Add(objectsMetadata); } public bool TryGetValue(string objectType, out RealmObjectBase.Metadata metadata) => @@ -1407,6 +1409,30 @@ public bool TryGetValue(TableKey tablekey, out RealmObjectBase.Metadata metadata public RealmObjectBase.Metadata this[string objectType] => stringToRealmObjectMetadataDict[objectType]; public RealmObjectBase.Metadata this[TableKey tablekey] => tableKeyToRealmObjectMetadataDict[tablekey]; + + public void Add(IEnumerable objectsMetadata) + { + foreach (var objectMetadata in objectsMetadata) + { + if (stringToRealmObjectMetadataDict.ContainsKey(objectMetadata.Schema.Name)) + { + Argument.AssertDebug($"Trying to add object schema to the string mapping that is already present: {objectMetadata.Schema.Name}"); + } + else + { + stringToRealmObjectMetadataDict[objectMetadata.Schema.Name] = objectMetadata; + } + + if (tableKeyToRealmObjectMetadataDict.ContainsKey(objectMetadata.TableKey)) + { + Argument.AssertDebug($"Trying to add object schema to the table key mapping that is already present: {objectMetadata.Schema.Name} - {objectMetadata.TableKey}"); + } + else + { + tableKeyToRealmObjectMetadataDict[objectMetadata.TableKey] = objectMetadata; + } + } + } } internal class State : IDisposable diff --git a/Realm/Realm/Schema/PropertyType.cs b/Realm/Realm/Schema/PropertyType.cs index fc33c52391..10e5294f6d 100644 --- a/Realm/Realm/Schema/PropertyType.cs +++ b/Realm/Realm/Schema/PropertyType.cs @@ -75,6 +75,11 @@ public enum PropertyType : ushort /// LinkingObjects = 8, + /// + /// A property that can contain RealmValue supported types. + /// + RealmValue = 9, + /// /// 96 bit ObjectID property. /// diff --git a/Realm/Realm/Schema/PropertyTypeEx.cs b/Realm/Realm/Schema/PropertyTypeEx.cs index f70b5aece6..179c9f9e3a 100644 --- a/Realm/Realm/Schema/PropertyTypeEx.cs +++ b/Realm/Realm/Schema/PropertyTypeEx.cs @@ -85,6 +85,9 @@ public static PropertyType ToPropertyType(this Type type, out Type objectType) case Type _ when type == typeof(Guid): return PropertyType.Guid | nullabilityModifier; + case Type _ when type == typeof(RealmValue): + return PropertyType.RealmValue | PropertyType.Nullable; + case Type _ when type.IsRealmObject() || type.IsEmbeddedObject(): objectType = type; return PropertyType.Object | PropertyType.Nullable; diff --git a/Tests/Realm.Tests/Database/CollectionTests.cs b/Tests/Realm.Tests/Database/CollectionTests.cs index a8e069aa6e..f33d489f0d 100644 --- a/Tests/Realm.Tests/Database/CollectionTests.cs +++ b/Tests/Realm.Tests/Database/CollectionTests.cs @@ -502,7 +502,6 @@ public static IEnumerable StringQuery_NumericValues() yield return new StringQueryNumericData(nameof(AllTypesObject.SingleProperty), 88.8f, 88.8, true); yield return new StringQueryNumericData(nameof(AllTypesObject.SingleProperty), 49f, 49, true); yield return new StringQueryNumericData(nameof(AllTypesObject.DoubleProperty), 106.0, 106m, true); - yield return new StringQueryNumericData(nameof(AllTypesObject.BooleanProperty), true, 1, true); yield return new StringQueryNumericData(nameof(AllTypesObject.DecimalProperty), 1m, 1f, true); yield return new StringQueryNumericData(nameof(AllTypesObject.DecimalProperty), 5m, 5.0, true); @@ -514,7 +513,6 @@ public static IEnumerable StringQuery_NumericValues() yield return new StringQueryNumericData(nameof(AllTypesObject.Int64Property), 74L, 7435, false); yield return new StringQueryNumericData(nameof(AllTypesObject.SingleProperty), 3.0f, 21.0, false); yield return new StringQueryNumericData(nameof(AllTypesObject.DoubleProperty), 4.0, 'c', false); - yield return new StringQueryNumericData(nameof(AllTypesObject.BooleanProperty), true, 298, false); // no implicit conversion no match yield return new StringQueryNumericData(nameof(AllTypesObject.DoubleProperty), 109.9, 109.9f, false); @@ -530,6 +528,7 @@ public static IEnumerable StringQuery_MismatchingTypes_ToTh yield return new StringQueryTestData(nameof(AllTypesObject.DoubleProperty), 5.0, "I'm getting angry"); yield return new StringQueryTestData(nameof(AllTypesObject.ByteProperty), 0x6, "I give up"); yield return new StringQueryTestData(nameof(AllTypesObject.BooleanProperty), true, "enough"); + yield return new StringQueryTestData(nameof(AllTypesObject.BooleanProperty), true, 1); yield return new StringQueryTestData(nameof(AllTypesObject.DateTimeOffsetProperty), new DateTimeOffset(1956, 6, 1, 0, 0, 0, TimeSpan.Zero), 5); yield return new StringQueryTestData(nameof(AllTypesObject.DecimalProperty), 7m, new byte[] { 0x1, 0x2, 0x3 }); yield return new StringQueryTestData(nameof(AllTypesObject.Decimal128Property), new Decimal128(564.42343424323), new byte[] { 0x3, 0x2, 0x1 }); @@ -667,7 +666,7 @@ public void QueryFilter_WithArgumentsUnmanagedObjects_ShouldThrow() _realm.Add(new Owner { TopDog = new Dog { Name = "Doge", Color = "almost yellow", Vaccinated = true } }); }); - Assert.Throws(() => _realm.All().Filter("TopDog = $0", new Dog { Name = "Doge", Color = "almost yellow", Vaccinated = true }), "**put your message here!**"); + Assert.Throws(() => _realm.All().Filter("TopDog = $0", new Dog { Name = "Doge", Color = "almost yellow", Vaccinated = true })); } [Test] diff --git a/Tests/Realm.Tests/Database/DynamicAccessTests.cs b/Tests/Realm.Tests/Database/DynamicAccessTests.cs index 0ae5eed1c4..e63203091d 100644 --- a/Tests/Realm.Tests/Database/DynamicAccessTests.cs +++ b/Tests/Realm.Tests/Database/DynamicAccessTests.cs @@ -17,22 +17,33 @@ //////////////////////////////////////////////////////////////////////////// using System; +using System.Collections.Generic; using System.Runtime.CompilerServices; using Microsoft.CSharp.RuntimeBinder; +using MongoDB.Bson; using NUnit.Framework; using Realms.Dynamic; namespace Realms.Tests.Database { - [TestFixture, Preserve(AllMembers = true)] + [TestFixture(DynamicTestObjectType.RealmObject)] + [TestFixture(DynamicTestObjectType.DynamicRealmObject)] + [Preserve(AllMembers = true)] public class DynamicAccessTests : RealmInstanceTest { + private readonly DynamicTestObjectType _mode; + + public DynamicAccessTests(DynamicTestObjectType mode) + { + _mode = mode; + } + protected override RealmConfiguration CreateConfiguration(string path) { return new RealmConfiguration(path) { - ObjectClasses = new[] { typeof(AllTypesObject) }, - IsDynamic = true + ObjectClasses = new[] { typeof(AllTypesObject), typeof(IntPropertyObject) }, + IsDynamic = _mode == DynamicTestObjectType.DynamicRealmObject }; } @@ -43,7 +54,14 @@ public void SimpleTest() using (var transaction = _realm.BeginWrite()) { allTypesObject = _realm.DynamicApi.CreateObject("AllTypesObject", null); - Assert.That(allTypesObject, Is.InstanceOf()); + if (_mode == DynamicTestObjectType.DynamicRealmObject) + { + Assert.That(allTypesObject, Is.InstanceOf()); + } + else + { + Assert.That(allTypesObject, Is.InstanceOf()); + } allTypesObject.CharProperty = 'F'; allTypesObject.NullableCharProperty = 'o'; @@ -65,50 +83,109 @@ public void SetAndGetValue(string propertyName, T propertyValue) { allTypesObject = _realm.DynamicApi.CreateObject("AllTypesObject", null); - CreateDynamicSetter(propertyName).Invoke(allTypesObject, propertyValue); + InvokeSetter(allTypesObject, propertyName, propertyValue); transaction.Commit(); } - Assert.That((T)CreateDynamicGetter(propertyName).Invoke(allTypesObject), Is.EqualTo(propertyValue)); + Assert.That((T)InvokeGetter(allTypesObject, propertyName), Is.EqualTo(propertyValue)); } [TestCaseSource(typeof(AccessTests), nameof(AccessTests.SetAndReplaceWithNullCases))] public void SetValueAndReplaceWithNull(string propertyName, T propertyValue) { - var getter = CreateDynamicGetter(propertyName); - object allTypesObject; using (var transaction = _realm.BeginWrite()) { allTypesObject = _realm.DynamicApi.CreateObject("AllTypesObject", null); - CreateDynamicSetter(propertyName).Invoke(allTypesObject, propertyValue); + InvokeSetter(allTypesObject, propertyName, propertyValue); transaction.Commit(); } - Assert.That((T)getter(allTypesObject), Is.EqualTo(propertyValue)); + Assert.That((T)InvokeGetter(allTypesObject, propertyName), Is.EqualTo(propertyValue)); using (var transaction = _realm.BeginWrite()) { - CreateDynamicSetter(propertyName).Invoke(allTypesObject, null); + InvokeSetter(allTypesObject, propertyName, null); transaction.Commit(); } - Assert.That(getter(allTypesObject), Is.EqualTo(null)); + Assert.That(InvokeGetter(allTypesObject, propertyName), Is.EqualTo(null)); + } + + public static IEnumerable RealmValues = new[] + { + RealmValue.Null, + RealmValue.Create(10, RealmValueType.Int), + RealmValue.Create(true, RealmValueType.Bool), + RealmValue.Create("abc", RealmValueType.String), + RealmValue.Create(new byte[] { 0, 1, 2 }, RealmValueType.Data), + RealmValue.Create(DateTimeOffset.FromUnixTimeSeconds(1616137641), RealmValueType.Date), + RealmValue.Create(1.5f, RealmValueType.Float), + RealmValue.Create(2.5d, RealmValueType.Double), + RealmValue.Create(5m, RealmValueType.Decimal128), + RealmValue.Create(new ObjectId("5f63e882536de46d71877979"), RealmValueType.ObjectId), + RealmValue.Create(new Guid("{F2952191-A847-41C3-8362-497F92CB7D24}"), RealmValueType.Guid), + }; + + [TestCaseSource(nameof(RealmValues))] + public void RealmValueTests(RealmValue rv) + { + dynamic ato = null; + + _realm.Write(() => + { + ato = _realm.DynamicApi.CreateObject("AllTypesObject", null); + + ato.RealmValueProperty = rv; + }); + + Assert.That((RealmValue)ato.RealmValueProperty, Is.EqualTo(rv)); + } + + [Test] + public void RealmValueTests_WithObject() + { + dynamic ato = null; + RealmValue rv = RealmValue.Null; + + _realm.Write(() => + { + var intObject = _realm.DynamicApi.CreateObject("IntPropertyObject", ObjectId.GenerateNewId()); + intObject.Int = 10; + rv = intObject; + + ato = _realm.DynamicApi.CreateObject("AllTypesObject", null); + ato.RealmValueProperty = rv; + }); + + Assert.That((RealmValue)ato.RealmValueProperty, Is.EqualTo(rv)); } - private static Func CreateDynamicGetter(string propertyName) + private object InvokeGetter(object o, string propertyName) { - var binder = Binder.GetMember(CSharpBinderFlags.None, propertyName, typeof(DynamicAccessTests), new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }); - var callsite = CallSite>.Create(binder); - return (self) => callsite.Target(callsite, self); + if (_mode == DynamicTestObjectType.DynamicRealmObject) + { + var binder = Binder.GetMember(CSharpBinderFlags.None, propertyName, typeof(DynamicAccessTests), new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }); + var callsite = CallSite>.Create(binder); + return callsite.Target(callsite, o); + } + + return TestHelpers.GetPropertyValue(o, propertyName); } - private static Action CreateDynamicSetter(string propertyName) + private void InvokeSetter(object o, string propertyName, T propertyValue) { - var binder = Binder.SetMember(CSharpBinderFlags.None, propertyName, typeof(DynamicAccessTests), new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null) }); - var callsite = CallSite>.Create(binder); - return (self, value) => callsite.Target(callsite, self, value); + if (_mode == DynamicTestObjectType.DynamicRealmObject) + { + var binder = Binder.SetMember(CSharpBinderFlags.None, propertyName, typeof(DynamicAccessTests), new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null) }); + var callsite = CallSite>.Create(binder); + callsite.Target(callsite, o, propertyValue); + } + else + { + TestHelpers.SetPropertyValue(o, propertyName, propertyValue); + } } } -} \ No newline at end of file +} diff --git a/Tests/Realm.Tests/Database/ListOfPrimitivesTests.cs b/Tests/Realm.Tests/Database/ListOfPrimitivesTests.cs index 0b12ae48e7..2c6d891813 100644 --- a/Tests/Realm.Tests/Database/ListOfPrimitivesTests.cs +++ b/Tests/Realm.Tests/Database/ListOfPrimitivesTests.cs @@ -22,6 +22,7 @@ using System.Threading.Tasks; using MongoDB.Bson; using NUnit.Framework; +using NUnit.Framework.Constraints; using Realms.Exceptions; namespace Realms.Tests.Database @@ -29,8 +30,6 @@ namespace Realms.Tests.Database [TestFixture, Preserve(AllMembers = true)] public class ListOfPrimitivesTests : RealmInstanceTest { - private static readonly Random _random = new Random(); - #region TestCaseSources private static readonly IEnumerable _booleanValues = new[] @@ -377,6 +376,23 @@ public static IEnumerable ByteArrayTestValues() yield return new object[] { new byte[][] { TestHelpers.GetBytes(1), null, TestHelpers.GetBytes(3), TestHelpers.GetBytes(3), null } }; } + public static IEnumerable RealmValueTestValues() + { + yield return new RealmValue[] { + RealmValue.Null, + RealmValue.Create(10, RealmValueType.Int), + RealmValue.Create(true, RealmValueType.Bool), + RealmValue.Create("abc", RealmValueType.String), + RealmValue.Create(new byte[] { 0, 1, 2 }, RealmValueType.Data), + RealmValue.Create(DateTimeOffset.FromUnixTimeSeconds(1616137641), RealmValueType.Date), + RealmValue.Create(1.5f, RealmValueType.Float), + RealmValue.Create(2.5d, RealmValueType.Double), + RealmValue.Create(5m, RealmValueType.Decimal128), + RealmValue.Create(new ObjectId("5f63e882536de46d71877979"), RealmValueType.ObjectId), + RealmValue.Create(new Guid("{F2952191-A847-41C3-8362-497F92CB7D24}"), RealmValueType.Guid), + RealmValue.Create(new IntPropertyObject { Int = 10 }, RealmValueType.Object) }; + } + #endregion TestCaseSources #region Managed Tests @@ -585,6 +601,12 @@ public void Test_ManagedByteArrayList(byte[][] values) RunManagedTests(obj => obj.ByteArrayList, values); } + [TestCaseSource(nameof(RealmValueTestValues))] + public void Test_ManagedRealmValueList(RealmValue[] values) + { + RunManagedTests(obj => obj.RealmValueList, values); + } + [TestCase] public void RequiredStringList_CanAddEmptyString() { @@ -630,216 +652,6 @@ public void RequiredStringList_WhenContainsNull_CanNotAddToRealm() Assert.That(ex.Message, Does.Contain("Attempted to add null to a list of required values")); } - private void RunManagedTests(Func> itemsGetter, T[] toAdd) - { - TestHelpers.RunAsyncTest(async () => - { - var listObject = new ListsObject(); - _realm.Write(() => _realm.Add(listObject)); - var items = itemsGetter(listObject); - await RunManagedTestsCore(items, toAdd); - }, timeout: 100000); - } - - private async Task RunManagedTestsCore(IList items, T[] toAdd) - { - var realm = (items as RealmList).Realm; - - if (toAdd == null) - { - toAdd = Array.Empty(); - } - - var notifications = new List(); - using var token = items.SubscribeForNotifications((sender, changes, error) => - { - if (changes != null) - { - notifications.Add(changes); - } - }); - - // Test add - realm.Write(() => - { - foreach (var item in toAdd) - { - items.Add(item); - } - }); - - // Test notifications - if (toAdd.Any()) - { - VerifyNotifications(realm, notifications, () => - { - Assert.That(notifications[0].InsertedIndices, Is.EquivalentTo(Enumerable.Range(0, toAdd.Length))); - }); - } - - // Test iterating - var iterator = 0; - foreach (var item in items) - { - Assert.That(item, Is.EqualTo(toAdd[iterator++])); - } - - // Test access by index - for (var i = 0; i < items.Count; i++) - { - Assert.That(items[i], Is.EqualTo(toAdd[i])); - } - - Assert.That(() => items[-1], Throws.TypeOf()); - Assert.That(() => items[items.Count], Throws.TypeOf()); - - // Test indexOf - foreach (var item in toAdd) - { - Assert.That(items.IndexOf(item), Is.EqualTo(Array.IndexOf(toAdd, item))); - } - - // Test threadsafe reference - var reference = ThreadSafeReference.Create(items); - await Task.Run(() => - { - using var bgRealm = GetRealm(realm.Config); - var backgroundList = bgRealm.ResolveReference(reference); - for (var i = 0; i < backgroundList.Count; i++) - { - Assert.That(backgroundList[i], Is.EqualTo(toAdd[i])); - } - }); - - if (toAdd.Any()) - { - // Test insert - var toInsert = toAdd[_random.Next(0, toAdd.Length)]; - realm.Write(() => - { - items.Insert(0, toInsert); - items.Insert(items.Count, toInsert); - - Assert.That(() => items.Insert(-1, toInsert), Throws.TypeOf()); - Assert.That(() => items.Insert(items.Count + 1, toInsert), Throws.TypeOf()); - }); - - Assert.That(items.First(), Is.EqualTo(toInsert)); - Assert.That(items.Last(), Is.EqualTo(toInsert)); - - // Test notifications - VerifyNotifications(realm, notifications, () => - { - Assert.That(notifications[0].InsertedIndices, Is.EquivalentTo(new[] { 0, items.Count - 1 })); - }); - - // Test remove - realm.Write(() => - { - items.Remove(toInsert); - items.RemoveAt(items.Count - 1); - - Assert.That(() => items.RemoveAt(-1), Throws.TypeOf()); - Assert.That(() => items.RemoveAt(items.Count + 1), Throws.TypeOf()); - }); - - CollectionAssert.AreEqual(items, toAdd); - - // Test notifications - VerifyNotifications(realm, notifications, () => - { - Assert.That(notifications[0].DeletedIndices, Is.EquivalentTo(new[] { 0, items.Count + 1 })); - }); - - // Test set - var indexToSet = TestHelpers.Random.Next(0, items.Count); - var previousValue = items[indexToSet]; - var valueToSet = toAdd[TestHelpers.Random.Next(0, toAdd.Length)]; - realm.Write(() => - { - items[indexToSet] = valueToSet; - - Assert.That(() => items[-1] = valueToSet, Throws.TypeOf()); - Assert.That(() => items[items.Count] = valueToSet, Throws.TypeOf()); - }); - - VerifyNotifications(realm, notifications, () => - { - Assert.That(notifications[0].ModifiedIndices, Is.EquivalentTo(new[] { indexToSet })); - }); - - realm.Write(() => items[indexToSet] = previousValue); - - VerifyNotifications(realm, notifications, () => - { - Assert.That(notifications[0].ModifiedIndices, Is.EquivalentTo(new[] { indexToSet })); - }); - - // Test move - var from = TestHelpers.Random.Next(0, items.Count); - var to = TestHelpers.Random.Next(0, items.Count); - - realm.Write(() => - { - items.Move(from, to); - - Assert.That(() => items.Move(-1, to), Throws.TypeOf()); - Assert.That(() => items.Move(from, -1), Throws.TypeOf()); - Assert.That(() => items.Move(items.Count + 1, to), Throws.TypeOf()); - Assert.That(() => items.Move(from, items.Count + 1), Throws.TypeOf()); - }); - - Assert.That(items[to], Is.EqualTo(toAdd[from])); - - // Test notifications - if (from != to) - { - VerifyNotifications(realm, notifications, () => - { - Assert.That(notifications[0].Moves.Length, Is.EqualTo(1)); - var move = notifications[0].Moves[0]; - - // Moves may be reported with swapped from/to arguments if the elements are adjacent - if (move.From == to) - { - Assert.That(move.From, Is.EqualTo(to)); - Assert.That(move.To, Is.EqualTo(from)); - } - else - { - Assert.That(move.From, Is.EqualTo(from)); - Assert.That(move.To, Is.EqualTo(to)); - } - }); - } - } - - // Test Clear - realm.Write(() => - { - items.Clear(); - }); - - Assert.That(items, Is.Empty); - - // Test notifications - if (toAdd.Any()) - { - VerifyNotifications(realm, notifications, () => - { - // TODO: verify notifications contains the expected Deletions collection - }); - } - } - - private static void VerifyNotifications(Realm realm, List notifications, Action verifier) - { - realm.Refresh(); - Assert.That(notifications.Count, Is.EqualTo(1)); - verifier(); - notifications.Clear(); - } - #endregion Managed Tests #region Unmanaged Tests @@ -1048,30 +860,448 @@ public void Test_UnmanagedByteArrayList(byte[][] values) RunUnmanagedTests(o => o.ByteArrayList, values); } - private void RunUnmanagedTests(Func> accessor, T[] toAdd) + [TestCaseSource(nameof(RealmValueTestValues))] + public void Test_UnmanagedRealmValueList(RealmValue[] values) { - if (toAdd == null) + RunUnmanagedTests(obj => obj.RealmValueList, values); + } + + #endregion Unmanaged Tests + + #region Utils + + private void RunManagedTests(Func> listGetter, T[] testList) + { + TestHelpers.RunAsyncTest(async () => { - toAdd = Array.Empty(); + var listObject = new ListsObject(); + var list = listGetter(listObject); + + var testData = new ListTestCaseData(testList); + testData.Seed(list); + + _realm.Write(() => _realm.Add(listObject)); + + var managedList = listGetter(listObject); + + Assert.That(list, Is.Not.SameAs(managedList)); + + RunTestsCore(testData, managedList); + + await testData.AssertThreadSafeReference(managedList); + testData.AssertNotifications(managedList); + }, timeout: 100000); + } + + private void RunUnmanagedTests(Func> listGetter, T[] testList) + { + var listObject = new ListsObject(); + var list = listGetter(listObject); + + var testData = new ListTestCaseData(testList); + testData.Seed(list); + + RunTestsCore(testData, list); + } + + private void RunTestsCore(ListTestCaseData testData, IList list) + { + testData.AssertEquality(list); + testData.AssertCount(list); + testData.AssertAccessByIndex(list); + testData.AssertAccessByIterator(list); + testData.AssertContains(list); + testData.AssertIndexOf(list); + + testData.AssertInsert(list); + testData.AssertMove(list); + testData.AssertSet(list); + testData.AssertRemove(list); + testData.AssertRemoveAt(list); + testData.AssertClear(list); + } + + public class ListTestCaseData : TestCaseData + { + private List referenceList = new List(); + + public ListTestCaseData(params T[] listData) + { + if (listData == null) + { + listData = Array.Empty(); + } + + referenceList.AddRange(listData); } - var listsObject = new ListsObject(); - var list = accessor(listsObject); + public void Seed(IList list) + { + WriteIfNecessary(list, () => + { + list.Clear(); + + for (int i = 0; i < referenceList.Count; i++) + { + list.Add(referenceList[i]); + } + }); + } - foreach (var item in toAdd) + public void AssertAccessByIterator(IList list) { - list.Add(item); + var iterator = 0; + foreach (var item in list) + { + Assert.That(item, Is.EqualTo(referenceList[iterator++])); + } } - CollectionAssert.AreEqual(list, toAdd); + public void AssertAccessByIndex(IList list) + { + for (int i = 0; i < referenceList.Count; i++) + { + Assert.That(list[i], Is.EqualTo(referenceList[i])); + } - _realm.Write(() => _realm.Add(listsObject)); + Assert.That(() => list[-1], Throws.TypeOf()); + Assert.That(() => list[list.Count], Throws.TypeOf()); + } - var managedList = accessor(listsObject); + public void AssertEquality(IList list) + { + Assert.That(list, Is.EquivalentTo(referenceList)); + } + + public void AssertIndexOf(IList list) + { + foreach (var val in referenceList) + { + Assert.That(list.IndexOf(val), Is.EqualTo(referenceList.IndexOf(val))); + } + } - CollectionAssert.AreEqual(managedList, toAdd); + public void AssertContains(IList list) + { + for (int i = 0; i < referenceList.Count; i++) + { + var rv = referenceList[i]; + Assert.That(list.Contains(rv), Is.True); + } + } + + public void AssertCount(IList list) + { + Assert.That(list.Count, Is.EqualTo(referenceList.Count)); + } + + public void AssertInsert(IList list) + { + if (!referenceList.Any()) + { + return; + } + + Seed(list); + + var toInsert = referenceList[TestHelpers.Random.Next(0, referenceList.Count)]; + + WriteIfNecessary(list, () => + { + list.Insert(0, toInsert); + list.Insert(list.Count, toInsert); + + Assert.That(() => list.Insert(-1, toInsert), Throws.TypeOf()); + Assert.That(() => list.Insert(list.Count + 1, toInsert), Throws.TypeOf()); + }); + + Assert.That(list.First(), Is.EqualTo(toInsert)); + Assert.That(list.Last(), Is.EqualTo(toInsert)); + } + + public void AssertClear(IList list) + { + WriteIfNecessary(list, () => + { + list.Clear(); + }); + + Assert.That(list.Count, Is.EqualTo(0)); + } + + public void AssertSet(IList list) + { + if (!referenceList.Any()) + { + return; + } + + Seed(list); + + var indexToSet = TestHelpers.Random.Next(0, referenceList.Count); + var valueToSet = referenceList[TestHelpers.Random.Next(0, referenceList.Count)]; + + WriteIfNecessary(list, () => + { + list[indexToSet] = valueToSet; + + Assert.That(list[indexToSet], Is.EqualTo(valueToSet)); + Assert.That(() => list[-1] = valueToSet, Throws.TypeOf()); + Assert.That(() => list[list.Count] = valueToSet, Throws.TypeOf()); + }); + } + + public void AssertMove(IList list) + { + if (!referenceList.Any()) + { + return; + } + + Seed(list); + + var from = TestHelpers.Random.Next(0, list.Count); + var to = TestHelpers.Random.Next(0, list.Count); + + WriteIfNecessary(list, () => + { + list.Move(from, to); + + Assert.That(() => list.Move(-1, to), Throws.TypeOf()); + Assert.That(() => list.Move(from, -1), Throws.TypeOf()); + Assert.That(() => list.Move(list.Count + 1, to), Throws.TypeOf()); + Assert.That(() => list.Move(from, list.Count + 1), Throws.TypeOf()); + }); + + Assert.That(list[to], Is.EqualTo(referenceList[from])); + } + + public void AssertRemove(IList list) + { + if (!referenceList.Any()) + { + return; + } + + Seed(list); + + var copyReferenceList = referenceList.ToList(); + var toRemove = copyReferenceList[TestHelpers.Random.Next(copyReferenceList.Count)]; + + copyReferenceList.Remove(toRemove); + + WriteIfNecessary(list, () => + { + list.Remove(toRemove); + + Assert.That(() => list.RemoveAt(-1), Throws.TypeOf()); + Assert.That(() => list.RemoveAt(list.Count), Throws.TypeOf()); + }); + + Assert.That(list, Is.EquivalentTo(copyReferenceList)); + } + + public void AssertRemoveAt(IList list) + { + if (!referenceList.Any()) + { + return; + } + + Seed(list); + + var copyReferenceList = referenceList.ToList(); + var toRemoveIndex = TestHelpers.Random.Next(copyReferenceList.Count); + + copyReferenceList.RemoveAt(toRemoveIndex); + + WriteIfNecessary(list, () => + { + list.RemoveAt(toRemoveIndex); + + Assert.That(() => list.RemoveAt(-1), Throws.TypeOf()); + Assert.That(() => list.RemoveAt(list.Count), Throws.TypeOf()); + }); + + Assert.That(list, Is.EquivalentTo(copyReferenceList)); + } + + public async Task AssertThreadSafeReference(IList list) + { + Assert.That(list, Is.TypeOf>()); + + var tsr = ThreadSafeReference.Create(list); + var originalThreadId = Environment.CurrentManagedThreadId; + + await Task.Run(() => + { + Assert.That(Environment.CurrentManagedThreadId, Is.Not.EqualTo(originalThreadId)); + + using (var bgRealm = Realm.GetInstance(list.AsRealmCollection().Realm.Config)) + { + var backgroundList = bgRealm.ResolveReference(tsr); + + for (var i = 0; i < backgroundList.Count; i++) + { + Assert.That(backgroundList[i], Is.EqualTo(referenceList[i])); + } + } + }); + } + + public void AssertNotifications(IList list) + { + if (!referenceList.Any()) + { + return; + } + + Assert.That(list, Is.TypeOf>()); + + var realm = list.AsRealmCollection().Realm; + + var changeSetList = new List(); + using var token = list.SubscribeForNotifications((collection, changes, error) => + { + Assert.That(error, Is.Null); + + if (changes != null) + { + changeSetList.Add(changes); + } + }); + + // Add + Seed(list); + + VerifyNotifications(realm, changeSetList, () => + { + Assert.That(changeSetList[0].InsertedIndices, Is.EquivalentTo(Enumerable.Range(0, referenceList.Count))); + }); + + // Insert + var toInsert = referenceList[TestHelpers.Random.Next(0, referenceList.Count)]; + + WriteIfNecessary(list, () => + { + list.Insert(0, toInsert); + list.Insert(list.Count, toInsert); + }); + + VerifyNotifications(realm, changeSetList, () => + { + Assert.That(changeSetList[0].InsertedIndices, Is.EquivalentTo(new[] { 0, list.Count - 1 })); + }); + + // Remove + realm.Write(() => + { + list.Remove(toInsert); + list.RemoveAt(list.Count - 1); + }); + + VerifyNotifications(realm, changeSetList, () => + { + Assert.That(changeSetList[0].DeletedIndices, Is.EquivalentTo(new[] { 0, list.Count + 1 })); + }); + + // Set + var indexToSet = TestHelpers.Random.Next(0, referenceList.Count); + var previousValue = list[indexToSet]; + var valueToSet = referenceList[TestHelpers.Random.Next(0, referenceList.Count)]; + + WriteIfNecessary(list, () => + { + list[indexToSet] = valueToSet; + }); + + VerifyNotifications(realm, changeSetList, () => + { + Assert.That(changeSetList[0].ModifiedIndices, Is.EquivalentTo(new[] { indexToSet })); + }); + + WriteIfNecessary(list, () => + { + list[indexToSet] = previousValue; + }); + + VerifyNotifications(realm, changeSetList, () => + { + Assert.That(changeSetList[0].ModifiedIndices, Is.EquivalentTo(new[] { indexToSet })); + }); + + // Move + var from = TestHelpers.Random.Next(0, list.Count); + var to = TestHelpers.Random.Next(0, list.Count); + + realm.Write(() => + { + list.Move(from, to); + }); + + if (from != to) + { + VerifyNotifications(realm, changeSetList, () => + { + Assert.That(changeSetList[0].Moves.Length, Is.EqualTo(1)); + var move = changeSetList[0].Moves[0]; + + // Moves may be reported with swapped from/to arguments if the elements are adjacent + if (move.From == to) + { + Assert.That(move.From, Is.EqualTo(to)); + Assert.That(move.To, Is.EqualTo(from)); + } + else + { + Assert.That(move.From, Is.EqualTo(from)); + Assert.That(move.To, Is.EqualTo(to)); + } + }); + } + + // Clear + realm.Write(() => + { + list.Clear(); + }); + + VerifyNotifications(realm, changeSetList, () => + { + Assert.That(changeSetList[0].DeletedIndices, Is.EquivalentTo(Enumerable.Range(0, referenceList.Count))); + }); + } + + private static void VerifyNotifications(Realm realm, List notifications, Action verifier) + { + realm.Refresh(); + Assert.That(notifications.Count, Is.EqualTo(1)); + verifier(); + notifications.Clear(); + } + + private static void WriteIfNecessary(IEnumerable collection, Action writeAction) + { + Transaction transaction = null; + try + { + if (collection is RealmCollectionBase realmCollection) + { + transaction = realmCollection.Realm.BeginWrite(); + } + + writeAction(); + + transaction?.Commit(); + } + catch + { + transaction?.Rollback(); + throw; + } + } } - #endregion Unmanaged Tests + #endregion } } diff --git a/Tests/Realm.Tests/Database/RealmDictionaryTests.cs b/Tests/Realm.Tests/Database/RealmDictionaryTests.cs index 49b708149f..343262c4cf 100644 --- a/Tests/Realm.Tests/Database/RealmDictionaryTests.cs +++ b/Tests/Realm.Tests/Database/RealmDictionaryTests.cs @@ -1072,6 +1072,46 @@ public void EmbeddedObject_Notifications() #endregion + #region RealmValue + + public static IEnumerable> RealmValueTestValues() + { + yield return new TestCaseData( + "sampleValue", + ("nullKey", RealmValue.Null), + ("intKey", RealmValue.Create(10, RealmValueType.Int)), + ("boolKey", RealmValue.Create(true, RealmValueType.Bool)), + ("stringKey", RealmValue.Create("abc", RealmValueType.String)), + ("dataKey", RealmValue.Create(new byte[] { 0, 1, 2 }, RealmValueType.Data)), + ("dateKey", RealmValue.Create(DateTimeOffset.FromUnixTimeSeconds(1616137641), RealmValueType.Date)), + ("floatKey", RealmValue.Create(1.5f, RealmValueType.Float)), + ("doubleKey", RealmValue.Create(2.5d, RealmValueType.Double)), + ("decimalKey", RealmValue.Create(5m, RealmValueType.Decimal128)), + ("objectIdKey", RealmValue.Create(new ObjectId("5f63e882536de46d71877979"), RealmValueType.ObjectId)), + ("guidKey", RealmValue.Create(new Guid("{F2952191-A847-41C3-8362-497F92CB7D24}"), RealmValueType.Guid)), + ("objectKey", RealmValue.Create(new IntPropertyObject { Int = 10 }, RealmValueType.Object))); + } + + [TestCaseSource(nameof(RealmValueTestValues))] + public void RealmValue_Unmanaged(TestCaseData testData) + { + RunUnmanagedTests(o => o.RealmValueDictionary, testData); + } + + [TestCaseSource(nameof(RealmValueTestValues))] + public void RealmValue_Managed(TestCaseData testData) + { + RunManagedTests(o => o.RealmValueDictionary, testData); + } + + [Test] + public void RealmValue_Notifications() + { + RunManagedNotificationsTests(o => o.RealmValueDictionary, RealmValueTestValues().Last()); + } + + #endregion + [Test] public void CanBeQueried() { diff --git a/Tests/Realm.Tests/Database/RealmResults/SimpleLINQtests.cs b/Tests/Realm.Tests/Database/RealmResults/SimpleLINQtests.cs index 7c87901ed2..03a18ee460 100644 --- a/Tests/Realm.Tests/Database/RealmResults/SimpleLINQtests.cs +++ b/Tests/Realm.Tests/Database/RealmResults/SimpleLINQtests.cs @@ -1351,4 +1351,4 @@ private class InstanceConstants public long SixtyThousandProperty { get; } = 60000; } } -} \ No newline at end of file +} diff --git a/Tests/Realm.Tests/Database/RealmSetTests.cs b/Tests/Realm.Tests/Database/RealmSetTests.cs index fb524a48f8..703200e17d 100644 --- a/Tests/Realm.Tests/Database/RealmSetTests.cs +++ b/Tests/Realm.Tests/Database/RealmSetTests.cs @@ -1162,6 +1162,92 @@ public void RealmSet_WhenManaged_Object_Notifications() #endregion + #region RealmValue + + public static IEnumerable> RealmTestValues() + { + var rv0 = RealmValue.Null; + var rv1 = RealmValue.Create(10, RealmValueType.Int); + var rv2 = RealmValue.Create(true, RealmValueType.Bool); + var rv3 = RealmValue.Create("abc", RealmValueType.String); + var rv4 = RealmValue.Create(new byte[] { 0, 1, 2 }, RealmValueType.Data); + var rv5 = RealmValue.Create(DateTimeOffset.FromUnixTimeSeconds(1616137641), RealmValueType.Date); + var rv6 = RealmValue.Create(1.5f, RealmValueType.Float); + var rv7 = RealmValue.Create(2.5d, RealmValueType.Double); + var rv8 = RealmValue.Create(5m, RealmValueType.Decimal128); + var rv9 = RealmValue.Create(new ObjectId("5f63e882536de46d71877979"), RealmValueType.ObjectId); + var rv10 = RealmValue.Create(new Guid("{F2952191-A847-41C3-8362-497F92CB7D24}"), RealmValueType.Guid); + var rv11 = GetRealmValueObject(); + + yield return new TestCaseData(new[] { rv0, rv1, rv2, rv3, rv4, rv5, rv6, rv7, rv8, rv9, rv10, rv11 }, new[] { rv0, rv1, rv2, rv3, rv4, rv5, rv6, rv7, rv8, rv9, rv10, rv11 }); + + rv11 = GetRealmValueObject(); + + yield return new TestCaseData(new[] { rv0, rv1, rv2, rv3, rv4, rv5 }, new[] { rv6, rv7, rv8, rv9, rv10, rv11 }); + + rv11 = GetRealmValueObject(); + + yield return new TestCaseData( Array.Empty(), new[] { rv0, rv1, rv2, rv3, rv4, rv5, rv6, rv7, rv8, rv9, rv10, rv11 }); + + static RealmValue GetRealmValueObject() => RealmValue.Create(new IntPropertyObject { Int = 10 }, RealmValueType.Object); + + var i1 = RealmValue.Create(1, RealmValueType.Int); + var i2 = RealmValue.Create(1d, RealmValueType.Double); + var i3 = RealmValue.Create(1f, RealmValueType.Float); + var i4 = RealmValue.Create(true, RealmValueType.Bool); + var i5 = RealmValue.Create(1m, RealmValueType.Decimal128); + + yield return new TestCaseData(new[] { i1, i2, i3, i4, i5 }, new[] { i1, i2, i3, i4, i5 }); + + var s1 = RealmValue.Create(string.Empty, RealmValueType.String); + var s2 = RealmValue.Create(0, RealmValueType.Int); + var s3 = RealmValue.Create(Guid.Empty, RealmValueType.Guid); + var s4 = RealmValue.Null; + + yield return new TestCaseData(new[] { s1, s2, s3, s4 }, new[] { s1, s2, s3, s4 }); + + var d1 = RealmValue.Create(1m, RealmValueType.Decimal128); + var d2 = RealmValue.Create(1f, RealmValueType.Decimal128); + var d3 = RealmValue.Create(1d, RealmValueType.Decimal128); + var d4 = RealmValue.Create(1, RealmValueType.Decimal128); + + yield return new TestCaseData(new[] { d1, d2, d3, d4 }, new[] { d1, d2, d3, d4 }); + } + + [TestCaseSource(nameof(RealmTestValues))] + public void RealmSet_WhenUnmanaged_RealmValue(TestCaseData testData) + { + RunUnmanagedTests(o => o.RealmValueSet, testData); + } + + [TestCaseSource(nameof(RealmTestValues))] + public void RealmSet_WhenManaged_RealmValue(TestCaseData testData) + { + RunManagedTests(o => o.RealmValueSet, o => o.RealmValueList, o => o.RealmValueDict, testData); + } + + [Test] + public void RealmSet_WhenManaged_RealmValue_Notifications() + { + var testData = new TestCaseData(new RealmValue[] { + RealmValue.Null, + RealmValue.Create(10, RealmValueType.Int), + RealmValue.Create(true, RealmValueType.Bool), + RealmValue.Create("abc", RealmValueType.String), + RealmValue.Create(new byte[] { 0, 1, 2 }, RealmValueType.Data), + RealmValue.Create(DateTimeOffset.FromUnixTimeSeconds(1616137641), RealmValueType.Date), + RealmValue.Create(1.5f, RealmValueType.Float), + RealmValue.Create(2.5d, RealmValueType.Double), + RealmValue.Create(5m, RealmValueType.Decimal128), + RealmValue.Create(new ObjectId("5f63e882536de46d71877979"), RealmValueType.ObjectId), + RealmValue.Create(new Guid("{F2952191-A847-41C3-8362-497F92CB7D24}"), RealmValueType.Guid), + RealmValue.Create(new IntPropertyObject { Int = 10 }, RealmValueType.Object) }); + + RunManagedNotificationsTests(o => o.RealmValueSet, testData, newValue: "newValue"); + } + + #endregion + private static void RunUnmanagedTests(Func> accessor, TestCaseData testData) { var testObject = new CollectionsObject(); diff --git a/Tests/Realm.Tests/Database/RealmValueTests.cs b/Tests/Realm.Tests/Database/RealmValueTests.cs new file mode 100644 index 0000000000..4be4111eb6 --- /dev/null +++ b/Tests/Realm.Tests/Database/RealmValueTests.cs @@ -0,0 +1,1094 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2021 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. +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using MongoDB.Bson; +using NUnit.Framework; + +namespace Realms.Tests.Database +{ + [TestFixture, Preserve(AllMembers = true)] + public class RealmValueTests : RealmInstanceTest + { + #region Primitive values + + #region TestCaseSources + + private static readonly char[] _charValues = new char[] { (char)0, 'a', char.MaxValue, char.MinValue }; + private static readonly byte[] _byteValues = new byte[] { 0, 1, byte.MaxValue, byte.MinValue }; + private static readonly int[] _intValues = new int[] { 0, 1, -1, int.MaxValue, int.MinValue }; + private static readonly short[] _shortValues = new short[] { 0, 1, -1, short.MaxValue, short.MinValue }; + private static readonly long[] _longValues = new long[] { 0, 1, -1, long.MaxValue, long.MinValue }; + private static readonly float[] _floatValues = new float[] { 0, 1, -1, float.MaxValue, float.MinValue }; + private static readonly double[] _doubleValues = new double[] { 0, 1, -1, float.MaxValue, float.MinValue }; + private static readonly Decimal128[] _decimal128Values = new Decimal128[] { 0, 1, -1, Decimal128.MaxValue, Decimal128.MinValue }; + private static readonly decimal[] _decimalValues = new decimal[] { 0, 1, -1, decimal.MaxValue, decimal.MinValue }; + private static readonly bool[] _boolValues = new bool[] { false, true }; + private static readonly DateTimeOffset[] _dateValues = new DateTimeOffset[] { DateTimeOffset.Now, DateTimeOffset.MaxValue, DateTimeOffset.MinValue }; + private static readonly Guid[] _guidValues = new Guid[] { Guid.NewGuid(), Guid.Empty }; + private static readonly ObjectId[] _objectIdValues = new ObjectId[] { ObjectId.GenerateNewId(), ObjectId.Empty }; + private static readonly string[] _stringValues = new string[] { "a", "abc", string.Empty }; + private static readonly byte[][] _dataValues = new byte[][] { new byte[] { 0, 1, 2 }, Array.Empty() }; + private static readonly RealmObject[] _objectValues = new RealmObject[] { new InternalObject { IntProperty = 10, StringProperty = "brown" } }; + + public static IEnumerable CharTestCases() => GenerateTestCases(_charValues); + + public static IEnumerable ByteTestCases() => GenerateTestCases(_byteValues); + + public static IEnumerable IntTestCases() => GenerateTestCases(_intValues); + + public static IEnumerable ShortTestCases() => GenerateTestCases(_shortValues); + + public static IEnumerable LongTestCases() => GenerateTestCases(_longValues); + + public static IEnumerable FloatTestCases() => GenerateTestCases(_floatValues); + + public static IEnumerable DoubleTestCases() => GenerateTestCases(_doubleValues); + + public static IEnumerable Decimal128TestCases() => GenerateTestCases(_decimal128Values); + + public static IEnumerable DecimalTestCases() => GenerateTestCases(_decimalValues); + + public static IEnumerable BoolTestCases() => GenerateTestCases(_boolValues); + + public static IEnumerable DateTestCases() => GenerateTestCases(_dateValues); + + public static IEnumerable GuidTestCases() => GenerateTestCases(_guidValues); + + public static IEnumerable ObjectIdTestCases() => GenerateTestCases(_objectIdValues); + + public static IEnumerable StringTestCases() => GenerateTestCases(_stringValues); + + public static IEnumerable DataTestCases() => GenerateTestCases(_dataValues); + + public static IEnumerable ObjectTestCases() => GenerateTestCases(_objectValues); + + private static IEnumerable GenerateTestCases(IEnumerable values) + { + foreach (var val in values) + { + yield return new object[] { val, false }; + yield return new object[] { val, true }; + } + } + + #endregion + + [TestCaseSource(nameof(CharTestCases))] + public void CharTests(char value, bool isManaged) + { + RunNumericTests(value, value, isManaged); + } + + [TestCaseSource(nameof(ByteTestCases))] + public void ByteTests(byte value, bool isManaged) + { + RunNumericTests(value, value, isManaged); + } + + [TestCaseSource(nameof(IntTestCases))] + public void IntTests(int value, bool isManaged) + { + RunNumericTests(value, value, isManaged); + } + + [TestCaseSource(nameof(ShortTestCases))] + public void ShortTests(short value, bool isManaged) + { + RunNumericTests(value, value, isManaged); + } + + [TestCaseSource(nameof(LongTestCases))] + public void LongTests(long value, bool isManaged) + { + RunNumericTests(value, value, isManaged); + } + + public void RunNumericTests(RealmValue rv, long value, bool isManaged) + { + if (isManaged) + { + var retrievedObject = PersistAndFind(rv); + rv = retrievedObject.RealmValueProperty; + } + + Assert.That(rv == value); + Assert.That(rv.Type, Is.EqualTo(RealmValueType.Int)); + Assert.That(rv != RealmValue.Null); + + // 8 - byte + var byteValue = (byte)value; + Assert.That((byte)rv == byteValue); + Assert.That(rv.As() == byteValue); + Assert.That((byte?)rv == byteValue); + Assert.That(rv.As() == byteValue); + Assert.That(rv.AsByte() == byteValue); + Assert.That(rv.AsNullableByte() == byteValue); + Assert.That(rv.AsByteRealmInteger() == byteValue); + Assert.That(rv.AsNullableByteRealmInteger() == byteValue); + + // 16 - short + var shortValue = (short)value; + Assert.That((short)rv == shortValue); + Assert.That(rv.As() == shortValue); + Assert.That((short?)rv == shortValue); + Assert.That(rv.As() == shortValue); + Assert.That(rv.AsInt16() == shortValue); + Assert.That(rv.AsNullableInt16() == shortValue); + Assert.That(rv.AsInt16RealmInteger() == shortValue); + Assert.That(rv.AsNullableInt16RealmInteger() == shortValue); + + // 32 - int + var intValue = (int)value; + Assert.That((int)rv == intValue); + Assert.That(rv.As() == intValue); + Assert.That((int?)rv == intValue); + Assert.That(rv.As() == intValue); + Assert.That(rv.AsInt32() == intValue); + Assert.That(rv.AsNullableInt32() == intValue); + Assert.That(rv.AsInt32RealmInteger() == intValue); + Assert.That(rv.AsNullableInt32RealmInteger() == intValue); + + // 64 - long + Assert.That((long)rv == value); + Assert.That(rv.As() == value); + Assert.That((long?)rv == value); + Assert.That(rv.As() == value); + Assert.That(rv.AsInt64() == value); + Assert.That(rv.AsNullableInt64() == value); + Assert.That(rv.AsInt64RealmInteger() == value); + Assert.That(rv.AsNullableInt64RealmInteger() == value); + } + + [TestCaseSource(nameof(FloatTestCases))] + public void FloatTests(float value, bool isManaged) + { + RealmValue rv = value; + + if (isManaged) + { + var retrievedObject = PersistAndFind(rv); + rv = retrievedObject.RealmValueProperty; + } + + Assert.That(rv == value); + Assert.That(rv.Type, Is.EqualTo(RealmValueType.Float)); + + Assert.That((float)rv == value); + Assert.That(rv.As() == value); + Assert.That((float?)rv == value); + Assert.That(rv.As() == value); + Assert.That(rv.AsFloat() == value); + Assert.That(rv.AsNullableFloat() == value); + Assert.That(rv != RealmValue.Null); + } + + [TestCaseSource(nameof(DoubleTestCases))] + public void DoubleTests(double value, bool isManaged) + { + RealmValue rv = value; + + if (isManaged) + { + var retrievedObject = PersistAndFind(rv); + rv = retrievedObject.RealmValueProperty; + } + + Assert.That(rv == value); + Assert.That(rv.Type, Is.EqualTo(RealmValueType.Double)); + + Assert.That((double)rv == value); + Assert.That(rv.As() == value); + Assert.That((double?)rv == value); + Assert.That(rv.As() == value); + Assert.That(rv.AsDouble() == value); + Assert.That(rv.AsNullableDouble() == value); + Assert.That(rv != RealmValue.Null); + } + + [TestCaseSource(nameof(Decimal128TestCases))] + public void Decimal128Tests(Decimal128 value, bool isManaged) + { + RealmValue rv = value; + + if (isManaged) + { + var retrievedObject = PersistAndFind(rv); + rv = retrievedObject.RealmValueProperty; + } + + Assert.That(rv == value); + Assert.That(rv.Type, Is.EqualTo(RealmValueType.Decimal128)); + + Assert.That((Decimal128)rv == value); + Assert.That(rv.As() == value); + Assert.That((Decimal128?)rv == value); + Assert.That(rv.As() == value); + Assert.That(rv.AsDecimal128() == value); + Assert.That(rv.AsNullableDecimal128() == value); + Assert.That(rv != RealmValue.Null); + } + + [TestCaseSource(nameof(DecimalTestCases))] + public void DecimalTests(decimal value, bool isManaged) + { + RealmValue rv = value; + + if (isManaged) + { + var retrievedObject = PersistAndFind(rv); + rv = retrievedObject.RealmValueProperty; + } + + Assert.That(rv == value); + Assert.That(rv.Type, Is.EqualTo(RealmValueType.Decimal128)); + + Assert.That((decimal)rv == value); + Assert.That(rv.As() == value); + Assert.That((decimal?)rv == value); + Assert.That(rv.As() == value); + Assert.That(rv.AsDecimal() == value); + Assert.That(rv.AsNullableDecimal() == value); + Assert.That(rv != RealmValue.Null); + } + + [TestCaseSource(nameof(BoolTestCases))] + public void BoolTests(bool value, bool isManaged) + { + RealmValue rv = value; + + if (isManaged) + { + var retrievedObject = PersistAndFind(rv); + rv = retrievedObject.RealmValueProperty; + } + + Assert.That(rv == value); + Assert.That(rv.Type, Is.EqualTo(RealmValueType.Bool)); + + Assert.That((bool)rv == value); + Assert.That(rv.As() == value); + Assert.That((bool?)rv == value); + Assert.That(rv.As() == value); + Assert.That(rv.AsBool() == value); + Assert.That(rv.AsNullableBool() == value); + Assert.That(rv != RealmValue.Null); + } + + [TestCaseSource(nameof(DateTestCases))] + public void DateTests(DateTimeOffset value, bool isManaged) + { + RealmValue rv = value; + + if (isManaged) + { + var retrievedObject = PersistAndFind(rv); + rv = retrievedObject.RealmValueProperty; + } + + Assert.That(rv == value); + Assert.That(rv.Type, Is.EqualTo(RealmValueType.Date)); + + Assert.That((DateTimeOffset)rv == value); + Assert.That(rv.As() == value); + Assert.That((DateTimeOffset?)rv == value); + Assert.That(rv.As() == value); + Assert.That(rv.AsDate() == value); + Assert.That(rv.AsNullableDate() == value); + Assert.That(rv != RealmValue.Null); + } + + [TestCaseSource(nameof(ObjectIdTestCases))] + public void ObjectIdTests(ObjectId value, bool isManaged) + { + RealmValue rv = value; + + if (isManaged) + { + var retrievedObject = PersistAndFind(rv); + rv = retrievedObject.RealmValueProperty; + } + + Assert.That(rv == value); + Assert.That(rv.Type, Is.EqualTo(RealmValueType.ObjectId)); + + Assert.That((ObjectId)rv == value); + Assert.That(rv.As() == value); + Assert.That((ObjectId?)rv == value); + Assert.That(rv.As() == value); + Assert.That(rv.AsObjectId() == value); + Assert.That(rv.AsNullableObjectId() == value); + Assert.That(rv != RealmValue.Null); + } + + [TestCaseSource(nameof(GuidTestCases))] + public void GuidTests(Guid value, bool isManaged) + { + RealmValue rv = value; + + if (isManaged) + { + var retrievedObject = PersistAndFind(rv); + rv = retrievedObject.RealmValueProperty; + } + + Assert.That(rv == value); + Assert.That(rv.Type, Is.EqualTo(RealmValueType.Guid)); + + Assert.That((Guid)rv == value); + Assert.That(rv.As() == value); + Assert.That((Guid?)rv == value); + Assert.That(rv.As() == value); + Assert.That(rv.AsGuid() == value); + Assert.That(rv.AsNullableGuid() == value); + Assert.That(rv != RealmValue.Null); + } + + [TestCaseSource(nameof(StringTestCases))] + public void StringTests(string value, bool isManaged) + { + RealmValue rv = value; + + if (isManaged) + { + var retrievedObject = PersistAndFind(rv); + rv = retrievedObject.RealmValueProperty; + } + + Assert.That(rv == value); + Assert.That(rv.Type, Is.EqualTo(RealmValueType.String)); + + Assert.That((string)rv == value); + Assert.That(rv.As() == value); + Assert.That(rv.AsString() == value); + Assert.That(rv != RealmValue.Null); + } + + [TestCaseSource(nameof(DataTestCases))] + public void DataTests(byte[] value, bool isManaged) + { + RealmValue rv = value; + + if (isManaged) + { + var retrievedObject = PersistAndFind(rv); + rv = retrievedObject.RealmValueProperty; + } + + Assert.That(rv.Type, Is.EqualTo(RealmValueType.Data)); + + Assert.That((byte[])rv, Is.EqualTo(value)); + Assert.That(rv.As(), Is.EqualTo(value)); + Assert.That(rv.AsData(), Is.EqualTo(value)); + Assert.That(rv != RealmValue.Null); + } + + [TestCaseSource(nameof(ObjectTestCases))] + public void ObjectTests(RealmObjectBase value, bool isManaged) + { + RealmValue rv = value; + + if (isManaged) + { + var retrievedObject = PersistAndFind(rv); + rv = retrievedObject.RealmValueProperty; + } + + Assert.That(rv.Type, Is.EqualTo(RealmValueType.Object)); + + Assert.That((RealmObjectBase)rv, Is.EqualTo(value)); + Assert.That(rv.As(), Is.EqualTo(value)); + Assert.That(rv.AsRealmObject(), Is.EqualTo(value)); + Assert.That(rv != RealmValue.Null); + } + + [TestCase(true)] + [TestCase(false)] + public void NullTests(bool isManaged) + { + RealmValue rv = RealmValue.Null; + + if (isManaged) + { + var retrievedObject = PersistAndFind(rv); + rv = retrievedObject.RealmValueProperty; + } + + Assert.That(rv == RealmValue.Null); + Assert.That(rv.Type, Is.EqualTo(RealmValueType.Null)); + + Assert.That(rv.AsNullableBool() == null); + Assert.That(rv.AsNullableChar() == null); + Assert.That(rv.AsNullableDate() == null); + Assert.That(rv.AsNullableDecimal() == null); + Assert.That(rv.AsNullableDecimal128() == null); + Assert.That(rv.AsNullableDouble() == null); + Assert.That(rv.AsNullableFloat() == null); + Assert.That(rv.AsNullableGuid() == null); + Assert.That(rv.AsNullableObjectId() == null); + Assert.That(rv.AsNullableByte() == null); + Assert.That(rv.AsNullableByteRealmInteger() == null); + Assert.That(rv.AsNullableInt16() == null); + Assert.That(rv.AsNullableInt16RealmInteger() == null); + Assert.That(rv.AsNullableInt32() == null); + Assert.That(rv.AsNullableInt32RealmInteger() == null); + Assert.That(rv.AsNullableInt64() == null); + Assert.That(rv.AsNullableInt64RealmInteger() == null); + + Assert.That((bool?)rv == null); + Assert.That((DateTimeOffset?)rv == null); + Assert.That((decimal?)rv == null); + Assert.That((Decimal128?)rv == null); + Assert.That((double?)rv == null); + Assert.That((float?)rv == null); + Assert.That((Guid?)rv == null); + Assert.That((ObjectId?)rv == null); + Assert.That((byte?)rv == null); + Assert.That((RealmInteger?)rv == null); + Assert.That((short?)rv == null); + Assert.That((RealmInteger?)rv == null); + Assert.That((int?)rv == null); + Assert.That((RealmInteger?)rv == null); + Assert.That((long?)rv == null); + Assert.That((RealmInteger?)rv == null); + } + + [Test] + public void RealmValue_WhenManaged_ObjectType() + { + RealmValue rv = new InternalObject { IntProperty = 10, StringProperty = "brown" }; + var managedObject = PersistAndFind(rv); + + Assert.That(managedObject.RealmValueProperty.ObjectType, Is.EqualTo(nameof(InternalObject))); + + _realm.Write(() => + { + managedObject.RealmValueProperty = "string"; + }); + + Assert.That(managedObject.RealmValueProperty.ObjectType, Is.Null); + } + + [Test] + public void RealmValue_WhenUnmanaged_ObjectType() + { + RealmValue rv = new InternalObject { IntProperty = 10, StringProperty = "brown" }; + var managedObject = new RealmValueObject { RealmValueProperty = rv }; + + Assert.That(managedObject.RealmValueProperty.ObjectType, Is.EqualTo(nameof(InternalObject))); + + managedObject.RealmValueProperty = "string"; + + Assert.That(managedObject.RealmValueProperty.ObjectType, Is.Null); + } + + [Test] + public void RealmValue_WhenRealmInteger_Increments() + { + RealmValue rv = 10; + var retrievedObject = PersistAndFind(rv); + + Assert.That(retrievedObject.RealmValueProperty.AsInt32() == 10); + + _realm.Write(() => + { + retrievedObject.RealmValueProperty.AsInt32RealmInteger().Increment(); + }); + + Assert.That(retrievedObject.RealmValueProperty.AsInt32() == 11); + + _realm.Write(() => + { + retrievedObject.RealmValueProperty.AsInt32RealmInteger().Decrement(); + }); + + Assert.That(retrievedObject.RealmValueProperty.AsInt32() == 10); + } + + [Test] + public void RealmValue_WhenCastingIsWrong_ThrowsException() + { + RealmValue rv = 10; + + Assert.That(() => rv.AsString(), Throws.Exception.TypeOf()); + Assert.That(() => rv.AsFloat(), Throws.Exception.TypeOf()); + + rv = Guid.NewGuid().ToString(); + + Assert.That(() => rv.AsInt16(), Throws.Exception.TypeOf()); + Assert.That(() => rv.AsGuid(), Throws.Exception.TypeOf()); + + rv = true; + + Assert.That(() => rv.AsInt16(), Throws.Exception.TypeOf()); + } + + [Test] + public void RealmValue_Reference_IsChangedCorrectly() + { + var rvo = new RealmValueObject(); + + rvo.RealmValueProperty = 10; + + _realm.Write(() => + { + _realm.Add(rvo); + }); + + var savedValue = rvo.RealmValueProperty; + + _realm.Write(() => + { + rvo.RealmValueProperty = "abc"; + }); + + Assert.That(rvo.RealmValueProperty != savedValue); + Assert.That(savedValue == 10); + } + + [Test] + public void RealmValue_WhenManaged_CanChangeType() + { + var rvo = new RealmValueObject(); + + rvo.RealmValueProperty = 10; + + _realm.Write(() => + { + _realm.Add(rvo); + }); + + Assert.That(rvo.RealmValueProperty == 10); + + _realm.Write(() => + { + rvo.RealmValueProperty = "abc"; + }); + + Assert.That(rvo.RealmValueProperty == "abc"); + + var guidValue = Guid.NewGuid(); + + _realm.Write(() => + { + rvo.RealmValueProperty = guidValue; + }); + + Assert.That(rvo.RealmValueProperty == guidValue); + + _realm.Write(() => + { + rvo.RealmValueProperty = RealmValue.Null; + }); + + Assert.That(rvo.RealmValueProperty == RealmValue.Null); + } + + [Test] + public void RealmValue_WhenManaged_NotificationTests() + { + var notifiedPropertyNames = new List(); + + var handler = new PropertyChangedEventHandler((sender, e) => + { + notifiedPropertyNames.Add(e.PropertyName); + }); + + var rvo = new RealmValueObject(); + + _realm.Write(() => + { + _realm.Add(rvo); + }); + + rvo.PropertyChanged += handler; + + _realm.Write(() => + { + rvo.RealmValueProperty = "abc"; + }); + + _realm.Refresh(); + + Assert.That(notifiedPropertyNames, Is.EquivalentTo(new[] { nameof(RealmValueObject.RealmValueProperty) })); + + _realm.Write(() => + { + rvo.RealmValueProperty = 10; + }); + + _realm.Refresh(); + + Assert.That(notifiedPropertyNames, Is.EquivalentTo(new[] + { + nameof(RealmValueObject.RealmValueProperty), + nameof(RealmValueObject.RealmValueProperty) + })); + } + + [TestCase(1, true)] + [TestCase(1, false)] + [TestCase(0, true)] + [TestCase(0, false)] + public void RealmValue_WhenManaged_BoolNotificationTests(int intValue, bool boolValue) + { + var notifiedPropertyNames = new List(); + + var handler = new PropertyChangedEventHandler((sender, e) => + { + notifiedPropertyNames.Add(e.PropertyName); + }); + + var rvo = new RealmValueObject(); + + _realm.Write(() => + { + _realm.Add(rvo); + }); + + rvo.PropertyChanged += handler; + + _realm.Write(() => + { + rvo.RealmValueProperty = intValue; + }); + + _realm.Refresh(); + + Assert.That(notifiedPropertyNames, Is.EquivalentTo(new[] { nameof(RealmValueObject.RealmValueProperty) })); + + _realm.Write(() => + { + rvo.RealmValueProperty = boolValue; + }); + + _realm.Refresh(); + + Assert.That(notifiedPropertyNames, Is.EquivalentTo(new[] + { + nameof(RealmValueObject.RealmValueProperty), + nameof(RealmValueObject.RealmValueProperty) + })); + + _realm.Write(() => + { + rvo.RealmValueProperty = intValue; + }); + + _realm.Refresh(); + + Assert.That(notifiedPropertyNames, Is.EquivalentTo(new[] + { + nameof(RealmValueObject.RealmValueProperty), + nameof(RealmValueObject.RealmValueProperty), + nameof(RealmValueObject.RealmValueProperty) + })); + } + + [Test] + public void RealmValue_WhenManaged_ObjectGetsPersisted() + { + var value = new InternalObject { IntProperty = 10, StringProperty = "brown" }; + RealmValue rv = value; + + _realm.Write(() => + { + _realm.Add(new RealmValueObject { RealmValueProperty = rv }); + }); + + var objs = _realm.All().ToList(); + + Assert.That(objs.Count, Is.EqualTo(1)); + Assert.That(objs[0], Is.EqualTo(value)); + } + + [Test] + public void RealmValueProperty_WhenObjectIsNotInSchema_ReturnsDynamic() + { + _realm.Write(() => + { + _realm.Add(new RealmValueObject { RealmValueProperty = new InternalObject { IntProperty = 10, StringProperty = "brown" } }); + }); + + _realm.Dispose(); + + var config = _configuration.ConfigWithPath(_configuration.DatabasePath); + config.ObjectClasses = new[] { typeof(RealmValueObject) }; + + using var singleSchemaRealm = GetRealm(config); + + var rvo = singleSchemaRealm.All().First(); + var rv = rvo.RealmValueProperty; + + Assert.That(rv.Type, Is.EqualTo(RealmValueType.Object)); + dynamic d = rv.AsRealmObject(); + + Assert.That(d.IntProperty, Is.EqualTo(10)); + Assert.That(d.StringProperty, Is.EqualTo("brown")); + } + + [Test] + public void RealmValueList_WhenObjectIsNotInSchema_ReturnsDynamic() + { + _realm.Write(() => + { + var obj = _realm.Add(new RealmValueObject()); + obj.RealmValueList.Add(new InternalObject { IntProperty = 10, StringProperty = "brown" }); + }); + + _realm.Dispose(); + + var config = _configuration.ConfigWithPath(_configuration.DatabasePath); + config.ObjectClasses = new[] { typeof(RealmValueObject) }; + + using var singleSchemaRealm = GetRealm(config); + + var rvo = singleSchemaRealm.All().First(); + var rv = rvo.RealmValueList.Single(); + + Assert.That(rv.Type, Is.EqualTo(RealmValueType.Object)); + dynamic d = rv.AsRealmObject(); + + Assert.That(d.IntProperty, Is.EqualTo(10)); + Assert.That(d.StringProperty, Is.EqualTo("brown")); + } + + [Test] + public void RealmValueSet_WhenObjectIsNotInSchema_ReturnsDynamic() + { + _realm.Write(() => + { + var obj = _realm.Add(new RealmValueObject()); + obj.RealmValueSet.Add(new InternalObject { IntProperty = 10, StringProperty = "brown" }); + }); + + _realm.Dispose(); + + var config = _configuration.ConfigWithPath(_configuration.DatabasePath); + config.ObjectClasses = new[] { typeof(RealmValueObject) }; + + using var singleSchemaRealm = GetRealm(config); + + var rvo = singleSchemaRealm.All().First(); + var rv = rvo.RealmValueSet.Single(); + + Assert.That(rv.Type, Is.EqualTo(RealmValueType.Object)); + dynamic d = rv.AsRealmObject(); + + Assert.That(d.IntProperty, Is.EqualTo(10)); + Assert.That(d.StringProperty, Is.EqualTo("brown")); + } + + [Test] + public void RealmValueDictionary_WhenObjectIsNotInSchema_ReturnsDynamic() + { + _realm.Write(() => + { + var obj = _realm.Add(new RealmValueObject()); + obj.RealmValueDictionary.Add("foo", new InternalObject { IntProperty = 10, StringProperty = "brown" }); + }); + + _realm.Dispose(); + + var config = _configuration.ConfigWithPath(_configuration.DatabasePath); + config.ObjectClasses = new[] { typeof(RealmValueObject) }; + + using var singleSchemaRealm = GetRealm(config); + + var rvo = singleSchemaRealm.All().First(); + var rv = rvo.RealmValueDictionary["foo"]; + + Assert.That(rv.Type, Is.EqualTo(RealmValueType.Object)); + dynamic d = rv.AsRealmObject(); + + Assert.That(d.IntProperty, Is.EqualTo(10)); + Assert.That(d.StringProperty, Is.EqualTo("brown")); + } + + [Test] + public void RealmValueResults_WhenObjectIsNotInSchema_ReturnsDynamic() + { + _realm.Write(() => + { + var obj = _realm.Add(new RealmValueObject()); + obj.RealmValueDictionary.Add("foo", new InternalObject { IntProperty = 10, StringProperty = "brown" }); + }); + + _realm.Dispose(); + + var config = _configuration.ConfigWithPath(_configuration.DatabasePath); + config.ObjectClasses = new[] { typeof(RealmValueObject) }; + + using var singleSchemaRealm = GetRealm(config); + + var rvo = singleSchemaRealm.All().First(); + + // A bit hacky - we take advantage of the fact Values is Results + Assert.That(rvo.RealmValueDictionary.Values, Is.TypeOf>()); + var rv = rvo.RealmValueDictionary.Values.Single(); + + Assert.That(rv.Type, Is.EqualTo(RealmValueType.Object)); + dynamic d = rv.AsRealmObject(); + + Assert.That(d.IntProperty, Is.EqualTo(10)); + Assert.That(d.StringProperty, Is.EqualTo("brown")); + } + + #endregion + + #region Queries + + public static IEnumerable QueryTestValues() + { + yield return new RealmValue[] + { + RealmValue.Null, + RealmValue.Create(10, RealmValueType.Int), + RealmValue.Create(true, RealmValueType.Bool), + RealmValue.Create("abc", RealmValueType.String), + RealmValue.Create(new byte[] { 0, 1, 2 }, RealmValueType.Data), + RealmValue.Create(DateTimeOffset.FromUnixTimeSeconds(1616137641), RealmValueType.Date), + RealmValue.Create(1.5f, RealmValueType.Float), + RealmValue.Create(2.5d, RealmValueType.Double), + RealmValue.Create(5m, RealmValueType.Decimal128), + RealmValue.Create(new ObjectId("5f63e882536de46d71877979"), RealmValueType.ObjectId), + RealmValue.Create(new Guid("{F2952191-A847-41C3-8362-497F92CB7D24}"), RealmValueType.Guid), + RealmValue.Create(new InternalObject { IntProperty = 10, StringProperty = "brown" }, RealmValueType.Object), + }; + } + + [TestCaseSource(nameof(QueryTestValues))] + public void Query_Generic(RealmValue[] realmValues) + { + var rvObjects = realmValues.Select((rv, index) => new RealmValueObject { Id = index, RealmValueProperty = rv }).ToList(); + + _realm.Write(() => + { + _realm.Add(rvObjects); + }); + + foreach (var realmValue in realmValues) + { + // Equality + var referenceResult = rvObjects.Where(r => r.RealmValueProperty == realmValue).OrderBy(r => r.Id).ToList(); + + var q = _realm.All().Where(r => r.RealmValueProperty == realmValue).OrderBy(r => r.Id).ToList(); + var f = _realm.All().Filter($"RealmValueProperty == $0", realmValue).OrderBy(r => r.Id).ToList(); + + Assert.That(q, Is.EquivalentTo(referenceResult), $"{realmValue.Type}"); + Assert.That(f, Is.EquivalentTo(referenceResult)); + + // Non-Equality + referenceResult = rvObjects.Where(r => r.RealmValueProperty != realmValue).OrderBy(r => r.Id).ToList(); + + q = _realm.All().Where(r => r.RealmValueProperty != realmValue).OrderBy(r => r.Id).ToList(); + f = _realm.All().Filter($"RealmValueProperty != $0", realmValue).OrderBy(r => r.Id).ToList(); + + Assert.That(q, Is.EquivalentTo(referenceResult)); + Assert.That(f, Is.EquivalentTo(referenceResult)); + } + + foreach (RealmValueType type in Enum.GetValues(typeof(RealmValueType))) + { + // Equality on RealmValueType + var referenceResult = rvObjects.Where(r => r.RealmValueProperty.Type == type).OrderBy(r => r.Id).ToList(); + + var q = _realm.All().Where(r => r.RealmValueProperty.Type == type).OrderBy(r => r.Id).ToList(); + var f = _realm.All().Filter($"RealmValueProperty.@type == '{ConvertRealmValueTypeToFilterAttribute(type)}'").OrderBy(r => r.Id).ToList(); + + Assert.That(q, Is.EquivalentTo(referenceResult)); + Assert.That(f, Is.EquivalentTo(referenceResult)); + + // Non-Equality on RealmValueType + referenceResult = rvObjects.Where(r => r.RealmValueProperty.Type != type).OrderBy(r => r.Id).ToList(); + + q = _realm.All().Where(r => r.RealmValueProperty.Type != type).OrderBy(r => r.Id).ToList(); + f = _realm.All().Filter($"RealmValueProperty.@type != '{ConvertRealmValueTypeToFilterAttribute(type)}'").OrderBy(r => r.Id).ToList(); + + Assert.That(q, Is.EquivalentTo(referenceResult)); + Assert.That(f, Is.EquivalentTo(referenceResult)); + } + } + + [Test] + public void Query_Numeric() + { + var rvo1 = new RealmValueObject { Id = 1, RealmValueProperty = 1 }; + var rvo2 = new RealmValueObject { Id = 2, RealmValueProperty = 1.0f }; + var rvo3 = new RealmValueObject { Id = 3, RealmValueProperty = 1.0d }; + var rvo4 = new RealmValueObject { Id = 4, RealmValueProperty = 1.0m }; + var rvo5 = new RealmValueObject { Id = 5, RealmValueProperty = 1.1 }; + var rvo6 = new RealmValueObject { Id = 6, RealmValueProperty = true }; + var rvo7 = new RealmValueObject { Id = 7, RealmValueProperty = "1" }; + + _realm.Write(() => + { + _realm.Add(new[] { rvo1, rvo2, rvo3, rvo4, rvo5, rvo6, rvo7 }); + }); + + // Numeric values are converted when possible + var n1 = _realm.All().Where(r => r.RealmValueProperty == 1).OrderBy(r => r.Id).ToList(); + var n2 = _realm.All().Where(r => r.RealmValueProperty == 1.0f).OrderBy(r => r.Id).ToList(); + var n3 = _realm.All().Where(r => r.RealmValueProperty == 1.0d).OrderBy(r => r.Id).ToList(); + var n4 = _realm.All().Where(r => r.RealmValueProperty == 1.0m).OrderBy(r => r.Id).ToList(); + var n5 = _realm.All().Where(r => r.RealmValueProperty == 1.1d).OrderBy(r => r.Id).ToList(); + + Assert.That(n1, Is.EquivalentTo(n2)); + Assert.That(n1, Is.EquivalentTo(n3)); + Assert.That(n1, Is.EquivalentTo(n4)); + Assert.That(n1, Is.EquivalentTo(new[] { rvo1, rvo2, rvo3, rvo4 })); + Assert.That(n1, Is.Not.EquivalentTo(n5)); + Assert.That(n5, Is.EquivalentTo(new[] { rvo5 })); + + // Bool values are not compared with numbers + var b1 = _realm.All().Where(r => r.RealmValueProperty == true).OrderBy(r => r.Id).ToList(); + Assert.That(b1, Is.EquivalentTo(new List { rvo6 })); + + // String values are not compared with numbers + var s1 = _realm.All().Where(r => r.RealmValueProperty == "1").OrderBy(r => r.Id).ToList(); + Assert.That(s1, Is.EquivalentTo(new List { rvo7 })); + + // Types are correctly assessed + var t1 = _realm.All().Where(r => r.RealmValueProperty.Type == RealmValueType.Int).OrderBy(r => r.Id).ToList(); + var t2 = _realm.All().Where(r => r.RealmValueProperty.Type == RealmValueType.Float).OrderBy(r => r.Id).ToList(); + var t3 = _realm.All().Where(r => r.RealmValueProperty.Type == RealmValueType.Double).OrderBy(r => r.Id).ToList(); + var t4 = _realm.All().Where(r => r.RealmValueProperty.Type == RealmValueType.Decimal128).OrderBy(r => r.Id).ToList(); + var t5 = _realm.All().Where(r => r.RealmValueProperty.Type == RealmValueType.Bool).OrderBy(r => r.Id).ToList(); + var t6 = _realm.All().Where(r => r.RealmValueProperty.Type == RealmValueType.String).OrderBy(r => r.Id).ToList(); + + var f1 = _realm.All().Filter("RealmValueProperty.@type == 'int'").OrderBy(r => r.Id).ToList(); + var f2 = _realm.All().Filter("RealmValueProperty.@type == 'float'").OrderBy(r => r.Id).ToList(); + var f3 = _realm.All().Filter("RealmValueProperty.@type == 'double'").OrderBy(r => r.Id).ToList(); + var f4 = _realm.All().Filter("RealmValueProperty.@type == 'decimal'").OrderBy(r => r.Id).ToList(); + var f5 = _realm.All().Filter("RealmValueProperty.@type == 'bool'").OrderBy(r => r.Id).ToList(); + var f6 = _realm.All().Filter("RealmValueProperty.@type == 'string'").OrderBy(r => r.Id).ToList(); + + Assert.That(t1, Is.EquivalentTo(new[] { rvo1 })); + Assert.That(t2, Is.EquivalentTo(new[] { rvo2 })); + Assert.That(t3, Is.EquivalentTo(new[] { rvo3, rvo5 })); + Assert.That(t4, Is.EquivalentTo(new[] { rvo4 })); + Assert.That(t5, Is.EquivalentTo(new[] { rvo6 })); + Assert.That(t6, Is.EquivalentTo(new[] { rvo7 })); + + Assert.That(f1, Is.EquivalentTo(t1)); + Assert.That(f2, Is.EquivalentTo(t2)); + Assert.That(f3, Is.EquivalentTo(t3)); + Assert.That(f4, Is.EquivalentTo(t4)); + Assert.That(f5, Is.EquivalentTo(t5)); + Assert.That(f6, Is.EquivalentTo(t6)); + } + + [Test] + public void Query_Filter() + { + var rvo1 = new RealmValueObject { Id = 1, RealmValueProperty = 11 }; + var rvo2 = new RealmValueObject { Id = 2, RealmValueProperty = 15.0f }; + var rvo3 = new RealmValueObject { Id = 3, RealmValueProperty = 15.0d }; + var rvo4 = new RealmValueObject { Id = 4, RealmValueProperty = 31.0m }; + var rvo5 = new RealmValueObject { Id = 5, RealmValueProperty = DateTimeOffset.Now.AddDays(-1) }; + var rvo6 = new RealmValueObject { Id = 6, RealmValueProperty = DateTimeOffset.Now.AddDays(1) }; + var rvo7 = new RealmValueObject { Id = 7, RealmValueProperty = "42" }; + + _realm.Write(() => + { + _realm.Add(new[] { rvo1, rvo2, rvo3, rvo4, rvo5, rvo6, rvo7 }); + }); + + var f1 = _realm.All().Filter("RealmValueProperty > 20").OrderBy(r => r.Id).ToList(); + Assert.That(f1, Is.EquivalentTo(new[] { rvo4 })); + + var f2 = _realm.All().Filter("RealmValueProperty < 20").OrderBy(r => r.Id).ToList(); + Assert.That(f2, Is.EquivalentTo(new[] { rvo1, rvo2, rvo3 })); + + var f3 = _realm.All().Filter("RealmValueProperty >= 15").OrderBy(r => r.Id).ToList(); + Assert.That(f3, Is.EquivalentTo(new[] { rvo2, rvo3, rvo4 })); + + var f4 = _realm.All().Filter("RealmValueProperty <= 15").OrderBy(r => r.Id).ToList(); + Assert.That(f4, Is.EquivalentTo(new[] { rvo1, rvo2, rvo3 })); + + var f5 = _realm.All().Filter("RealmValueProperty < $0", DateTimeOffset.Now).OrderBy(r => r.Id).ToList(); + Assert.That(f5, Is.EquivalentTo(new[] { rvo5 })); + + var f6 = _realm.All().Filter("RealmValueProperty > $0", DateTimeOffset.Now).OrderBy(r => r.Id).ToList(); + Assert.That(f6, Is.EquivalentTo(new[] { rvo6 })); + } + + #endregion + + private RealmValueObject PersistAndFind(RealmValue rv) + { + _realm.Write(() => + { + _realm.Add(new RealmValueObject { RealmValueProperty = rv }); + }); + + return _realm.All().First(); + } + + private static string ConvertRealmValueTypeToFilterAttribute(RealmValueType rvt) + { + return rvt switch + { + RealmValueType.Null => "null", + RealmValueType.Int => "int", + RealmValueType.Bool => "bool", + RealmValueType.String => "string", + RealmValueType.Data => "binary", + RealmValueType.Date => "date", + RealmValueType.Float => "float", + RealmValueType.Double => "double", + RealmValueType.Decimal128 => "decimal", + RealmValueType.ObjectId => "objectid", + RealmValueType.Object => "object", + RealmValueType.Guid => "uuid", + _ => throw new NotImplementedException(), + }; + } + + private class RealmValueObject : RealmObject + { + public int Id { get; set; } + + public RealmValue RealmValueProperty { get; set; } + + public IList RealmValueList { get; } + + public ISet RealmValueSet { get; } + + public IDictionary RealmValueDictionary { get; } + + public IDictionary TestDict { get; } + } + + private class InternalObject : RealmObject, IEquatable + { + public int IntProperty { get; set; } + + public string StringProperty { get; set; } + + public override bool Equals(object obj) => Equals(obj as InternalObject); + + public bool Equals(InternalObject other) => other != null && + IntProperty == other.IntProperty && + StringProperty == other.StringProperty; + } + } +} diff --git a/Tests/Realm.Tests/Database/TestObjects.cs b/Tests/Realm.Tests/Database/TestObjects.cs index 6fbe8754d3..adc67246ce 100644 --- a/Tests/Realm.Tests/Database/TestObjects.cs +++ b/Tests/Realm.Tests/Database/TestObjects.cs @@ -93,6 +93,8 @@ public class AllTypesObject : RealmObject public RealmInteger Int32CounterProperty { get; set; } public RealmInteger Int64CounterProperty { get; set; } + + public RealmValue RealmValueProperty { get; set; } } public class DecimalsObject : RealmObject @@ -175,6 +177,8 @@ public class ListsObject : RealmObject public IList?> NullableInt32CounterList { get; } public IList?> NullableInt64CounterList { get; } + + public IList RealmValueList { get; } } public class CollectionsObject : RealmObject @@ -255,6 +259,8 @@ public class CollectionsObject : RealmObject public ISet ObjectSet { get; } + public ISet RealmValueSet { get; } + public IList CharList { get; } public IList ByteList { get; } @@ -331,6 +337,8 @@ public class CollectionsObject : RealmObject public IList ObjectList { get; } + public IList RealmValueList { get; } + public IDictionary CharDict { get; } public IDictionary ByteDict { get; } @@ -406,6 +414,8 @@ public class CollectionsObject : RealmObject public IDictionary?> NullableInt64CounterDict { get; } public IDictionary ObjectDict { get; } + + public IDictionary RealmValueDict { get; } } // This is a stripped-down version of SetsObject because Sync doesn't support float @@ -446,6 +456,8 @@ public class SyncCollectionsObject : RealmObject public ISet ObjectSet { get; } + public ISet RealmValueSet { get; } + public IDictionary CharDict { get; } public IDictionary ByteDict { get; } @@ -477,6 +489,47 @@ public class SyncCollectionsObject : RealmObject public IDictionary ObjectDict { get; } public IDictionary EmbeddedObjectDictionary { get; } + + public IDictionary RealmValueDict { get; } + } + + // This is a stripped-down version of SetsObject because Sync doesn't support float + // or collections of nullable primitives + public class SyncAllTypesObject : RealmObject + { + [MapTo("_id")] + [PrimaryKey] + public ObjectId Id { get; private set; } = ObjectId.GenerateNewId(); + + public char CharProperty { get; set; } + + public byte ByteProperty { get; set; } + + public short Int16Property { get; set; } + + public int Int32Property { get; set; } + + public long Int64Property { get; set; } + + public double DoubleProperty { get; set; } + + public bool BooleanProperty { get; set; } + + public DateTimeOffset DateTimeOffsetProperty { get; set; } + + public decimal DecimalProperty { get; set; } + + public Decimal128 Decimal128Property { get; set; } + + public ObjectId ObjectIdProperty { get; set; } + + public Guid GuidProperty { get; set; } + + public string StringProperty { get; set; } + + public byte[] ByteArrayProperty { get; set; } + + public RealmValue RealmValueProperty { get; set; } } public class DictionariesObject : RealmObject @@ -560,6 +613,9 @@ public class DictionariesObject : RealmObject public IDictionary ObjectDictionary { get; } public IDictionary EmbeddedObjectDictionary { get; } + + public IDictionary RealmValueDictionary { get; } + } public class CounterObject : RealmObject diff --git a/Tests/Realm.Tests/Sync/DataTypeTests.cs b/Tests/Realm.Tests/Sync/DataTypeTests.cs index 58666901a9..22825e70b7 100644 --- a/Tests/Realm.Tests/Sync/DataTypeTests.cs +++ b/Tests/Realm.Tests/Sync/DataTypeTests.cs @@ -160,6 +160,29 @@ public class DataTypeSynchronizationTests : SyncTestBase #endregion + #region RealmValue + + public static IEnumerable<(RealmValue Item1, RealmValue Item2)> RealmTestValues() + { + yield return ("abc", 10); + yield return (new ObjectId("5f63e882536de46d71877979"), new Guid("{F2952191-A847-41C3-8362-497F92CB7D24}")); + yield return (new byte[] { 0, 1, 2 }, DateTimeOffset.FromUnixTimeSeconds(1616137641)); + yield return (true, new IntPropertyObject { Int = 10 }); + yield return (RealmValue.Null, 5m); + yield return (12, 15d); + } + + [TestCaseSource(nameof(RealmTestValues))] + public void Set_RealmValue((RealmValue Item1, RealmValue Item2) values) => TestSetCore(o => o.RealmValueSet, values.Item1, values.Item2); + + [TestCaseSource(nameof(RealmTestValues))] + public void Dict_RealmValue((RealmValue Item1, RealmValue Item2) values) => TestDictionaryCore(o => o.RealmValueDict, values.Item1, values.Item2); + + [TestCaseSource(nameof(RealmTestValues))] + public void Property_RealmValue((RealmValue Item1, RealmValue Item2) values) => TestPropertyCore(o => o.RealmValueProperty, (o, rv) => o.RealmValueProperty = rv, values.Item1, values.Item2); + + #endregion + private void TestSetCore(Func> getter, T item1, T item2, Func equalsOverride = null) { if (equalsOverride == null) @@ -309,6 +332,74 @@ private void TestDictionaryCore(Func(Func getter, Action setter, T item1, T item2, Func equalsOverride = null) + { + if (equalsOverride == null) + { + equalsOverride = (a, b) => a.Equals(b); + } + + SyncTestHelpers.RunBaasTestAsync(async () => + { + var partition = Guid.NewGuid().ToString(); + var realm1 = await GetIntegrationRealmAsync(partition); + var realm2 = await GetIntegrationRealmAsync(partition); + + var obj1 = realm1.Write(() => + { + return realm1.Add(new SyncAllTypesObject()); + }); + + await WaitForUploadAsync(realm1); + await WaitForDownloadAsync(realm2); + + var obj2 = realm2.Find(obj1.Id); + + realm1.Write(() => + { + setter(obj1, item1); + }); + + await WaitForPropertyChangedAsync(obj2); + + var prop1 = getter(obj1); + var prop2 = getter(obj2); + + Assert.That(prop1, Is.EqualTo(prop2).Using(equalsOverride)); + Assert.That(prop1, Is.EqualTo(item1).Using(equalsOverride)); + + realm2.Write(() => + { + setter(obj2, item2); + }); + + await WaitForPropertyChangedAsync(obj1); + + prop1 = getter(obj1); + prop2 = getter(obj2); + + Assert.That(prop1, Is.EqualTo(prop2).Using(equalsOverride)); + Assert.That(prop2, Is.EqualTo(item2).Using(equalsOverride)); + }, ensureNoSessionErrors: true); + } + + private static async Task WaitForPropertyChangedAsync(RealmObject realmObject, int timeout = 10 * 1000) + { + var tcs = new TaskCompletionSource(); + realmObject.PropertyChanged += RealmObject_PropertyChanged; + + void RealmObject_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e != null) + { + tcs.TrySetResult(null); + } + } + + await tcs.Task.Timeout(timeout); + realmObject.PropertyChanged -= RealmObject_PropertyChanged; + } + private static async Task WaitForCollectionChangeAsync(IRealmCollection collection, int timeout = 10 * 1000) { var tcs = new TaskCompletionSource(); diff --git a/Tests/Realm.Tests/Sync/SyncTestBase.cs b/Tests/Realm.Tests/Sync/SyncTestBase.cs index 902d5160ab..0f96bdf712 100644 --- a/Tests/Realm.Tests/Sync/SyncTestBase.cs +++ b/Tests/Realm.Tests/Sync/SyncTestBase.cs @@ -160,7 +160,7 @@ protected async Task GetIntegrationConfigAsync(Guid? partitio private static SyncConfiguration UpdateConfig(SyncConfiguration config) { - config.ObjectClasses = new[] { typeof(HugeSyncObject), typeof(PrimaryKeyStringObject), typeof(ObjectIdPrimaryKeyWithValueObject), typeof(SyncCollectionsObject), typeof(IntPropertyObject), typeof(EmbeddedIntPropertyObject) }; + config.ObjectClasses = new[] { typeof(HugeSyncObject), typeof(PrimaryKeyStringObject), typeof(ObjectIdPrimaryKeyWithValueObject), typeof(SyncCollectionsObject), typeof(IntPropertyObject), typeof(EmbeddedIntPropertyObject), typeof(SyncAllTypesObject) }; config.SessionStopPolicy = SessionStopPolicy.Immediately; return config; diff --git a/wrappers/realm-core b/wrappers/realm-core index a428a9ea88..19f3314cf8 160000 --- a/wrappers/realm-core +++ b/wrappers/realm-core @@ -1 +1 @@ -Subproject commit a428a9ea88ecad404e25408296579fa404e88e01 +Subproject commit 19f3314cf8496151163b21c4575834728efc8c02 diff --git a/wrappers/src/list_cs.cpp b/wrappers/src/list_cs.cpp index bad584d082..39e9c2d9a1 100644 --- a/wrappers/src/list_cs.cpp +++ b/wrappers/src/list_cs.cpp @@ -136,12 +136,12 @@ REALM_EXPORT void list_get_value(List& list, size_t ndx, realm_value_t* value, N throw IndexOutOfRangeException("Get from RealmList", ndx, count); if ((list.get_type() & ~PropertyType::Flags) == PropertyType::Object) { - *value = to_capi(new Object(list.get_realm(), list.get_object_schema(), list.get(ndx))); + *value = to_capi(list.get(ndx), list.get_realm()); } else { auto val = list.get_any(ndx); if (!val.is_null() && val.get_type() == type_TypedLink) { - *value = to_capi(new Object(list.get_realm(), val.get())); + *value = to_capi(val.get(), list.get_realm()); } else { *value = to_capi(std::move(val)); @@ -168,8 +168,8 @@ REALM_EXPORT size_t list_find_value(List& list, realm_value_t value, NativeExcep throw ObjectManagedByAnotherRealmException("Can't look up index of an object that belongs to a different Realm."); } - if ((list_type & PropertyType::Flags) == PropertyType::Mixed) { - return list.find_any(ObjLink(value.link.object->get_object_schema().table_key, value.link.object->obj().get_key())); + if ((list_type & ~PropertyType::Flags) == PropertyType::Mixed) { + return list.find_any(value.link.object->obj()); } return list.find(value.link.object->obj()); diff --git a/wrappers/src/marshalling.hpp b/wrappers/src/marshalling.hpp index a68e01c069..44dedcb387 100644 --- a/wrappers/src/marshalling.hpp +++ b/wrappers/src/marshalling.hpp @@ -21,9 +21,12 @@ #include #include #include +#include + #include "wrapper_exceptions.hpp" #include "error_handling.hpp" #include "timestamp_helpers.hpp" +#include "shared_realm_cs.hpp" namespace realm { namespace binding { @@ -64,6 +67,7 @@ typedef struct realm_decimal128 { typedef struct realm_link { Object* object; + TableKey table_key; } realm_link_t; typedef struct realm_object_id { @@ -302,14 +306,37 @@ static inline Mixed from_capi(realm_value_t val) REALM_TERMINATE("Invalid realm_value_t"); } -static inline realm_value_t to_capi(Object* obj) +static inline realm_value_t to_capi(Obj obj, SharedRealm realm) { + auto table_key = obj.get_table()->get_key(); + auto schema = realm->schema().find(table_key); + if (schema == realm->schema().end()) + { + // These shenanigans are only necessary because realm->schema() doesn't automatically update. + // TODO: remove this code when https://github.com/realm/realm-core/issues/4584 is resolved + CSharpBindingContext *cs_binding_context = dynamic_cast(realm->m_binding_context.get()); + schema = cs_binding_context->m_realm_schema.find(table_key); + if (schema == cs_binding_context->m_realm_schema.end()) + { + cs_binding_context->m_realm_schema = ObjectStore::schema_from_group(realm->read_group()); + schema = cs_binding_context->m_realm_schema.find(table_key); + } + } + + auto object = new Object(realm, *schema, std::move(obj)); + realm_value_t val{}; val.type = realm_value_type::RLM_TYPE_LINK; - val.link.object = obj; + val.link.object = object; + val.link.table_key = table_key; return val; } +static inline realm_value_t to_capi(ObjLink obj_link, SharedRealm realm) +{ + return to_capi(realm->read_group().get_object(obj_link), realm); +} + static inline realm_value_t to_capi(Mixed value) { realm_value_t val{}; @@ -361,7 +388,7 @@ static inline realm_value_t to_capi(Mixed value) case type_TypedLink: [[fallthrough]]; case type_Link: - REALM_TERMINATE("Can't use to_capi on values containing links."); + REALM_TERMINATE("Can't use this overload of to_capi on values containing links, use to_capi(Obj, SharedRealm) instead."); case type_ObjectId: { val.type = realm_value_type::RLM_TYPE_OBJECT_ID; val.object_id = to_capi(value.get()); @@ -397,12 +424,12 @@ inline realm_value_t to_capi(object_store::Dictionary& dictionary, Mixed val) switch (val.get_type()) { case type_Link: if ((dictionary.get_type() & ~PropertyType::Flags) == PropertyType::Object) { - return to_capi(new Object(dictionary.get_realm(), ObjLink(dictionary.get_object_schema().table_key, val.get()))); + return to_capi(ObjLink(dictionary.get_object_schema().table_key, val.get()), dictionary.get_realm()); } REALM_UNREACHABLE(); case type_TypedLink: - return to_capi(new Object(dictionary.get_realm(), val.get_link())); + return to_capi(val.get_link(), dictionary.get_realm()); default: return to_capi(std::move(val)); } diff --git a/wrappers/src/object_cs.cpp b/wrappers/src/object_cs.cpp index 3a05d4e025..c65c4f0ce5 100644 --- a/wrappers/src/object_cs.cpp +++ b/wrappers/src/object_cs.cpp @@ -30,6 +30,14 @@ using namespace realm; using namespace realm::binding; +using GetNativeSchemaT = void(SchemaForMarshaling schema, void* managed_callback); + +namespace realm { +namespace binding { + extern std::function s_get_native_schema; +} +} + extern "C" { REALM_EXPORT bool object_get_is_valid(const Object& object, NativeException::Marshallable& ex) { @@ -79,7 +87,7 @@ extern "C" { if ((prop.type & ~PropertyType::Flags) == PropertyType::Object) { const Obj link_obj = object.obj().get_linked_object(prop.column_key); if (link_obj) { - *value = to_capi(new Object(object.realm(), link_obj)); + *value = to_capi(link_obj, object.realm()); } else { value->type = realm_value_type::RLM_TYPE_NULL; @@ -88,7 +96,7 @@ extern "C" { else { auto val = object.obj().get_any(prop.column_key); if (!val.is_null() && val.get_type() == type_TypedLink) { - *value = to_capi(new Object(object.realm(), val.get())); + *value = to_capi(val.get(), object.realm()); } else { *value = to_capi(std::move(val)); @@ -97,6 +105,23 @@ extern "C" { }); } + REALM_EXPORT void object_get_schema(const Object& object, void* managed_callback, NativeException::Marshallable& ex) + { + handle_errors(ex, [&]() { + std::vector schema_objects; + std::vector schema_properties; + + auto& object_schema = object.get_object_schema(); + schema_objects.push_back(SchemaObject::for_marshalling(object_schema, schema_properties, object_schema.is_embedded)); + + s_get_native_schema(SchemaForMarshaling{ + schema_objects.data(), + static_cast(schema_objects.size()), + schema_properties.data() + }, managed_callback); + }); + } + REALM_EXPORT void object_set_value(const Object& object, size_t property_ndx, realm_value_t value, NativeException::Marshallable& ex) { handle_errors(ex, [&]() { @@ -109,9 +134,8 @@ extern "C" { throw NotNullableException(schema.name, prop.name); } - // TODO: replace prop.column_key().get_type() with prop.type - if (!value.is_null() && to_capi(prop.type) != value.type && - prop.column_key.get_type() != col_type_Mixed) { + if (!value.is_null() && (prop.type & ~PropertyType::Flags) != PropertyType::Mixed && + to_capi(prop.type) != value.type) { auto& schema = object.get_object_schema(); throw PropertyTypeMismatchException( schema.name, @@ -256,7 +280,7 @@ extern "C" { verify_can_set(object); auto col_key = get_column_key(object, property_ndx); - return object.obj().add_int(col_key, value).get(col_key); + return object.obj().add_int(col_key, value).get_any(col_key).get_int(); }); } diff --git a/wrappers/src/query_cs.cpp b/wrappers/src/query_cs.cpp index 5a9b929b23..21547e040a 100644 --- a/wrappers/src/query_cs.cpp +++ b/wrappers/src/query_cs.cpp @@ -34,6 +34,38 @@ inline ColKey get_key_for_prop(Query& query, SharedRealm& realm, size_t property return realm->schema().find(ObjectStore::object_type_for_table_name(query.get_table()->get_name()))->persisted_properties[property_index].column_key; } +inline TypeOfValue::Attribute attribute_from(realm_value_type type) +{ + switch (type) { + case realm_value_type::RLM_TYPE_NULL: + return TypeOfValue::Attribute::Null; + case realm_value_type::RLM_TYPE_INT: + return TypeOfValue::Attribute::Int; + case realm_value_type::RLM_TYPE_BOOL: + return TypeOfValue::Attribute::Bool; + case realm_value_type::RLM_TYPE_STRING: + return TypeOfValue::Attribute::String; + case realm_value_type::RLM_TYPE_BINARY: + return TypeOfValue::Attribute::Binary; + case realm_value_type::RLM_TYPE_TIMESTAMP: + return TypeOfValue::Attribute::Timestamp; + case realm_value_type::RLM_TYPE_FLOAT: + return TypeOfValue::Attribute::Float; + case realm_value_type::RLM_TYPE_DOUBLE: + return TypeOfValue::Attribute::Double; + case realm_value_type::RLM_TYPE_DECIMAL128: + return TypeOfValue::Attribute::Decimal128; + case realm_value_type::RLM_TYPE_OBJECT_ID: + return TypeOfValue::Attribute::ObjectId; + case realm_value_type::RLM_TYPE_LINK: + return TypeOfValue::Attribute::ObjectLink; + case realm_value_type::RLM_TYPE_UUID: + return TypeOfValue::Attribute::UUID; + default: + REALM_UNREACHABLE(); + } +} + extern "C" { REALM_EXPORT void query_destroy(Query* query) @@ -158,8 +190,11 @@ REALM_EXPORT void query_primitive_equal(Query& query, SharedRealm& realm, size_t case realm_value_type::RLM_TYPE_BINARY: query.equal(std::move(col_key), from_capi(primitive.binary)); break; + case realm_value_type::RLM_TYPE_STRING: + query.equal(std::move(col_key), from_capi(primitive.string)); + break; case realm_value_type::RLM_TYPE_LINK: - query.links_to(std::move(col_key), primitive.link.object->obj().get_key()); + query.equal(std::move(col_key), from_capi(primitive)); break; } }); @@ -199,8 +234,11 @@ REALM_EXPORT void query_primitive_not_equal(Query& query, SharedRealm& realm, si case realm_value_type::RLM_TYPE_BINARY: query.not_equal(std::move(col_key), from_capi(primitive.binary)); break; + case realm_value_type::RLM_TYPE_STRING: + query.not_equal(std::move(col_key), from_capi(primitive.string)); + break; case realm_value_type::RLM_TYPE_LINK: - query.Not().links_to(std::move(col_key), primitive.link.object->obj().get_key()); + query.not_equal(std::move(col_key), from_capi(primitive)); break; } }); @@ -363,4 +401,19 @@ REALM_EXPORT Results* query_create_results(Query& query, SharedRealm& realm, Des }); } +REALM_EXPORT void query_realm_value_type_equal(Query& query, SharedRealm& realm, size_t property_index, realm_value_type realm_value_type, NativeException::Marshallable& ex) +{ + handle_errors(ex, [&]() { + auto col_key = get_key_for_prop(query, realm, property_index); + query.and_query(query.get_table()->column(col_key).type_of_value() == TypeOfValue(attribute_from(realm_value_type))); + }); +} + +REALM_EXPORT void query_realm_value_type_not_equal(Query& query, SharedRealm& realm, size_t property_index, realm_value_type realm_value_type, NativeException::Marshallable& ex) +{ + handle_errors(ex, [&]() { + auto col_key = get_key_for_prop(query, realm, property_index); + query.and_query(query.get_table()->column(col_key).type_of_value() != TypeOfValue(attribute_from(realm_value_type))); //Need to check if correct + }); +} } // extern "C" diff --git a/wrappers/src/results_cs.cpp b/wrappers/src/results_cs.cpp index bb7af8aaae..d61be37927 100644 --- a/wrappers/src/results_cs.cpp +++ b/wrappers/src/results_cs.cpp @@ -61,7 +61,7 @@ REALM_EXPORT void results_get_value(Results& results, size_t ndx, realm_value_t* if ((results.get_type() & ~PropertyType::Flags) == PropertyType::Object) { if (auto obj = results.get(ndx)) { - *value = to_capi(new Object(results.get_realm(), results.get_object_schema(), obj)); + *value = to_capi(obj, results.get_realm()); } else { *value = realm_value_t{}; @@ -70,7 +70,7 @@ REALM_EXPORT void results_get_value(Results& results, size_t ndx, realm_value_t* else { auto val = results.get_any(ndx); if (!val.is_null() && val.get_type() == type_TypedLink) { - *value = to_capi(new Object(results.get_realm(), val.get())); + *value = to_capi(val.get(), results.get_realm()); } else { *value = to_capi(std::move(val)); @@ -175,13 +175,17 @@ REALM_EXPORT Results* results_snapshot(const Results& results, NativeException:: }); } -REALM_EXPORT size_t results_find_object(Results& results, const Object& object, NativeException::Marshallable& ex) +REALM_EXPORT size_t results_find_value(Results& results, realm_value_t value, NativeException::Marshallable& ex) { return handle_errors(ex, [&]() { - if (results.get_realm() != object.realm()) { - throw ObjectManagedByAnotherRealmException("Can't look up index of an object that belongs to a different Realm."); + if (value.type == realm_value_type::RLM_TYPE_LINK) { + if (results.get_realm() != value.link.object->realm()) { + throw ObjectManagedByAnotherRealmException("Can't look up index of an object that belongs to a different Realm."); + } + return results.index_of(value.link.object->obj()); } - return results.index_of(object.obj()); + + return results.index_of(from_capi(value)); }); } diff --git a/wrappers/src/set_cs.cpp b/wrappers/src/set_cs.cpp index 236f84b036..191cb5f843 100644 --- a/wrappers/src/set_cs.cpp +++ b/wrappers/src/set_cs.cpp @@ -69,12 +69,12 @@ REALM_EXPORT void realm_set_get_value(object_store::Set& set, size_t ndx, realm_ throw IndexOutOfRangeException("Get from RealmSet", ndx, count); if ((set.get_type() & ~PropertyType::Flags) == PropertyType::Object) { - *value = to_capi(new Object(set.get_realm(), set.get_object_schema(), set.get(ndx))); + *value = to_capi(set.get(ndx), set.get_realm()); } else { auto val = set.get_any(ndx); if (!val.is_null() && val.get_type() == type_TypedLink) { - *value = to_capi(new Object(set.get_realm(), val.get())); + *value = to_capi(val.get(), set.get_realm()); } else { *value = to_capi(std::move(val)); @@ -119,8 +119,8 @@ REALM_EXPORT bool realm_set_contains_value(object_store::Set& set, realm_value_t throw ObjectManagedByAnotherRealmException("Can't look up index of an object that belongs to a different Realm."); } - if ((set_type & PropertyType::Flags) == PropertyType::Mixed) { - return set.find_any(ObjLink(value.link.object->get_object_schema().table_key, value.link.object->obj().get_key())) > -1; + if ((set_type & ~PropertyType::Flags) == PropertyType::Mixed) { + return set.find_any(ObjLink(value.link.object->get_object_schema().table_key, value.link.object->obj().get_key())) != not_found; } return set.find(value.link.object->obj()) != realm::not_found; diff --git a/wrappers/src/shared_realm_cs.cpp b/wrappers/src/shared_realm_cs.cpp index b5d3215a86..00a838a7d2 100644 --- a/wrappers/src/shared_realm_cs.cpp +++ b/wrappers/src/shared_realm_cs.cpp @@ -69,6 +69,7 @@ namespace binding { CSharpBindingContext::~CSharpBindingContext() { s_on_binding_context_destructed(m_managed_state_handle); + m_realm_schema = realm::Schema(); } void log_message(std::string message, util::Logger::Level level) diff --git a/wrappers/src/shared_realm_cs.hpp b/wrappers/src/shared_realm_cs.hpp index 7adbf36b6c..4079561ad4 100644 --- a/wrappers/src/shared_realm_cs.hpp +++ b/wrappers/src/shared_realm_cs.hpp @@ -19,7 +19,6 @@ #ifndef SHARED_REALM_CS_HPP #define SHARED_REALM_CS_HPP -#include "marshalling.hpp" #include "schema_cs.hpp" #include "sync_session_cs.hpp" @@ -94,6 +93,10 @@ namespace binding { { return m_managed_state_handle; } + + // TODO: this should go away once https://github.com/realm/realm-core/issues/4584 is resolved + Schema m_realm_schema; + private: void* m_managed_state_handle;