Skip to content

Commit

Permalink
Improvements related to Remote MongoDB access (#3414)
Browse files Browse the repository at this point in the history
* Add a smoke test

* Wire up basic serialization through the generator

* wip

* Add more tests

* Add tests for links

* Embedded object changes

* Resolve some warnings

* Add wasm workload

* Setup workloads everywhere

* Try to setup .net 6

* Setup .net before we install the workloads

* Add global.json

* Remove wasm-tools-net6

* Static queries build on (#3487)

* Various fixes

* Removed unused

* Fixes to bsondocument equality

* Passingn tests

* Added embedded test

* Added test for embedded

* Added fix for not implemented

* Fixed serializer and added correct serialization of realm value

* Fixed tests for object in realm value

* Fixed deserialization object in realm value

* Moved method

* Split collection class

* Revert "Split collection class"

This reverts commit eee2d42.

* Added test to remove

* Removed unused class and test

* Separated serializer from serializer registry

* Added basic tests for primitives

* Added more tests

* Added tests for links

* Added several tests

* Added tests for embedded

* Removed unused param

* Removed unnecessary

* Various fixes

* Added missing cases

* Added docs and improved serialization

* Added tests

* Added saving of schema

* Added correct handling of extra fields and changelog

* Corrected exception check

* Small fixes

* Small corrections

* Moved tests to a new fixture

* Small correction

* Reinstated reflection

* Small fixes

* Warning fix

* Updated source generated test classes

* Corrected weavers errors

* Corrected platformhelper and testobject

* Fixed source generator test

* Added fix for ctor

* Small fixes

* Corrections to test

* Small fix

* Added tests to explain datetime differences

* Added support for legacy serialization

* Avoiding sub-millisecond precision in date generation

* Small corrections

* Added wait for tests

* Error corrected

* Removed comment

---------

Co-authored-by: Ferdinando Papale <4850119+papafe@users.noreply.github.com>
  • Loading branch information
nirinchev and papafe authored Feb 8, 2024
1 parent 39cd3d3 commit c4546f9
Show file tree
Hide file tree
Showing 167 changed files with 13,667 additions and 417 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
## vNext (TBD)

### Breaking Changes
* Added automatic serialization and deserialization of Realm classes when using methods on `MongoClient.Collection`, without the need to annotate classes with `MongoDB.Bson`attributes. This feature required to change the default serialization for various types (including `DateTimeOffset`). If you prefer to use the previous serialization, you need to call `Realm.SetLegacySerialization` before any kind of serialization is done, otherwise it may not work as epxected. [#3459](https://github.com/realm/realm-dotnet/pull/3459)

### Enhancements
* None
* Added the `MongoClient.GetCollection<T>` method to get a collection of documents from MongoDB that can be deserialized in Realm objects. This methods works the same as `MongoClient.GetDatabase(dbName).GetCollection(collectionName)`, but the database name and collection name are automatically derived from the Realm object class. [#3414](https://github.com/realm/realm-dotnet/pull/3414)

### Fixed
* None
Expand Down
5 changes: 5 additions & 0 deletions Realm.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=animales/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=asyncopentask/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=autoimplemented/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=autoverify/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=baasapikey/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=baascluster/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=baasdifferentiator/@EntryIndexedValue">True</s:Boolean>
Expand Down Expand Up @@ -60,6 +61,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=Name_0020/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=nomoreuserthread/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=notificationtoken/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Noupsert/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=nulled/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=N_00FAmenor/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=paramref/@EntryIndexedValue">True</s:Boolean>
Expand All @@ -68,6 +70,8 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=Quenta/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Quenya/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Queryables/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=realmlogfile/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=realmloglevel/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=refcounted/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=refcounting/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Reflectable/@EntryIndexedValue">True</s:Boolean>
Expand All @@ -94,6 +98,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=syncsession/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=syncuser/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=tablekey/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=testhost/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=t_0020rec/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unbindlist/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=unbindlistlock/@EntryIndexedValue">True</s:Boolean>
Expand Down
170 changes: 152 additions & 18 deletions Realm/Realm.SourceGenerator/ClassCodeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
Expand All @@ -29,6 +30,7 @@ internal class ClassCodeBuilder
{
private readonly string[] _defaultNamespaces =
{
"MongoDB.Bson.Serialization",
"System",
"System.Collections.Generic",
"System.Linq",
Expand All @@ -49,6 +51,7 @@ internal class ClassCodeBuilder
private readonly string _accessorInterfaceName;
private readonly string _managedAccessorClassName;
private readonly string _unmanagedAccessorClassName;
private readonly string _serializerClassName;

public ClassCodeBuilder(ClassInfo classInfo, GeneratorConfig generatorConfig)
{
Expand All @@ -72,18 +75,14 @@ public ClassCodeBuilder(ClassInfo classInfo, GeneratorConfig generatorConfig)
_accessorInterfaceName = $"I{className}Accessor";
_managedAccessorClassName = $"{className}ManagedAccessor";
_unmanagedAccessorClassName = $"{className}UnmanagedAccessor";
_serializerClassName = $"{className}Serializer";
}

public string GenerateSource()
{
var usings = GetUsings();

var interfaceString = GenerateInterface().Indent();
var managedAccessorString = GenerateManagedAccessor().Indent();
var unmanagedAccessorString = GenerateUnmanagedAccessor().Indent();
var classObjectHelperString = GenerateClassObjectHelper().Indent();

var partialClassString = GeneratePartialClass(interfaceString, managedAccessorString, unmanagedAccessorString, classObjectHelperString);
var partialClassString = GeneratePartialClass();

return $@"// <auto-generated />
#nullable enable
Expand Down Expand Up @@ -129,7 +128,7 @@ internal interface {_accessorInterfaceName} : Realms.IRealmAccessor
}}";
}

private string GeneratePartialClass(string interfaceString, string managedAccessorString, string unmanagedAccessorString, string classObjectHelperString)
private string GeneratePartialClass()
{
var schemaProperties = new StringBuilder();
var copyToRealm = new StringBuilder();
Expand Down Expand Up @@ -496,15 +495,24 @@ public override bool Equals(object? obj)
[Woven(typeof({_helperClassName})), Realms.Preserve(AllMembers = true)]
{SyntaxFacts.GetText(_classInfo.Accessibility)} partial class {_classInfo.Name} : {baseInterface}, INotifyPropertyChanged, IReflectableType
{{
[Realms.Preserve]
static {_classInfo.Name}()
{{
Realms.Serialization.RealmObjectSerializer.Register(new {_serializerClassName}());
}}
{contents.Indent()}
{classObjectHelperString}
{GenerateClassObjectHelper().Indent()}
{GenerateInterface().Indent()}
{interfaceString}
{GenerateManagedAccessor().Indent()}
{managedAccessorString}
{GenerateUnmanagedAccessor().Indent()}
{unmanagedAccessorString}
{GenerateSerializer().Indent()}
}}";

foreach (var enclosingClass in _classInfo.EnclosingClasses)
Expand Down Expand Up @@ -583,23 +591,21 @@ private string GenerateUnmanagedAccessor()
}
else
{
var propertyMapToName = property.GetMappedOrOriginalName();

string constructorString;

switch (property.TypeInfo.CollectionType)
{
case CollectionType.List:
constructorString = $"new List<{internalType}>()";
getListValueLines.AppendLine($@"""{propertyMapToName}"" => (IList<T>){property.Name},");
getListValueLines.AppendLine($@"""{stringName}"" => (IList<T>){property.Name},");
break;
case CollectionType.Set:
constructorString = $"new HashSet<{internalType}>(RealmSet<{internalType}>.Comparer)";
getSetValueLines.AppendLine($@"""{propertyMapToName}"" => (ISet<T>){property.Name},");
getSetValueLines.AppendLine($@"""{stringName}"" => (ISet<T>){property.Name},");
break;
case CollectionType.Dictionary:
constructorString = $"new Dictionary<string, {internalType}>()";
getDictionaryValueLines.AppendLine($@"""{propertyMapToName}"" => (IDictionary<string, TValue>){property.Name},");
getDictionaryValueLines.AppendLine($@"""{stringName}"" => (IDictionary<string, TValue>){property.Name},");
break;
default:
throw new NotImplementedException($"Collection {property.TypeInfo.CollectionType} is not supported yet");
Expand Down Expand Up @@ -756,7 +762,7 @@ private string GenerateUnmanagedAccessor()
}

return $@"[EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)]
internal class {_unmanagedAccessorClassName} : Realms.UnmanagedAccessor, {_accessorInterfaceName}
private class {_unmanagedAccessorClassName} : Realms.UnmanagedAccessor, {_accessorInterfaceName}
{{
public override ObjectSchema ObjectSchema => {_classInfo.Name}.RealmSchema;
Expand Down Expand Up @@ -865,12 +871,140 @@ private string GenerateManagedAccessor()
}

return $@"[EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)]
internal class {_managedAccessorClassName} : Realms.ManagedAccessor, {_accessorInterfaceName}
private class {_managedAccessorClassName} : Realms.ManagedAccessor, {_accessorInterfaceName}
{{
{propertiesBuilder.Indent(trimNewLines: true)}
}}";
}

private string GenerateSerializer()
{
var serializeValueLines = new StringBuilder();
var readValueLines = new StringBuilder();
var readArrayElementLines = new StringBuilder();
var readDocumentFieldLines = new StringBuilder();
var readArrayLines = new StringBuilder();
var readDictionaryLines = new StringBuilder();

foreach (var property in _classInfo.Properties)
{
var name = property.Name;
var stringName = property.GetMappedOrOriginalName();

if (property.TypeInfo.IsBacklink)
{
continue; // Backlinks are not de/serialized
}
else if (property.TypeInfo.IsCollection)
{
serializeValueLines.AppendLine($"Write{property.TypeInfo.CollectionType}(context, args, \"{stringName}\", value.{name});");
if (property.TypeInfo.IsDictionary)
{
var type = property.TypeInfo.GetCorrectlyAnnotatedTypeName(property.IsRequired).InternalType;

var deserialize = property.TypeInfo.InternalType!.ObjectType is ObjectType.None or ObjectType.EmbeddedObject
? $"BsonSerializer.LookupSerializer<{type}>().Deserialize(context)"
: $"Realms.Serialization.RealmObjectSerializer.LookupSerializer<{type}>()!.DeserializeById(context)!";

readDocumentFieldLines.AppendLine($@"case ""{stringName}"":
instance.{name}[fieldName] = {deserialize};
break;");

readDictionaryLines.AppendLine($@"case ""{stringName}"":");
}
else
{
var type = property.TypeInfo.GetCorrectlyAnnotatedTypeName(property.IsRequired).InternalType;

var deserialize = property.TypeInfo.InternalType!.ObjectType is ObjectType.None or ObjectType.EmbeddedObject
? $"BsonSerializer.LookupSerializer<{type}>().Deserialize(context)"
: $"Realms.Serialization.RealmObjectSerializer.LookupSerializer<{type}>()!.DeserializeById(context)!";

readArrayElementLines.AppendLine($@"case ""{stringName}"":
instance.{name}.Add({deserialize});
break;");
readArrayLines.AppendLine($@"case ""{stringName}"":");
}
}
else
{
var type = property.TypeInfo.GetCorrectlyAnnotatedTypeName(property.IsRequired).CompleteType;

serializeValueLines.AppendLine($"WriteValue(context, args, \"{stringName}\", value.{name});");
var deserialize = property.TypeInfo.ObjectType is ObjectType.None or ObjectType.EmbeddedObject
? $"BsonSerializer.LookupSerializer<{type}>().Deserialize(context)"
: $"Realms.Serialization.RealmObjectSerializer.LookupSerializer<{type}>()!.DeserializeById(context)";
readValueLines.AppendLine($@"case ""{stringName}"":
instance.{name} = {deserialize};
break;");
}
}

if (readArrayLines.Length > 0)
{
readValueLines.Append(readArrayLines);
readValueLines.AppendLine(@" ReadArray(instance, name, context);
break;");
}

if (readDictionaryLines.Length > 0)
{
readValueLines.Append(readDictionaryLines);
readValueLines.AppendLine(@" ReadDictionary(instance, name, context);
break;");
}

return $@"[EditorBrowsable(EditorBrowsableState.Never), Realms.Preserve(AllMembers = true)]
private class {_serializerClassName} : Realms.Serialization.RealmObjectSerializerBase<{_classInfo.Name}>
{{
public override string SchemaName => ""{_classInfo.MapTo ?? _classInfo.Name}"";
protected override void SerializeValue(MongoDB.Bson.Serialization.BsonSerializationContext context, BsonSerializationArgs args, {_classInfo.Name} value)
{{
context.Writer.WriteStartDocument();
{serializeValueLines.Indent(2, trimNewLines: true)}
context.Writer.WriteEndDocument();
}}
protected override {_classInfo.Name} CreateInstance() => new {_classInfo.Name}();
protected override void ReadValue({_classInfo.Name} instance, string name, BsonDeserializationContext context)
{{
{(readValueLines.Length == 0
? "// No Realm properties to deserialize"
: $@"switch (name)
{{
{readValueLines.Indent(trimNewLines: true)}
default:
context.Reader.SkipValue();
break;
}}").Indent(2)}
}}
protected override void ReadArrayElement({_classInfo.Name} instance, string name, BsonDeserializationContext context)
{{
{(readArrayElementLines.Length == 0
? "// No persisted list/set properties to deserialize"
: $@"switch (name)
{{
{readArrayElementLines.Indent(trimNewLines: true)}
}}").Indent(2)}
}}
protected override void ReadDocumentField({_classInfo.Name} instance, string name, string fieldName, BsonDeserializationContext context)
{{
{(readDocumentFieldLines.Length == 0
? "// No persisted dictionary properties to deserialize"
: $@"switch (name)
{{
{readDocumentFieldLines.Indent(trimNewLines: true)}
}}").Indent(2)}
}}
}}";
}

private static string GetBackingFieldName(string propertyName)
{
return "_" + char.ToLowerInvariant(propertyName[0]) + propertyName[1..];
Expand Down
2 changes: 1 addition & 1 deletion Realm/Realm.SourceGenerator/Realm.SourceGenerator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
</ItemGroup>
</Target>
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" GeneratePathProperty="true" PrivateAssets="all" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" GeneratePathProperty="true" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion Realm/Realm.UnityUtils/Realm.UnityUtils.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="7.0.1">
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
2 changes: 1 addition & 1 deletion Realm/Realm/Handles/SyncUserHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ public async Task<string> CallFunctionAsync(AppHandle app, string name, string a
NativeMethods.call_function(this, app, name, name.IntPtrLength(), args, args.IntPtrLength(), service, service.IntPtrLength(), GCHandle.ToIntPtr(tcsHandle), out var ex);
ex.ThrowIfNecessary();

return await tcs.Task;
return await tcs.Task; //.NET Host locks the dll when there's an exception here (coming from native)

Check warning on line 244 in Realm/Realm/Handles/SyncUserHandle.cs

View workflow job for this annotation

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

Check warning on line 244 in Realm/Realm/Handles/SyncUserHandle.cs

View workflow job for this annotation

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

Check warning on line 244 in Realm/Realm/Handles/SyncUserHandle.cs

View workflow job for this annotation

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

Check warning on line 244 in Realm/Realm/Handles/SyncUserHandle.cs

View workflow job for this annotation

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

Check warning on line 244 in Realm/Realm/Handles/SyncUserHandle.cs

View workflow job for this annotation

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

Check warning on line 244 in Realm/Realm/Handles/SyncUserHandle.cs

View workflow job for this annotation

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

Check warning on line 244 in Realm/Realm/Handles/SyncUserHandle.cs

View workflow job for this annotation

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

Check warning on line 244 in Realm/Realm/Handles/SyncUserHandle.cs

View workflow job for this annotation

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

Check warning on line 244 in Realm/Realm/Handles/SyncUserHandle.cs

View workflow job for this annotation

GitHub Actions / Test / Weaver (macos-latest, osx-x64)

Check warning on line 244 in Realm/Realm/Handles/SyncUserHandle.cs

View workflow job for this annotation

GitHub Actions / Test / Weaver (macos-latest, osx-x64)

Check warning on line 244 in Realm/Realm/Handles/SyncUserHandle.cs

View workflow job for this annotation

GitHub Actions / Test / Weaver (macos-latest, osx-x64)

Check warning on line 244 in Realm/Realm/Handles/SyncUserHandle.cs

View workflow job for this annotation

GitHub Actions / Test / Weaver (macos-latest, osx-x64)

Check warning on line 244 in Realm/Realm/Handles/SyncUserHandle.cs

View workflow job for this annotation

GitHub Actions / Package / NuGet

Check warning on line 244 in Realm/Realm/Handles/SyncUserHandle.cs

View workflow job for this annotation

GitHub Actions / Package / NuGet

Check warning on line 244 in Realm/Realm/Handles/SyncUserHandle.cs

View workflow job for this annotation

GitHub Actions / Analyze C#

Check warning on line 244 in Realm/Realm/Handles/SyncUserHandle.cs

View workflow job for this annotation

GitHub Actions / Analyze C#

Check warning on line 244 in Realm/Realm/Handles/SyncUserHandle.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=net6.0]
}
finally
{
Expand Down
Loading

0 comments on commit c4546f9

Please sign in to comment.