Automatic Lightweight Migration without explicitly create model versions?
HI @JohnEstropia
i would like to ask about lightweight migration in CoreStore
i watch at WWDC video https://developer.apple.com/videos/play/wwdc2022/10120/ that we can do lightweight migration without creating new model version.
Does CoreStore has support to do that?
Since i have many model/table that will be a tedious process to duplicate previous model into new version and make adjustment in new version model
thank you in advance 🙏
I think you misunderstood. Lightweight migrations allow for migrations between two model versions, without creating a Mapping Model. You still need to keep the models from your past versions.
With that in mind, CoreStore does support lightweight migrations. See https://github.com/JohnEstropia/CoreStore#migrations
Perhaps i will explain my case study
I have three table
enum V1 {
class User: CoreStoreObject {
@Field.Relationship("profile", inverse: \.$user, deleteRule: .cascade)
var profile: UserProfile?
@Field.Relationship("authentication", inverse: \.$user, deleteRule: .cascade)
var authentication: UserAuthentication?
}
class UserAuthentication: CoreStoreObject {
@Field.Stored("_loginMethod")
var _loginMethod: String? = nil
@Field.Stored("loginId")
var loginId: String? = nil
@Field.Stored("profileId")
var profileId: String? = nil
@Field.Stored("lastLoginTimetamp")
var lastLoginTimestamp: Int = 0
@Field.Stored("createdTimestamp")
var createdTimestamp: Int = 0
// Foreign Key Relationship
@Field.Relationship("user")
var user: User?
}
class UserProfile: CoreStoreObject {
@Field.Stored("name")
var name: String? = nil
@Field.Coded("phoneNumbers", coder: FieldCoders.Json.self)
var phoneNumbers: [String] = []
@Field.Relationship("emails", inverse: \.$userProfile, deleteRule: .cascade)
var emails: [UserEmail]
@Field.Stored("")
var photoUrl: String? = nil
// Foreign Key Relationship
@Field.Relationship("user")
var user: User?
}
class UserEmail: CoreStoreObject {
@Field.Stored("email")
var email: String = ""
// Foreign Key Relationship
@Field.Relationship("userProfile")
var userProfile: UserProfile?
}
}
and after i publish my app, i want to update my published app and add 2 column in UserAuthentication which isCorporateUser and isVerified, so i have to copy my previous CoreStoreObject from v1, into enum v2 and add 2 column into UserAuthentication
enum V2 {
class User: CoreStoreObject, ImportableObject {
typealias ImportSource = [String: Any]
@Field.Relationship("profile", inverse: \.$user, deleteRule: .cascade)
var profile: UserProfile?
@Field.Relationship("authentication", inverse: \.$user, deleteRule: .cascade)
var authentication: UserAuthentication?
func didInsert(from source: [String : Any], in transaction: CoreStore.BaseDataTransaction) throws {
let profileJson = source["profile"] as? UserProfile.ImportSource ?? [:]
profile = try transaction.importObject(Into<UserProfile>(), source: profileJson)
let authenticationJson = source["authentication"] as? UserProfile.ImportSource ?? [:]
authentication = try transaction.importObject(Into<UserAuthentication>(), source: authenticationJson)
}
}
class UserAuthentication: CoreStoreObject, ImportableObject {
typealias ImportSource = [String: Any]
@Field.Stored("_loginMethod")
var _loginMethod: String? = nil
@Field.Stored("loginId")
var loginId: String? = nil
@Field.Stored("profileId")
var profileId: String? = nil
@Field.Stored("lastLoginTimetamp")
var lastLoginTimestamp: Int = 0
@Field.Stored("createdTimestamp")
var createdTimestamp: Int = 0
// Foreign Key Relationship
@Field.Relationship("user")
var user: User?
// Additional Field
@Field.Stored("isCorporateUser")
var isCorporateUser: Bool = false
@Field.Stored("isVerified")
var isVerified: Bool = false
func didInsert(from source: [String : Any], in transaction: CoreStore.BaseDataTransaction) throws {
_loginMethod = source["_loginMethod"] as? String ?? ""
loginId = source["loginId"] as? String ?? ""
profileId = source["profileId"] as? String ?? ""
lastLoginTimestamp = source["lastLoginTimestamp"] as? Int ?? 0
createdTimestamp = source["createdTimestamp"] as? Int ?? 0
}
}
class UserProfile: CoreStoreObject, ImportableObject {
typealias ImportSource = [String: Any]
@Field.Stored("name")
var name: String? = nil
@Field.Coded("phoneNumbers", coder: FieldCoders.Json.self)
var phoneNumbers: [String] = []
@Field.Relationship("emails", inverse: \.$userProfile, deleteRule: .cascade)
var emails: [UserEmail]
@Field.Stored("photoUrl")
var photoUrl: String? = nil
// Foreign Key Relationship
@Field.Relationship("user")
var user: User?
func didInsert(from source: [String : Any], in transaction: CoreStore.BaseDataTransaction) throws {
name = source["name"] as? String ?? ""
phoneNumbers = source["phoneNumbers"] as? [String] ?? []
photoUrl = source["photoUrl"] as? String ?? ""
let emailJson: [UserEmail.ImportSource] = source["emails"] as? [UserEmail.ImportSource] ?? []
emails = try transaction.importObjects(Into<UserEmail>(), sourceArray: emailJson)
}
}
class UserEmail: CoreStoreObject, ImportableObject {
typealias ImportSource = [String: Any]
@Field.Stored("email")
var email: String = ""
@Field.Stored("domain")
var domain: String = ""
// Foreign Key Relationship
@Field.Relationship("userProfile")
var userProfile: UserProfile?
func didInsert(from source: [String : Any], in transaction: CoreStore.BaseDataTransaction) throws {
email = source["email"] as? String ?? ""
}
}
}
So my DataStack will be like this
let currentStack = DataStack(
CoreStoreSchema(
modelVersion: "v1",
entities: [
Entity<V1.User>("User"),
Entity<V1.UserProfile>("UserProfile"),
Entity<V1.UserAuthentication>("UserAuthentication"),
Entity<V1.UserEmail>("UserEmail")
],
versionLock: [
.....
]
),
CoreStoreSchema(
modelVersion: "v2",
entities: [
Entity<V2.User>("User"),
Entity<V2.UserProfile>("UserProfile"),
Entity<V2.UserAuthentication>("UserAuthentication"),
Entity<V2.UserEmail>("UserEmail")
]
),
migrationChain: ["v1", "v2"]
)
i'm wondering is it possible, to not create V2 and just add new column into UserAuthentication in V1?
i'm wondering is it possible
No, it won't be. Core Data needs to know the old model for it to determine if any migrations, including lightweight ones, are needed in the first place.
To save you some maintenance work when adding new versions, I recommend using typealiases for your model classes, as shown in some examples in the README:

This way, you'd only need to refer to V1 or V2 in your migration setup code, and your app can use the aliased names forever.
ah i see okay, thanks john @JohnEstropia May i know what is your suggestion if i have more than 50 CoreStoreObject that separated in several modules how to manage each object based on version?
For example i have 10 CoreStoreObject in Booking Module, 20 CoreStoreObject in Flight Module and 30 CoreStoreObject in Hotel Module each CoreStoreObject has relationship each other, so i can not be split into multiple database
@dwirandytlvk You're more familiar with your object relationships so this would be up to you, but you have many options:
- let the module that sets up your
DataStackdepend on all modules that contain your objects - separate all ORM-related code to its own module
- inject the
CoreStoreObjectsubclasses dynamically duringDataStacksetup.CoreStoreSchemadoesn't need static typing, just theEntity<T>instance passed asDynamicEntity: