From 8abaebc75de4684f68f26091169df58e60ad5bb4 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Mon, 31 Jan 2022 23:04:37 +0100 Subject: [PATCH 1/3] Allow setting primary keys during migration --- Realm/Realm/DatabaseTypes/RealmObjectBase.cs | 9 +- Realm/Realm/Handles/SharedRealmHandle.cs | 2 +- Realm/Realm/Realm.cs | 4 +- Tests/Realm.Tests/Database/MigrationTests.cs | 121 ++++++++++++++++++- 4 files changed, 132 insertions(+), 4 deletions(-) diff --git a/Realm/Realm/DatabaseTypes/RealmObjectBase.cs b/Realm/Realm/DatabaseTypes/RealmObjectBase.cs index cec8e72e80..1784318267 100644 --- a/Realm/Realm/DatabaseTypes/RealmObjectBase.cs +++ b/Realm/Realm/DatabaseTypes/RealmObjectBase.cs @@ -224,7 +224,14 @@ protected void SetValueUnique(string propertyName, RealmValue val) { Debug.Assert(IsManaged, "Object is not managed, but managed access was attempted"); - _objectHandle.SetValueUnique(propertyName, _metadata, val); + if (_realm.IsInMigration) + { + _objectHandle.SetValue(propertyName, _metadata, val, _realm); + } + else + { + _objectHandle.SetValueUnique(propertyName, _metadata, val); + } } protected internal IList GetListValue(string propertyName) diff --git a/Realm/Realm/Handles/SharedRealmHandle.cs b/Realm/Realm/Handles/SharedRealmHandle.cs index 59f56c0096..4ca7344879 100644 --- a/Realm/Realm/Handles/SharedRealmHandle.cs +++ b/Realm/Realm/Handles/SharedRealmHandle.cs @@ -594,7 +594,7 @@ private static bool OnMigration(IntPtr oldRealmPtr, IntPtr newRealmPtr, IntPtr m var oldRealm = new Realm(oldRealmHandle, oldConfiguration, RealmSchema.CreateFromObjectStoreSchema(oldSchema)); var newRealmHandle = new UnownedRealmHandle(newRealmPtr); - var newRealm = new Realm(newRealmHandle, migration.Configuration, migration.Schema); + var newRealm = new Realm(newRealmHandle, migration.Configuration, migration.Schema, isInMigration: true); var result = migration.Execute(oldRealm, newRealm, migrationSchema); diff --git a/Realm/Realm/Realm.cs b/Realm/Realm/Realm.cs index 36367e5101..caf87f293e 100644 --- a/Realm/Realm/Realm.cs +++ b/Realm/Realm/Realm.cs @@ -158,6 +158,7 @@ public static void DeleteRealm(RealmConfigurationBase configuration) internal readonly SharedRealmHandle SharedRealmHandle; internal readonly RealmMetadata Metadata; + internal readonly bool IsInMigration; /// /// Gets an object encompassing the dynamic API for this Realm instance. @@ -283,9 +284,10 @@ public SubscriptionSet Subscriptions } } - internal Realm(SharedRealmHandle sharedRealmHandle, RealmConfigurationBase config, RealmSchema schema) + internal Realm(SharedRealmHandle sharedRealmHandle, RealmConfigurationBase config, RealmSchema schema, bool isInMigration = false) { Config = config; + IsInMigration = isInMigration; if (config.EnableCache && sharedRealmHandle.OwnsNativeRealm) { diff --git a/Tests/Realm.Tests/Database/MigrationTests.cs b/Tests/Realm.Tests/Database/MigrationTests.cs index 9e529f0350..8fdef4280b 100644 --- a/Tests/Realm.Tests/Database/MigrationTests.cs +++ b/Tests/Realm.Tests/Database/MigrationTests.cs @@ -18,7 +18,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using NUnit.Framework; using Realms.Exceptions; @@ -408,5 +407,125 @@ public void MigrationRemoveTypeInvalidArguments() using var realm = GetRealm(newRealmConfig); } + + [Test] + public void Migration_ChangePrimaryKey_Dynamic() + { + var oldRealmConfig = new RealmConfiguration(Guid.NewGuid().ToString()); + using (var oldRealm = GetRealm(oldRealmConfig)) + { + oldRealm.Write(() => + { + oldRealm.Add(new IntPrimaryKeyWithValueObject + { + Id = 123, + StringValue = "123" + }); + }); + } + + var newRealmConfig = new RealmConfiguration(oldRealmConfig.DatabasePath) + { + SchemaVersion = 1, + MigrationCallback = (migration, oldSchemaVersion) => + { + var value = (RealmObjectBase)migration.NewRealm.DynamicApi.Find(nameof(IntPrimaryKeyWithValueObject), 123); + value.DynamicApi.Set("_id", 456); + } + }; + + using var realm = GetRealm(newRealmConfig); + + var obj123 = realm.Find(123); + var obj456 = realm.Find(456); + + Assert.That(obj123, Is.Null); + Assert.That(obj456, Is.Not.Null); + Assert.That(obj456.StringValue, Is.EqualTo("123")); + } + + [Test] + public void Migration_ChangePrimaryKey_Static() + { + var oldRealmConfig = new RealmConfiguration(Guid.NewGuid().ToString()); + using (var oldRealm = GetRealm(oldRealmConfig)) + { + oldRealm.Write(() => + { + oldRealm.Add(new IntPrimaryKeyWithValueObject + { + Id = 123, + StringValue = "123" + }); + }); + } + + var newRealmConfig = new RealmConfiguration(oldRealmConfig.DatabasePath) + { + SchemaVersion = 1, + MigrationCallback = (migration, oldSchemaVersion) => + { + var value = migration.NewRealm.Find(123); + value.Id = 456; + } + }; + + using var realm = GetRealm(newRealmConfig); + + var obj123 = realm.Find(123); + var obj456 = realm.Find(456); + + Assert.That(obj123, Is.Null); + Assert.That(obj456, Is.Not.Null); + Assert.That(obj456.StringValue, Is.EqualTo("123")); + } + + [Test] + public void Migration_ChangePrimaryKey_WithDuplicates_Throws() + { + var oldRealmConfig = new RealmConfiguration(Guid.NewGuid().ToString()); + using (var oldRealm = GetRealm(oldRealmConfig)) + { + oldRealm.Write(() => + { + oldRealm.Add(new IntPrimaryKeyWithValueObject + { + Id = 1, + StringValue = "1" + }); + oldRealm.Add(new IntPrimaryKeyWithValueObject + { + Id = 2, + StringValue = "2" + }); + }); + } + + var newRealmConfig = new RealmConfiguration(oldRealmConfig.DatabasePath) + { + SchemaVersion = 1, + MigrationCallback = (migration, oldSchemaVersion) => + { + new Action(() => + { + var value = migration.NewRealm.Find(1); + value.Id = 2; + })(); + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true); + } + }; + + var ex = Assert.Throws(() => GetRealm(newRealmConfig)); + Assert.That(ex.Message, Does.Contain($"{nameof(IntPrimaryKeyWithValueObject)}._id")); + + // Ensure we haven't messed up the data + using var oldRealmAgain = GetRealm(oldRealmConfig); + + var obj1 = oldRealmAgain.Find(1); + var obj2 = oldRealmAgain.Find(2); + + Assert.That(obj1.StringValue, Is.EqualTo("1")); + Assert.That(obj2.StringValue, Is.EqualTo("2")); + } } } From 2e4816aeda943f0be3d67b02965f4bef3ea4b6b4 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Mon, 7 Feb 2022 12:08:54 +0100 Subject: [PATCH 2/3] Add changelog and more tests --- CHANGELOG.md | 16 ++++- Realm/Realm/Realm.cs | 7 +- Tests/Realm.Tests/Database/MigrationTests.cs | 74 ++++++++++++++++++-- 3 files changed, 89 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3da6a3105..d523aea8f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,21 @@ ## vNext (TBD) ### Enhancements -* None +* Lifted a limitation that would prevent you from changing the primary key of objects during a migration. It is now possible to do it with both the dynamic and the strongly-typed API: + ```csharp + var config = new RealmConfiguration + { + SchemaVersion = 5, + MigrationCallback = (migration, oldVersion) => + { + // Increment the primary key value of all Foos + foreach (var obj in realm.All()) + { + obj.Id = obj.Id + 1000; + } + } + } + ``` ### Fixed * Fixed an issue with xUnit tests that would cause `System.Runtime.InteropServices.SEHException` to be thrown whenever Realm was accessed in a non-async test. (Issue [#1865](https://github.com/realm/realm-dotnet/issues/1865)) diff --git a/Realm/Realm/Realm.cs b/Realm/Realm/Realm.cs index a2a7c4ec9e..e3513cfe79 100644 --- a/Realm/Realm/Realm.cs +++ b/Realm/Realm/Realm.cs @@ -1191,7 +1191,12 @@ public T ResolveReference(ThreadSafeReference.Object reference) return null; } - return (T)MakeObject(reference.Metadata, objectHandle); + if (!Metadata.TryGetValue(reference.Metadata.Schema.Name, out var metadata)) + { + metadata = reference.Metadata; + } + + return (T)MakeObject(metadata, objectHandle); } /// diff --git a/Tests/Realm.Tests/Database/MigrationTests.cs b/Tests/Realm.Tests/Database/MigrationTests.cs index 2a65724846..125e9da136 100644 --- a/Tests/Realm.Tests/Database/MigrationTests.cs +++ b/Tests/Realm.Tests/Database/MigrationTests.cs @@ -629,6 +629,52 @@ public void Migration_ChangePrimaryKey_Static() Assert.That(obj456.StringValue, Is.EqualTo("123")); } + [Test] + public void Migration_ChangePrimaryKeyType() + { + var oldRealmConfig = new RealmConfiguration(Guid.NewGuid().ToString()) + { + Schema = new[] { typeof(ObjectV1) } + }; + + using (var oldRealm = GetRealm(oldRealmConfig)) + { + oldRealm.Write(() => + { + oldRealm.Add(new ObjectV1 + { + Id = 1, + Value = "foo" + }); + + oldRealm.Add(new ObjectV1 + { + Id = 2, + Value = "bar" + }); + }); + } + + var newRealmConfig = new RealmConfiguration(oldRealmConfig.DatabasePath) + { + SchemaVersion = 1, + Schema = new[] { typeof(ObjectV2) }, + MigrationCallback = (migration, oldSchemaVersion) => + { + foreach (var oldObj in (IQueryable)migration.OldRealm.DynamicApi.All("Object")) + { + var newObj = (ObjectV2)migration.NewRealm.ResolveReference(ThreadSafeReference.Create(oldObj)); + newObj.Id = oldObj.DynamicApi.Get("Id").ToString(); + } + } + }; + + using var realm = GetRealm(newRealmConfig); + + Assert.That(realm.All().AsEnumerable().Select(o => o.Value), Is.EquivalentTo(new[] { "foo", "bar" })); + Assert.That(realm.All().AsEnumerable().Select(o => o.Id), Is.EquivalentTo(new[] { "1", "2" })); + } + [Test] public void Migration_ChangePrimaryKey_WithDuplicates_Throws() { @@ -655,12 +701,8 @@ public void Migration_ChangePrimaryKey_WithDuplicates_Throws() SchemaVersion = 1, MigrationCallback = (migration, oldSchemaVersion) => { - new Action(() => - { - var value = migration.NewRealm.Find(1); - value.Id = 2; - })(); - GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true); + var value = migration.NewRealm.Find(1); + value.Id = 2; } }; @@ -676,5 +718,25 @@ public void Migration_ChangePrimaryKey_WithDuplicates_Throws() Assert.That(obj1.StringValue, Is.EqualTo("1")); Assert.That(obj2.StringValue, Is.EqualTo("2")); } + + [Explicit] + [MapTo("Object")] + private class ObjectV1 : RealmObject + { + [PrimaryKey] + public int Id { get; set; } + + public string Value { get; set; } + } + + [Explicit] + [MapTo("Object")] + private class ObjectV2 : RealmObject + { + [PrimaryKey] + public string Id { get; set; } + + public string Value { get; set; } + } } } From 7479e7d6e8988749a5c33549ea32a737d22d40b7 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Tue, 8 Feb 2022 14:23:59 +0100 Subject: [PATCH 3/3] Update CHANGELOG.md Co-authored-by: Yavor Georgiev --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d523aea8f1..b65b1fc9c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ MigrationCallback = (migration, oldVersion) => { // Increment the primary key value of all Foos - foreach (var obj in realm.All()) + foreach (var obj in migration.NewRealm.All()) { obj.Id = obj.Id + 1000; }