Skip to content

Commit

Permalink
Added stub for unmanaged object dynamic api
Browse files Browse the repository at this point in the history
  • Loading branch information
papafe committed Aug 12, 2024
1 parent 04f8863 commit 187f501
Show file tree
Hide file tree
Showing 6 changed files with 380 additions and 184 deletions.
4 changes: 2 additions & 2 deletions Realm/Realm/DatabaseTypes/Accessors/ManagedAccessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ protected ManagedAccessor()
{
_hashCode = new(() => ObjectHandle!.GetObjHash());
_objectSchema = new(() => Realm!.Config.RelaxedSchema ? Metadata!.Schema.MakeCopyWithHandle(ObjectHandle!) : Metadata!.Schema);
_dynamicObjectApi = new(() => new(this));
_dynamicObjectApi = new(() => new DynamicManagedObjectApi(this));
}

[MemberNotNull(nameof(Realm), nameof(ObjectHandle), nameof(Metadata))]
Expand All @@ -114,7 +114,7 @@ public RealmValue GetValue(string propertyName)
return ObjectHandle.GetValue(propertyName, Metadata, Realm);
}

/// <inheritdoc/>
/// AddDocs
public bool TryGetValue(string propertyName, out RealmValue value)
{
return ObjectHandle.TryGetValue(propertyName, Metadata, Realm, out value);
Expand Down
32 changes: 32 additions & 0 deletions Realm/Realm/DatabaseTypes/Accessors/UnmanagedAccessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ public abstract class UnmanagedAccessor : IRealmAccessor

private Action<string>? _onNotifyPropertyChanged;

//TODO we could initialize this lazily

Check warning on line 40 in Realm/Realm/DatabaseTypes/Accessors/UnmanagedAccessor.cs

View workflow job for this annotation

GitHub Actions / Verify TODOs

Realm/Realm/DatabaseTypes/Accessors/UnmanagedAccessor.cs#L40

TODO entry doesn't have a link to Github issue or Jira ticket we could initialize this lazily

Check warning on line 40 in Realm/Realm/DatabaseTypes/Accessors/UnmanagedAccessor.cs

View workflow job for this annotation

GitHub Actions / Test Weaver (ubuntu-latest, linux-x64)

Check warning on line 40 in Realm/Realm/DatabaseTypes/Accessors/UnmanagedAccessor.cs

View workflow job for this annotation

GitHub Actions / Test Weaver (macos-14, osx-arm64)

Check warning on line 40 in Realm/Realm/DatabaseTypes/Accessors/UnmanagedAccessor.cs

View workflow job for this annotation

GitHub Actions / Test Source Generation

Check warning on line 40 in Realm/Realm/DatabaseTypes/Accessors/UnmanagedAccessor.cs

View workflow job for this annotation

GitHub Actions / Test Weaver (windows-latest, win-x64)

Check warning on line 40 in Realm/Realm/DatabaseTypes/Accessors/UnmanagedAccessor.cs

View workflow job for this annotation

GitHub Actions / Test Code Coverage

Single line comment should begin with a space. (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1005.md) [/home/runner/work/realm-dotnet/realm-dotnet/Realm/Realm/Realm.csproj::TargetFramework=net8.0]

Check warning on line 40 in Realm/Realm/DatabaseTypes/Accessors/UnmanagedAccessor.cs

View workflow job for this annotation

GitHub Actions / Package NuGet

protected Dictionary<string, RealmValue> _extraProperties = new();

Check warning on line 41 in Realm/Realm/DatabaseTypes/Accessors/UnmanagedAccessor.cs

View workflow job for this annotation

GitHub Actions / Test Weaver (ubuntu-latest, linux-x64)

Check warning on line 41 in Realm/Realm/DatabaseTypes/Accessors/UnmanagedAccessor.cs

View workflow job for this annotation

GitHub Actions / Test Weaver (macos-14, osx-arm64)

Check warning on line 41 in Realm/Realm/DatabaseTypes/Accessors/UnmanagedAccessor.cs

View workflow job for this annotation

GitHub Actions / Test Source Generation

Check warning on line 41 in Realm/Realm/DatabaseTypes/Accessors/UnmanagedAccessor.cs

View workflow job for this annotation

GitHub Actions / Test Weaver (windows-latest, win-x64)

Check warning on line 41 in Realm/Realm/DatabaseTypes/Accessors/UnmanagedAccessor.cs

View workflow job for this annotation

GitHub Actions / Test Code Coverage

Elements should be documented (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1600.md) [/home/runner/work/realm-dotnet/realm-dotnet/Realm/Realm/Realm.csproj::TargetFramework=net8.0]

Check warning on line 41 in Realm/Realm/DatabaseTypes/Accessors/UnmanagedAccessor.cs

View workflow job for this annotation

GitHub Actions / Package NuGet


/// <inheritdoc/>
public bool IsManaged => false;

Expand Down Expand Up @@ -93,6 +96,10 @@ public IQueryable<T> GetBacklinks<T>(string propertyName)
/// <inheritdoc/>
public abstract void SetValueUnique(string propertyName, RealmValue val);

public abstract bool TryGet(string propertyName, out RealmValue value);

public abstract bool Unset(string propertyName);

/// <inheritdoc/>
public virtual void SubscribeForNotifications(Action<string> notifyPropertyChangedDelegate)
{
Expand Down Expand Up @@ -168,5 +175,30 @@ public override void SetValueUnique(string propertyName, RealmValue val)
{
throw new NotSupportedException("This should not be used for now");
}

public override bool TryGet(string propertyName, out RealmValue value)
{
return _extraProperties.TryGetValue(propertyName, out value);
}

public override bool Unset(string propertyName)
{
return _extraProperties.Remove(propertyName);
}

public bool TryGetExtraProperty(string propertyName, out RealmValue value)
{
return _extraProperties.TryGetValue(propertyName, out value);
}

public RealmValue GetExtraProperty(string propertyName)
{
return _extraProperties[propertyName];
}

public void SetExtraProperty(string propertyName, RealmValue val)
{
_extraProperties[propertyName] = val;
}
}
}
235 changes: 235 additions & 0 deletions Realm/Realm/Dynamic/DynamicManagedObjectApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2024 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.Linq;
using System.Runtime.CompilerServices;
using Realms.Helpers;
using Realms.Schema;

