I recently made some core data modifications to my LogDiver – my excellent log parsing tool and planned to use Automatic Migration to migrate existing databases.
The Problem
However, what I found was that every time I attempted to open a file with the old data model (thus triggering automatic migration), I would get the following error:
And my console reported:
encountered the following error: { NSLocalizedDescription = "The document \U201cold_model.logdive\U201d could not be opened. An error occured during persistent store migration."; NSUnderlyingError = "Error Domain=NSCocoaErrorDomain Code=134110 \"An error occured during persistent store migration.\" UserInfo=0x1002d8b30 {sourceURL=file://localhost/Users/edwardsc/source/tmp/old_model.logdive, reason=Can't copy source store to destination store path, destinationURL=file://localhost/Users/edwardsc/source/tmp/.old_model.logdive.migrationdestination_41b5a6b5c6e848c462a8480cd24caef3, NSUnderlyingError=0x1002daec0 \"The operation couldn\U2019t be completed. (NSSQLiteErrorDomain error 14.)\"}"; destinationURL = "file://localhost/Users/edwardsc/source/tmp/.old_model.logdive.migrationdestination_41b5a6b5c6e848c462a8480cd24caef3"; reason = "Can't copy source store to destination store path"; sourceURL = "file://localhost/Users/edwardsc/source/tmp/old_model.logdive"; }
This isn’t particularly helpful. After many hours of googling for NSSQLiteErrorDomain error 14
with no luck, I gave up and started to do some more research.
Errors not being populated
So, upon initial investigation, it looks like it can’t copy the file to a temporary file for migration. I checked and re-checked my file and directory permissions and everything looked OK.
I set a breakpoint in my implementation of configurePersistentStoreCoordinatorForURL:ofType:modelConfiguration:storeOptions:error:
which, BTW, looks something like:
-(BOOL)configurePersistentStoreCoordinatorForURL:(NSURL *)url ofType:(NSString *)fileType modelConfiguration:(NSString *)configuration storeOptions:(NSDictionary *)storeOptions error:(NSError **)error { NSMutableDictionary *options = (storeOptions != nil) ? [storeOptions mutableCopy] : [[NSMutableDictionary alloc] init]; options[NSMigratePersistentStoresAutomaticallyOption] = @(YES); options[NSInferMappingModelAutomaticallyOption] = @(YES); BOOL ok = [super configurePersistentStoreCoordinatorForURL:url ofType:fileType modelConfiguration:configuration storeOptions:options error:error]; NSLog(@"OK=%@", @(ok)); return ok; }
and even though the call to super
was returning NO
, the error
variable didn’t seem to being populated. Weird! Anyway, if I let the program keep running, eventually somewhere in the Apple framework it dumps out the error message reported above.
Core Data debug info
So, I added the following flag to my application’s runtime arguments:
-com.apple.CoreData.SQLDebug 3
and now, I get the following additional output:
CoreData: annotation: Connecting to sqlite database file at "/Users/edwardsc/source/tmp/old_model.logdive" CoreData: sql: pragma cache_size=500 CoreData: sql: SELECT Z_VERSION, Z_UUID, Z_PLIST FROM Z_METADATA CoreData: annotation: Disconnecting from sqlite database. Failed to delete support directory for store: /Users/edwardsc/source/tmp/.old_model.logdive.migrationdestination_41b5a6b5c6e848c462a8480cd24caef3
Aha… so, I can’t delete the support directory (whatever that means)! Again, after many more hours of unsuccessfully googling for that phrase, I found another Core Data flag.
Automatic migration debug info
This time I added the following flag to the application’s runtime:
-com.apple.CoreData.MigrationDebug 1
and I get some more info:
CoreData: annotation: (migration) will attempt automatic schema migration CoreData: annotation: Disconnecting from sqlite database. CoreData: annotation: (migration) looking for mapping model with source hashes: CoreData: annotation: (migration) no suitable mapping model found CoreData: annotation: (migration) inferring a mapping model between data models with source hashes: Failed to delete support directory for store: /Users/edwardsc/source/tmp/.old_model.logdive.migrationdestination_41b5a6b5c6e848c462a8480cd24caef3 CoreData: annotation: (migration) failed to copy store file from file://localhost/Users/edwardsc/source/tmp/old_model.logdive to file://localhost/Users/edwardsc/source/tmp/.old_model.logdive.migrationdestination_41b5a6b5c6e848c462a8480cd24caef3. (Error Domain=NSSQLiteErrorDomain Code=14 "The operation couldn’t be completed. (NSSQLiteErrorDomain error 14.)" UserInfo=0x100238310 {NSFilePath=/Users/edwardsc/source/tmp/.old_model.logdive.migrationdestination_41b5a6b5c6e848c462a8480cd24caef3, reason=Failed to open destination database}) CoreData: annotation: (migration) leaving incompletely migrated store on disk after automatic migration failure. (file://localhost/Users/edwardsc/source/tmp/.old_model.logdive.migrationdestination_41b5a6b5c6e848c462a8480cd24caef3) CoreData: error: (migration) migration failed with error Error Domain=NSCocoaErrorDomain Code=134110 "An error occured during persistent store migration." UserInfo=0x101a48630 {sourceURL=file://localhost/Users/edwardsc/source/tmp/old_model.logdive, reason=Can't copy source store to destination store path, destinationURL=file://localhost/Users/edwardsc/source/tmp/.old_model.logdive.migrationdestination_41b5a6b5c6e848c462a8480cd24caef3, NSUnderlyingError=0x100254cb0 "The operation couldn’t be completed. (NSSQLiteErrorDomain error 14.)"}
Hmmm – so, we’ve got more info in there, but still totally useless in terms of telling me what is wrong. At this stage, I am looking around for a Core Data engineer to strangle!
Some more searching goes on, and eventually I find a couple of posts that give me a glimmer of an idea.
The Solution
It turns out that automatic migration needs to copy the core data database to a temporary file so that it can perform the migration. This seems sensible, until it has to run in a Sandboxed environment where the user hasn’t given explicit permission to write to that temporary file.
I’m puzzled how this could ever have passed QA at Apple? It wouldn’t be so bad if they had at least output a meaningful error message instead of the nonsense above.
Anyhoo, the way to get around it is to create a couple of temporary entitlements – namely:
<key>com.apple.security.temporary-exception.files.absolute-path.read-write</key> <string>/Volumes/</string> <key>com.apple.security.temporary-exception.files.home-relative-path.read-write</key> <string>/</string>
Once you have these added to your entitlements file automatic migration works as advertised. Hopefully, this will save someone else the pain I just went through.