Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added missing migration methods #2635

Merged
merged 16 commits into from
Sep 29, 2021
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
## vNext (TBD)

### Enhancements
* Added two new methods on `Migration` (Issue [#2543](https://github.com/realm/realm-dotnet/issues/2543)):
* `RemoveType(typeName)` allows to completely remove a type and its schema from a realm during a migration.
* `RenameProperty(typeName, oldPropertyName, newPropertyName)` allows to rename a property during a migration.
* A Realm Schema can now be constructed at runtime as opposed to generated automatically from the model classes. The automatic generation continues to work and should cover the needs of the vast majority of Realm users. Manually constructing the schema may be required when the shape of the objects depends on some information only known at runtime or in very rare cases where it may provide performance benefits by representing a collection of known size as properties on the class. (Issue [#824](https://github.com/realm/realm-dotnet/issues/824))
* `RealmConfiguration.ObjectClasses` has now been deprecated in favor of `RealmConfiguration.Schema`. `RealmSchema` has an implicit conversion operator from `Type[]` so code that previously looked like `ObjectClasses = new[] { typeof(Foo), typeof(Bar) }` can be trivially updated to `Schema = new[] { typeof(Foo), typeof(Bar) }`.
* `Property` has been converted to a read-only struct by removing the setters from its properties. Those didn't do anything previously, so we don't expect anyone was using them.
Expand Down
33 changes: 30 additions & 3 deletions Realm/Realm/Handles/SharedRealmHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ private static class NativeMethods
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void LogMessageCallback(PrimitiveValue message, LogLevel level);

// migrationSchema is a special schema that is used only in the context of a migration block.
// It is a pointer because we need to be able to modify this schema in some migration methods directly in core.
[return: MarshalAs(UnmanagedType.U1)]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate bool MigrationCallback(IntPtr oldRealm, IntPtr newRealm, Native.Schema oldSchema, ulong schemaVersion, IntPtr managedMigrationHandle);
internal delegate bool MigrationCallback(IntPtr oldRealm, IntPtr newRealm, IntPtr migrationSchema, Native.Schema oldSchema, ulong schemaVersion, IntPtr managedMigrationHandle);

[return: MarshalAs(UnmanagedType.U1)]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
Expand Down Expand Up @@ -181,6 +183,17 @@ public static extern void install_callbacks(
[DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_create_results", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr create_results(SharedRealmHandle sharedRealm, UInt32 table_key, out NativeException ex);

[DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_rename_property", CallingConvention = CallingConvention.Cdecl)]
public static extern void rename_property(SharedRealmHandle sharedRealm,
[MarshalAs(UnmanagedType.LPWStr)] string typeName, IntPtr typeNameLength,
[MarshalAs(UnmanagedType.LPWStr)] string oldName, IntPtr oldNameLength,
[MarshalAs(UnmanagedType.LPWStr)] string newName, IntPtr newNameLength,
IntPtr migrationSchema,
out NativeException ex);

[DllImport(InteropConfig.DLL_NAME, EntryPoint = "shared_realm_remove_type", CallingConvention = CallingConvention.Cdecl)]
public static extern bool remove_type(SharedRealmHandle sharedRealm, [MarshalAs(UnmanagedType.LPWStr)] string typeName, IntPtr typeLength, out NativeException ex);

#pragma warning restore SA1121 // Use built-in type alias
#pragma warning restore IDE0049 // Use built-in type alias
}
Expand Down Expand Up @@ -464,6 +477,20 @@ public bool TryFindObject(TableKey tableKey, in RealmValue id, out ObjectHandle
return true;
}

public void RenameProperty(string typeName, string oldName, string newName, IntPtr migrationSchema)
{
NativeMethods.rename_property(this, typeName, (IntPtr)typeName.Length,
oldName, (IntPtr)oldName.Length, newName, (IntPtr)newName.Length, migrationSchema, out var nativeException);
nativeException.ThrowIfNecessary();
}

public bool RemoveType(string typeName)
{
var result = NativeMethods.remove_type(this, typeName, (IntPtr)typeName.Length, out var nativeException);
nativeException.ThrowIfNecessary();
return result;
}

public ResultsHandle CreateResults(TableKey tableKey)
{
var result = NativeMethods.create_results(this, tableKey.Value, out var nativeException);
Expand Down Expand Up @@ -521,7 +548,7 @@ private static void LogMessage(PrimitiveValue message, LogLevel level)
}

[MonoPInvokeCallback(typeof(NativeMethods.MigrationCallback))]
private static bool OnMigration(IntPtr oldRealmPtr, IntPtr newRealmPtr, Native.Schema oldSchema, ulong schemaVersion, IntPtr managedMigrationHandle)
private static bool OnMigration(IntPtr oldRealmPtr, IntPtr newRealmPtr, IntPtr migrationSchema, Native.Schema oldSchema, ulong schemaVersion, IntPtr managedMigrationHandle)
{
var migrationHandle = GCHandle.FromIntPtr(managedMigrationHandle);
var migration = (Migration)migrationHandle.Target;
Expand All @@ -538,7 +565,7 @@ private static bool OnMigration(IntPtr oldRealmPtr, IntPtr newRealmPtr, Native.S
var newRealmHandle = new UnownedRealmHandle(newRealmPtr);
var newRealm = new Realm(newRealmHandle, migration.Configuration, migration.Schema);

var result = migration.Execute(oldRealm, newRealm);
var result = migration.Execute(oldRealm, newRealm, migrationSchema);

return result;
}
Expand Down
66 changes: 65 additions & 1 deletion Realm/Realm/Migration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

using System;
using System.Runtime.InteropServices;
using Realms.Helpers;
using Realms.Schema;

namespace Realms
Expand All @@ -34,6 +35,7 @@ namespace Realms
public class Migration
{
private GCHandle? _handle;
private IntPtr _migrationSchema;

internal GCHandle MigrationHandle => _handle ?? throw new ObjectDisposedException(nameof(Migration));

Expand Down Expand Up @@ -62,10 +64,11 @@ internal Migration(RealmConfiguration configuration, RealmSchema schema)
_handle = GCHandle.Alloc(this);
}

internal bool Execute(Realm oldRealm, Realm newRealm)
internal bool Execute(Realm oldRealm, Realm newRealm, IntPtr migrationSchema)
{
OldRealm = oldRealm;
NewRealm = newRealm;
_migrationSchema = migrationSchema;

try
{
Expand All @@ -83,11 +86,72 @@ internal bool Execute(Realm oldRealm, Realm newRealm)

NewRealm.Dispose();
NewRealm = null;

_migrationSchema = IntPtr.Zero;
}

return true;
}

/// <summary>
/// Removes a type during a migration. All the data associated with the type, as well as its schema, will be removed from <see cref="Realm"/>.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to differentiate between the (general) schema and the object schema to not confuse anyone here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's clear enough with the "its", but maybe it's not?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clear enough for me because I know how Realm works and what the difference between a schema and an object schema is. Then again, how important is this for a user? They just want to use the framework and not understand too much about the internals I suppose. I guess it's fine both ways. ;)

/// </summary>
/// <param name="typeName">The type that needs to be removed. </param>
/// <remarks>
/// The removed type will still be accessible from <see cref="OldRealm"/> in the migration block.
/// The type must not be present in the new schema. <see cref="Realm.RemoveAll{T}"/> can be used on <see cref="NewRealm"/> if one needs to delete the content of the table.
/// </remarks>
/// <returns><c>true</c> if the type does exist in the old schema, <c>false</c> otherwise.</returns>
public bool RemoveType(string typeName)
{
Argument.NotNullOrEmpty(typeName, nameof(typeName));
return NewRealm.SharedRealmHandle.RemoveType(typeName);
}

/// <summary>
/// Renames a property during a migration.
/// </summary>
/// <param name="typeName">The type for which the property rename needs to be performed. </param>
/// <param name="oldPropertyName">The previous name of the property. </param>
/// <param name="newPropertyName">The new name of the property. </param>
/// <remarks>
/// It is not possible to access the renamed property in <see cref="NewRealm"/> in the migration block after this method is called.
/// If it is necessary to access the renamed property, the method should be called after the property access, or the value retrieved from <see cref="OldRealm"/> can be used.
/// </remarks>
/// <example>
/// <code>
/// // Model in the old schema
/// class Dog : RealmObject
/// {
/// public string DogName { get; set; }
/// }
///
/// // Model in the new schema
/// class Dog : RealmObject
/// {
/// public string Name { get; set; }
/// }
///
/// //After the migration Dog.Name will contain the same values as Dog.DogName from the old realm, without the need to copy them explicitly
/// var config = new RealmConfiguration
/// {
/// SchemaVersion = 1,
/// MigrationCallback = (migration, oldSchemaVersion) =>
/// {
/// migration.RenameProperty("Dog", "DogName", "Name");
/// }
/// };
/// </code>
/// </example>
public void RenameProperty(string typeName, string oldPropertyName, string newPropertyName)
{
Argument.NotNullOrEmpty(typeName, nameof(typeName));
Argument.NotNullOrEmpty(oldPropertyName, nameof(oldPropertyName));
Argument.NotNullOrEmpty(newPropertyName, nameof(newPropertyName));

NewRealm.SharedRealmHandle.RenameProperty(typeName, oldPropertyName, newPropertyName, _migrationSchema);
}

internal void ReleaseHandle()
{
_handle?.Free();
Expand Down
Loading