namespace Realms
{
/// <inheritdoc/>
public class DynamicManagedObjectApi : DynamicObjectApi
{
private readonly ManagedAccessor _managedAccessor;

private readonly bool _isRelaxedSchema;

internal DynamicManagedObjectApi(ManagedAccessor managedAccessor)
{
_managedAccessor = managedAccessor;
_isRelaxedSchema = managedAccessor.Realm.Config.RelaxedSchema;
}

/// <inheritdoc/>
public override RealmValue Get(string propertyName)
{
CheckGetPropertySuitability(propertyName);

return _managedAccessor.GetValue(propertyName);
}

/// <inheritdoc/>
public override T Get<T>(string propertyName)
{
return Get(propertyName).As<T>();
}

/// <inheritdoc/>
public override bool TryGet(string propertyName, out RealmValue propertyValue)
{
CheckGetPropertySuitability(propertyName);

return _managedAccessor.TryGetValue(propertyName, out propertyValue);
}

/// <inheritdoc/>
public override bool TryGet<T>(string propertyName, out T? propertyValue)
where T : default
{
var foundValue = TryGet(propertyName, out var val);
if (foundValue)
{
propertyValue = val.As<T>();
return true;
}

propertyValue = default;
return false;
}

/// <inheritdoc/>
public override void Set(string propertyName, RealmValue value)
{
if (GetModelProperty(propertyName, throwOnMissing: !_isRelaxedSchema) is Property property)
{
if (property.Type.IsComputed())
{
throw new NotSupportedException(
$"{_managedAccessor.ObjectSchema.Name}.{propertyName} is {property.GetDotnetTypeName()} (backlinks collection) and can't be set directly");
}

if (property.Type.IsCollection(out _))
{
throw new NotSupportedException(
$"{_managedAccessor.ObjectSchema.Name}.{propertyName} is {property.GetDotnetTypeName()} (collection) and can't be set directly.");
}

if (!property.Type.IsNullable() && value.Type == RealmValueType.Null)
{
throw new ArgumentException($"{_managedAccessor.ObjectSchema.Name}.{propertyName} is {property.GetDotnetTypeName()} which is not nullable, but the supplied value is <null>.");
}

if (!property.Type.IsRealmValue() && value.Type != RealmValueType.Null && property.Type.ToRealmValueType() != value.Type)
{
throw new ArgumentException($"{_managedAccessor.ObjectSchema.Name}.{propertyName} is {property.GetDotnetTypeName()} but the supplied value is {value.AsAny()?.GetType().Name} ({value}).");
}

if (property.IsPrimaryKey)
{
_managedAccessor.SetValueUnique(propertyName, value);
return;
}
}

_managedAccessor.SetValue(propertyName, value);
}

/// <inheritdoc/>
public override bool Unset(string propertyName)
{
return _managedAccessor.UnsetProperty(propertyName);
}

/// <inheritdoc/>
public override IQueryable<IRealmObjectBase> GetBacklinks(string propertyName)
{
var property = GetModelProperty(propertyName, PropertyTypeEx.IsComputed);

var resultsHandle = _managedAccessor.ObjectHandle.GetBacklinks(propertyName, _managedAccessor.Metadata);

var relatedMeta = _managedAccessor.Realm.Metadata[property.ObjectType!];
if (relatedMeta.Schema.BaseType == ObjectSchema.ObjectType.EmbeddedObject)
{
return new RealmResults<IEmbeddedObject>(_managedAccessor.Realm, resultsHandle, relatedMeta);
}

return new RealmResults<IRealmObject>(_managedAccessor.Realm, resultsHandle, relatedMeta);
}

/// <inheritdoc/>
public override IQueryable<IRealmObjectBase> GetBacklinksFromType(string fromObjectType, string fromPropertyName)
{
Argument.Ensure(_managedAccessor.Realm.Metadata.TryGetValue(fromObjectType, out var relatedMeta), $"Could not find schema for type {fromObjectType}", nameof(fromObjectType));

var resultsHandle = _managedAccessor.ObjectHandle.GetBacklinksForType(relatedMeta.TableKey, fromPropertyName, relatedMeta);
if (relatedMeta.Schema.BaseType == ObjectSchema.ObjectType.EmbeddedObject)
{
return new RealmResults<IEmbeddedObject>(_managedAccessor.Realm, resultsHandle, relatedMeta);
}

return new RealmResults<IRealmObject>(_managedAccessor.Realm, resultsHandle, relatedMeta);
}

/// <inheritdoc/>
public override IList<T> GetList<T>(string propertyName)
{
var property = GetModelProperty(propertyName, PropertyTypeEx.IsList);

var result = _managedAccessor.ObjectHandle.GetList<T>(_managedAccessor.Realm, propertyName, _managedAccessor.Metadata, property.ObjectType);
result.IsDynamic = true;
return result;
}

/// <inheritdoc/>
public override ISet<T> GetSet<T>(string propertyName)
{
var property = GetModelProperty(propertyName, PropertyTypeEx.IsSet);

var result = _managedAccessor.ObjectHandle.GetSet<T>(_managedAccessor.Realm, propertyName, _managedAccessor.Metadata, property.ObjectType);
result.IsDynamic = true;
return result;
}

/// <inheritdoc/>
public override IDictionary<string, T> GetDictionary<T>(string propertyName)
{
var property = GetModelProperty(propertyName, PropertyTypeEx.IsDictionary);

var result = _managedAccessor.ObjectHandle.GetDictionary<T>(_managedAccessor.Realm, propertyName, _managedAccessor.Metadata, property.ObjectType);
result.IsDynamic = true;
return result;
}

private void CheckGetPropertySuitability(string propertyName)
{
if (GetModelProperty(propertyName, throwOnMissing: !_isRelaxedSchema) is Property property)
{
if (property.Type.IsComputed())
{
throw new NotSupportedException(
$"{_managedAccessor.ObjectSchema.Name}.{propertyName} is {property.GetDotnetTypeName()} (backlinks collection) and can't be accessed using {nameof(Dynamic)}.{nameof(Get)}. Use {nameof(GetBacklinks)} instead.");
}

if (property.Type.IsCollection(out var collectionType) && collectionType == PropertyType.Set)
{
throw new NotSupportedException(
$"{_managedAccessor.ObjectSchema.Name}.{propertyName} is {property.GetDotnetTypeName()} and can't be accessed using {nameof(Dynamic)}.{nameof(Get)}. Use GetSet instead.");
}
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Property? GetModelProperty(string propertyName, bool throwOnMissing)
{
Argument.NotNull(propertyName, nameof(propertyName));

if (!_managedAccessor.ObjectSchema.TryFindModelProperty(propertyName, out var property))
{
if (throwOnMissing)
{
throw new MissingMemberException(_managedAccessor.ObjectSchema.Name, propertyName);
}

return null;
}

return property;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Property GetModelProperty(string propertyName, Func<PropertyType, bool> typeCheck, [CallerMemberName] string methodName = "")
{
Argument.NotNull(propertyName, nameof(propertyName));

if (!_managedAccessor.ObjectSchema.TryFindModelProperty(propertyName, out var property))
{
throw new MissingMemberException(_managedAccessor.ObjectSchema.Name, propertyName);
}

if (!typeCheck(property.Type))
{
throw new ArgumentException($"{_managedAccessor.ObjectSchema.Name}.{propertyName} is {property.GetDotnetTypeName()} which can't be accessed using {methodName}.");
}

return property;
}
}
}
Loading

0 comments on commit 187f501

Please sign in to comment.