NSMigrationManager.migrateStore with NSPersistentHistoryTrackingKey

Viewed 141

I have a core data implementation. The stack is loaded using a NSPersistentContainer. During setup, I set the NSPersistentHistoryTrackingKey on the NSPersistentStoreDescription.

description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)

I'm trying to implement progressive migrations along this line https://williamboles.me/progressive-core-data-migration/ (fantastic article, by the way!)

The first problem I ran into was forcing checkpointing in the WAL. The code is pretty straightforward:

func forceWALCheckpointingForStore(at storeURL: URL) {
guard let metadata = NSPersistentStoreCoordinator.metadata(at: storeURL), let currentModel = NSManagedObjectModel.compatibleModelForStoreMetadata(metadata) else {
    return
}

do {
    let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: currentModel)

    let options = [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
    let store = persistentStoreCoordinator.addPersistentStore(at: storeURL, options: options)
    try persistentStoreCoordinator.remove(store)
} catch let error {
    fatalError("failed to force WAL checkpointing, error: \(error)")
  }
}

The problem occurs when the NSPersistentStoreCoordinator runs addPersistentStore. I get the following error:

Store opened without NSPersistentHistoryTrackingKey but previously had been opened with NSPersistentHistoryTrackingKey - Forcing into Read Only mode store at url...

This makes perfect sense. The Core Data framework created additional tables (NSPersistentHistoryToken, NSPersistentHistoryTransaction etc..) that you have access to to manage changes in history. If you "open" or "access" the database without the history tracking option, the Core Data framework puts the database in Read Only mode to avoid data integrity issues.

As far as I can see in the documentation, the NSPersistentHistoryTrackingKey can only be set on the container, and not on the NSPersistentStoreCoordinator directly (via options).

Trying to stay "within the boundaries of the Container", I decided to call addPersistentStore (with the Pragmas Option) on the "persistentStoreCoordinator" property of the Container. The problem with this approach is that the container is instantiated with the latest version NSManagedObjectModel. Because we're smack bang in the middle of a migration process, the migration hasn't happened yet. Attempting to manipulate the store via the store coordinator inside the container in any way results in this error:

The model used to open the store is incompatible with the one used to create the store.

I had to therefore instantiate a container with the existing version of the model. Further research also revealed that I can force a WAL checkpoint via the NSPersistent container (and avoid having to use the coordinator) by setting the following option on the container description:

description.setValue("DELETE" as NSObject, forPragmaNamed: "journal_mode")

I created a temporary pre-migration container, instantiated it with the current (old) version of the managed object model, and set the above pragmas option to force a WAL checkpoint. It worked like a charm! The existing database was now ready to be migrated to the new model version(s).

The migration process kicks off and I hit a wall here:

NSMigrationManager.migrateStore(from: currentURL, sourceType: NSSQLiteStoreType, options: nil, with: mappingModel, toDestinationURL: destinationURL, destinationType: NSSQLiteStoreType, destinationOptions: nil)

Once again, we're back to the original problem:

Store opened without NSPersistentHistoryTrackingKey but previously had been opened with NSPersistentHistoryTrackingKey - Forcing into Read Only mode store at url...

I want to migrate my database WITH the history tracking option enabled. After all, the history tracking tables created by the Core Data framework must also be migrated to the new version of the database. But, I don't know how to achieve this with the available Core Data classes. It is always best to stay as close to the vendor recommended implementations as possible and not do weird workarounds.

Here's what I know:

  • With lightweight migration options set on my Container, I can create new model versions to my heart's content!
  • With the NSPersistentHistoryTrackingKey also set on the same container, the Core Data framework automatically migrates my store from one model version to the next without missing a beat!
  • Therefore, if I now want to migrate my database manually with all the options set on the container, I should be able to do it because if Core Data can do it, I should be able to do it.... yes?

The documentation on these issues is a bit light. One of two things is happening here. Either the documentation is not updated OR ... I'm trying to do the weirdest thing known to man and it should never be done .... ever...

0 Answers
Related