views:

974

answers:

6

Hello,

In my app I sometimes need to rebuild and repopulate database file. SQLite databse is created and managed by CoreData stack.

What I'm trying to do is drop the file and then simply recreate persistentStoreCoordinator object.

It works under simulator but not on device, where I'm getting such an error:

NSFilePath = "/var/mobile/Applications/936C6CC7-423A-46F4-ADC0-7184EAB0CADD/Documents/MYDB.sqlite";
NSUnderlyingException = I/O error for database at /var/mobile/Applications/936C6CC7-423A-46F4-ADC0-7184EAB0CADD/Documents/MYDB.sqlite.  SQLite error code:1, 'table ZXXXX already exists';

I cannot find the cause of this in any way. It indicates two different problems - Cocoa error 256 indicates that file does not exist or is not readable. But file IS created after creating persistenStoreCoordinator, although it's empty, but after executing some queries it disappears.

Second message indicating attempt to create alredy existing table is quite strange in that case.

I'm quite confused and cannot get the point what's going on here. My code looks like this:

NSString *path = [[WLLocalService dataStorePath] relativePath];
NSError *error = nil;

WLLOG(@"About to remove file %@", path);

[[NSFileManager defaultManager] removeItemAtPath: path error: &error];

if (error != nil) {
 WLLOG(@"Error removing the DB: %@", error);
}


[self persistentStoreCoordinator];

WLLOG(@"Rebuild DB result %d", [[NSFileManager defaultManager] fileExistsAtPath: path]);

After this code is exectued, DB file exists but is empty. When then first query (and all following) is executed, it gives me the error above and file disappears.

Does anybody has an idea what's wrong with it?

Big thanks for pointing me the right way!

A: 

You can keep a "clean" copy of your sqlite database as part of the application bundle, then just copy over the version in the documents directory whenever you'd like to refresh the database.

Here's some code from an App that does something similar (although this version will not copy over and existing db):

// Check for the existence of the seed database
// Get the path to the documents directory and append the databaseName
NSString* databasePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: kDatabaseName];

NSFileManager* fileManager = [NSFileManager defaultManager];
if ( ![fileManager fileExistsAtPath: databasePath] ) 
{
    NSString* databasePathFromApp = [[[NSBundle mainBundle] resourcePath] 
                                     stringByAppendingPathComponent: kDatabaseName];

    [fileManager copyItemAtPath: databasePathFromApp 
                         toPath: databasePath 
                          error: nil];
}
[fileManager release];
jessecurry
A: 

Thanks for an answer. This is quite interesting idea. However, database structure is initialy created by coredata when first request is done and I'd like to keep coredata responsible to re-create it when the file is removed.

What isn't clear to me is difference in situation when the app is launched for the first time and db file doesn't exists - in that case it's created automatically without any error after first call to db, and when the file is deleted, which is basically the same situation (if I get it right), and it fails to create db.

I'm sure that before db file is deleted, there are no living instances of either of coredata stack instances (model, coordinator, context), so I don't have an idea what is wrong as the process which should fire db structs to create is completely the same as in the first case (first app launch).

Also the question is why it works under the simulator and not on the device. Weird...

Matthes
This should be a comment on Marcus' answer, and not an answer by itself.
Brad Larson
I know, but I wrote my answer from my iPhone where this website doesn't work correctly, so I even don't see the comment field and add comment button.
Burt
+2  A: 

The Core Data stack does not like you removing the file under it. If you are wanting to delete the file you should tear down the stack, delete the file and then reconstruct the stack. That will eliminate the issue.

Part of the problem is that the stack keeps a cache of the data that is in the file. When you remove the file you don't have a way to clear that cache and you are then putting Core Data into an unknown and unstable state.

You can try telling the NSPersistentStoreCoordinator you are removing the file with a call to -removePersistentStore:error: and then adding the new store with a call to -addPersistentStoreWithType:configuration:URL:options:error:. I am doing that currently in ZSync and it works just fine.

Marcus S. Zarra
Thanks for a tip, it's interesting and I'm going to try it. But isn't it the same when all instances of NSPersistentStoreCoordinator are released just before the file is removed? That's what happens just before call to my method above. I'd suppose it will remove and free all subsequent data structures as well. Can you explain why it's important to explicitly remove persistent store from coordinator before removing the file itself?
Burt
So I've tried your solution and it works! Big thanks for that! However I'm still wondering where's the difference between explicitly removing persistent store and simply releasing coordinator.
Burt
If you set the `NSPersistentStoreCoordinator` to nil within the `NSManagedObjectContext` and release it, then you would probably accomplish the same thing but that is far heavier than just instructing the `NSPersistentStoreCoordinator` to remove the store.
Marcus S. Zarra
A: 

Did anyone experienced same problem on jailbroken devices?

I'm removing persistent store from coordinator, deleting file and then adding new store as described above. It works fine on normal devices, but fails with error "SQLite error code:1, 'table ZXXXX already exists'" on jailbroken devices. This happens after any attempt to query or save any data within NSManagedObjectContext.

Calvin Enrixon
A: 

I use the following method -resetApplicationModel in my app delegate and it works fine for me.

You may not need the kApplicationIsFirstTimeRunKey user default, but I use it to test whether to populate the Core Data store with default settings in a custom method called -setupModelDefaults, which I also call from -applicationDidFinishLaunching: if the first-time run flag is YES.

- (BOOL) resetApplicationModel {

    // ----------------------
    // This method removes all traces of the Core Data store and then resets the application defaults
    // ----------------------

    [[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:YES] forKey:kApplicationIsFirstTimeRunKey];
    NSLog(@"Turned ON the first-time run flag...");

    NSError *_error = nil;
    NSURL *_storeURL = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: @"MyAppSQLStore.sqlite"]];
    NSPersistentStore *_store = [persistentStoreCoordinator persistentStoreForURL:_storeURL];

    //
    // Remove the SQL store and the file associated with it
    //
    if ([persistentStoreCoordinator removePersistentStore:_store error:&_error]) {
        [[NSFileManager defaultManager] removeItemAtPath:_storeURL.path error:&_error];
    }

    if (_error) {
        NSLog(@"Failed to remove persistent store: %@", [_error localizedDescription]);
        NSArray *_detailedErrors = [[_error userInfo] objectForKey:NSDetailedErrorsKey];
        if (_detailedErrors != nil && [_detailedErrors count] > 0) {
            for (NSError *_detailedError in _detailedErrors) {
                NSLog(@" DetailedError: %@", [_detailedError userInfo]);
            }                
        }
        else {
            NSLog(@" %@", [_error userInfo]);
        }
        return NO;
    }

    [persistentStoreCoordinator release], persistentStoreCoordinator = nil;
    [managedObjectContext release], managedObjectContext = nil;

    //
    // Rebuild the application's managed object context
    //
    [self managedObjectContext];

    //
    // Repopulate Core Data defaults
    //
    [self setupModelDefaults];

    return YES;
}
Alex Reynolds
A: 

Hi, This is my first project with core data, and I'm importing data from web, but I need to remove the core-data database before the new download, and this proces is very slow.

I think that is a good solution delete the file and then reconstruct the stack, like Marcus Says, I know that I need to do this

"You can try telling the NSPersistentStoreCoordinator you are removing the file with a call to -removePersistentStore:error: and then adding the new store with a call to -addPersistentStoreWithType:configuration:URL:options:error:. I am doing that currently in ZSync and it works just fine."

But Can you put an specific example of your code. I will apreciate your help!

Thanks, Jesus Trujillo