I have a system that is based on the SQLite database. Each client has a local database, and once in a while the update arrives from the main server, just a small delta .db file. The task is to merge to local database with the delta file, the schema is identical in both.
For my database management I use fmdb wrapper that can be found here. In the main thread, I keep the connection to the local database open. The delta file arrives in the background, and I want to do the merge in the background to avoid any user interface freezes that this could cause.
As for the merge itself, the only option that I found is to attach the delta database to the local database, then insert/update the rows, and finally detach the delta. This does not work as smooth as I expected.
Code description:
The onDeltaGenerated method is invoked in a background thread whenever delta database is ready to be processed (arrives from the server and is saved in the readable location).
The deltaDBPath is the absolute location of the delta database in the filesystem.
The db variable references open FMDataBase connection.
Code:
- (void)onDeltaGenerated:(NSNotification*)n {
NSString* deltaDBPath = [[n userInfo] objectForKey:#"deltaPath"];
#synchronized(db) {
[db executeUpdate:#"ATTACH DATABASE ? AS delta", deltaDBPath];
if ([db hadError]) {
NSLog(#" ****ERROR*** %d: %#", [db lastErrorCode], [db lastErrorMessage]);
} else {
NSLog(#"Delta attached from %#", deltaDBPath);
}
[db beginTransaction];
BOOL update1 = NO;
BOOL update2 = NO;
BOOL transaction = NO;
update1 = [db executeUpdate:#"INSERT OR REPLACE INTO equipment SELECT * FROM delta.equipment"];
if (!update1) {
NSLog(#" *** ERROR *** update 1 failed!");
NSLog(#" ****ERROR*** %d: %#", [db lastErrorCode], [db lastErrorMessage]);
}
update2 = [db executeUpdate:#"INSERT OR REPLACE INTO equipmentExt SELECT * FROM delta.equipmentExt"];
if (!update2) {
NSLog(#" *** ERROR *** update 2 failed!");
NSLog(#" ****ERROR*** %d: %#", [db lastErrorCode], [db lastErrorMessage]);
}
transaction = [db commit];
if (!transaction) {
NSLog(#" *** ERROR *** transaction failed!");
NSLog(#" ****ERROR*** %d: %#", [db lastErrorCode], [db lastErrorMessage]);
}
[db executeUpdate:#"DETACH DATABASE delta"];
if ([db hadError]) {
NSLog(#" ****ERROR*** %d: %#", [db lastErrorCode], [db lastErrorMessage]);
} else {
NSLog(#"Delta detached");
}
}
}
After this method is invoked for the first time, all seem to be fine until I try to detach the database. When I try to do it, I get the following error:
2012-01-11 12:08:52.106 DBApp[1415:11507] Error calling sqlite3_step (1: SQL logic error or missing database) SQLITE_ERROR
2012-01-11 12:08:52.107 DBApp[1415:11507] DB Query: DETACH delta
2012-01-11 12:08:52.107 DBApp[1415:11507] ****ERROR*** 1: database delta is locked
I also tried to the same but without putting inserts into transaction, the result is identical. Another thing was to remove the #synchronized clause, but also no luck. My guess is that if fails when trying to access local database connection from the background thread, but then how come it manages to attach and insert? Any help appreciated.
Edit
I moved the code to the main thread, so the db is now accessed from the main thread only. The problem remains.
Edit2
Ok, so after trying everything, I gave up on this for a moment and then came back when the first answer appeared here. Surprisingly, everything seems to work fine now, so my code must be correct. I suspect this was the problem with different threads locking the file, as I used XCode, SQLiteDatabaseBrowser and my application to open the database. Even though the lsof showed that the file was not locked, I think it was wrong and either XCode or SQLiteDatabaseBrowser was locking it. I consider the problem solved, and the lesson taken from this is not to thrust lsof so much and also plan the debugging better next time.
Just checking – does the NSLog(#"Delta attached from %#", deltaDBPath); get printed successfully, and the errors you describe happen after that?
The line Error calling sqlite3_step (1: SQL logic error or missing database) SQLITE_ERROR is the probably going to be the most interesting bit to look into.
After a little bit of Googling, one issue that comes up is the database file may not be writeable.
http://www.iphonedevsdk.com/forum/iphone-sdk-development/20142-problem-insert-fmdb.html
If the main DB you’re updating is within the app’s bundle, you’re not allowed to modify it – you should make a copy into the Documents or other writeable directory first.
Is the error actually occurring when you try and detach, or is it actually happening when you try and perform the INSERT OR REPLACE transaction?
Should you put another if ([db hadError]) {… just after these statements to make sure?
did you already
[db open];
elsewhere?
Are you sure that u reset all queries to db?
Make sure u make sqlite3_reset(stmt) call.
Here's one that just bit me in the (you know where):
I was being thorough (too much so, it seems) testing while building a new feature that required an additional ATTACHed database (in .NET, by the way).
So I
var i = query.ExecuteNonQuery("ATTACH DATABASE #FilePath AS `MergeDestination`;", fullPath);
FakeCallThatDoesNothing()
var i = query.ExecuteNonQuery("DETACH DATABASE `MergeDestination`;");
which, appearently, doesn't allow enough time for the attachment to take place and results in much head-scratching.
Related
I'm trying to fetch all contacts from El Capitan, and I'm using the Contacts API to do so. Here is the method where I fetch the contacts:
-(void)getAllContacts{
NSError* contactError;
//initialize the contact store
CNContactStore* addressBook = [[CNContactStore alloc]init];
//specify the fields I need
NSArray * keysToFetch =#[CNContactEmailAddressesKey, CNContactPhoneNumbersKey, [CNContactFormatter descriptorForRequiredKeysForStyle:CNContactFormatterStyleFullName]];
//establish the request with the above keys
CNContactFetchRequest * request = [[CNContactFetchRequest alloc]initWithKeysToFetch:keysToFetch];
//fetch all the contacts
BOOL success = [addressBook enumerateContactsWithFetchRequest:request error:&contactError usingBlock:^(CNContact * __nonnull contact, BOOL * __nonnull stop){
//I'm doing nothing here to try to isolate the problem, but even if I do something here, the problem remains
}];
//success is always true
//contactError is always nil
//5 or so seconds later, I see the error output to the console
}
Nothing fancy going on, but I everytime after this method executes, 5-10 seconds later the following error is output to the console:
[Accounts] Failed to update account with identifier 72360D58-3D7H-49FC-1086-QRCIEQ8A991A0, error: Error Domain=ABAddressBookErrorDomain Code=1002 "(null)"
I'm not explicitly trying to update any accounts. If I don't include the enumerateContactsWithFetchRequest call, then no error is output. I've tried this on multiple different Macs with different contact lists, and I get this same error. I am also checking that the CNContactStore authorization status is CNAuthorizationStatusAuthorized before I invoke getAllContacts.
This is really frustrating, I'm starting to suspect this might be a bug in the Contacts API? Has anyone else seen this issue, or have any idea what might be causing it? Thanks for the help!
PS. I changed the ID of the account in the error statement in case that is a security concern :)
I'm trying to implement a restore purchase button for a non-consumable product using the excellent MKStoreKit.
I've got the folliwing code on the restore button:
[[MKStoreManager sharedManager] restorePreviousTransactionsOnComplete:^{
[_priceLabel completeWithText:NSLocalizedString(#"App restored :)", #"")];
} onError:^(NSError *error) {
[_priceLabel completeWithText:NSLocalizedString(#"Unable to restore", #"")];
}];
the completion block gets called correctly, but when I call
[MKStoreManager isFeaturePurchased:#"com.myapp.pro"]
I get a NO. Feel like I am missing something obvious?
If yours is a subscription product, you should use isSubscriptionActive
If this is in sandbox, which I assume it is - Apple actually expire subscriptions within about 30 seconds, even if they're supposed to last a month.
So initially you may get a that the subcription is active, but later (and by later I mean soon after), the answer will change from Apple.
The application has been put in iCloud since the beginning, so I have the opportunities to fill it with data, and see synchronization happening on other devices.
However I am starting to have problems after deleting and redeploying the application on one device only.
In particular, at startup, it's taking a lot at this point:
NSLog(#"add coordinator");
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
NSLog(#"finish");
I can see "add coordinator" in console, then it seems to be in pause forever. Instead by activating core data logging, I can see that is doing a lot of select and update.
Then an infinite list of log error messages for each transactionNumber is shown:
CoreData: Ubiquity: Error importing transaction log: <PFUbiquityTransactionLog: 0x1929e350>
transactionLogLocation: <PFUbiquityLocation: 0x17bf1c40>: /private/var/mobile/Library/Mobile Documents/6G8M57K6MU~myapp/mobile.63606F7B-D6A9-5937-9160-126AC1315EBF/myAppCloud/3IEWV8FXM6GJ58H5PkyqFsyuoUZ2qIOuJOfon1QIYC4=/035A5A1C-F893-4B67-A784-866A4DE1B3F1.1.cdt
transactionNumber: 1
, exception: -[__NSCFDictionary setObject:forKey:]: attempt to insert nil key
-[_PFUbiquityRecordsImporter operation:failedWithError:](839): CoreData: Ubiquity: Import operation encountered an error: Error Domain=NSCocoaErrorDomain Code=134060 "The operation couldn’t be completed. (Cocoa error 134060.)" UserInfo=0x194c9f40 {exception=-[__NSCFDictionary setObject:forKey:]: attempt to insert nil key} while trying to import the log file at the URL: <PFUbiquityTransactionLog: 0x1929e350>
transactionLogLocation: <PFUbiquityLocation: 0x17bf1c40>: /private/var/mobile/Library/Mobile Documents/6G8M57K6MU~myapp/mobile.63606F7B-D6A9-5937-9160-126AC1315EBF/myAppCloud/3IEWV8FXM6GJ58H5PkyqFsyuoUZ2qIOuJOfon1QIYC4=/035A5A1C-F893-4B67-A784-866A4DE1B3F1.1.cdt
transactionNumber: 1
Then as I said, the "finish" is printed, and the app doesn't to have any data at all.
An Apple engineer replied me to a post I did on Apple Dev forum, stating that this is a known bug that has been fixed on forthcoming iOS 6.
So, there's no solution than scratching all data on devices and start again.
At this point, I suppose I will ship with CoreData without iCloud.
Since the iOS 5 Betas I have been testing my app's code and the lightweight migration bit for my Core Data model has been failing. I get the "no such column: FOK_REFLEXIVE" error, but strangely I get it on the first run on devices and the simulator when no older version is present. I have changed nothing since the iOS 4.x code that worked beautifully.
The full error gives no extra information about the error and after pounding my head on the table for a bit I may have narrowed down the error to a reflexive relationship in my data model. Name has a many-to-many relationship with Name via a relationship called, names. (The idea is that Rob is related to Bob and Robert, etc). Can this all of a sudden be taboo in iOS 5?
Also, does Core Data run through every version of the data model and migrate for each no matter what version of the data model the app should be using? It's strange to get a migration error on an app being run for the very first time I would think.
Here's the only related code although it's Marcus Zarra's code which I believe everyone on Earth is using.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (persistentStoreCoordinator_ != nil) {
return persistentStoreCoordinator_;
}
NSURL *storeURL = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"Saline.sqlite"]];
NSError *error = nil;
persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil] error:&error])
{
NSLog(#"Schema changes...");
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return persistentStoreCoordinator_;
}
UPDATE: Full error follows
2011-10-20 08:14:17.700 Saline[28315:207] CoreData: error: (1) I/O error for database at /Users/rob5408/Library/Application Support/iPhone Simulator/5.0/Applications/865AD162-49BC-4809-A63A-23E6D5A5E5C4/Documents/.Saline.sqlite.migrationdestination_41b5a6b5c6e848c462a8480cd24caef3. SQLite error code:1, 'no such column: FOK_REFLEXIVE'
2011-10-20 08:14:17.731 Saline[28315:207] Schema changes...
2011-10-20 08:14:17.732 Saline[28315:207] Unresolved error Error Domain=NSCocoaErrorDomain Code=134110 "The operation couldn’t be completed. (Cocoa error 134110.)" UserInfo=0x6f7b2d0 {destinationURL=file://localhost/Users/rob5408/Library/Application%20Support/iPhone%20Simulator/5.0/Applications/865AD162-49BC-4809-A63A-23E6D5A5E5C4/Documents/.Saline.sqlite.migrationdestination_41b5a6b5c6e848c462a8480cd24caef3, reason=Cannot migrate store in-place: I/O error for database at /Users/rob5408/Library/Application Support/iPhone Simulator/5.0/Applications/865AD162-49BC-4809-A63A-23E6D5A5E5C4/Documents/.Saline.sqlite.migrationdestination_41b5a6b5c6e848c462a8480cd24caef3. SQLite error code:1, 'no such column: FOK_REFLEXIVE', NSUnderlyingError=0x6f7cda0 "The operation couldn’t be completed. (Cocoa error 134110.)", sourceURL=file://localhost/Users/rob5408/Library/Application%20Support/iPhone%20Simulator/5.0/Applications/865AD162-49BC-4809-A63A-23E6D5A5E5C4/Documents/Saline.sqlite}, {
NSUnderlyingError = "Error Domain=NSCocoaErrorDomain Code=134110 \"The operation couldn\U2019t be completed. (Cocoa error 134110.)\" UserInfo=0x6f75780 {NSSQLiteErrorDomain=1, NSFilePath=/Users/rob5408/Library/Application Support/iPhone Simulator/5.0/Applications/865AD162-49BC-4809-A63A-23E6D5A5E5C4/Documents/.Saline.sqlite.migrationdestination_41b5a6b5c6e848c462a8480cd24caef3, reason=I/O error for database at /Users/rob5408/Library/Application Support/iPhone Simulator/5.0/Applications/865AD162-49BC-4809-A63A-23E6D5A5E5C4/Documents/.Saline.sqlite.migrationdestination_41b5a6b5c6e848c462a8480cd24caef3. SQLite error code:1, 'no such column: FOK_REFLEXIVE', NSUnderlyingException=I/O error for database at /Users/rob5408/Library/Application Support/iPhone Simulator/5.0/Applications/865AD162-49BC-4809-A63A-23E6D5A5E5C4/Documents/.Saline.sqlite.migrationdestination_41b5a6b5c6e848c462a8480cd24caef3. SQLite error code:1, 'no such column: FOK_REFLEXIVE'}";
destinationURL = "file://localhost/Users/rob5408/Library/Application%20Support/iPhone%20Simulator/5.0/Applications/865AD162-49BC-4809-A63A-23E6D5A5E5C4/Documents/.Saline.sqlite.migrationdestination_41b5a6b5c6e848c462a8480cd24caef3";
reason = "Cannot migrate store in-place: I/O error for database at /Users/rob5408/Library/Application Support/iPhone Simulator/5.0/Applications/865AD162-49BC-4809-A63A-23E6D5A5E5C4/Documents/.Saline.sqlite.migrationdestination_41b5a6b5c6e848c462a8480cd24caef3. SQLite error code:1, 'no such column: FOK_REFLEXIVE'";
sourceURL = "file://localhost/Users/rob5408/Library/Application%20Support/iPhone%20Simulator/5.0/Applications/865AD162-49BC-4809-A63A-23E6D5A5E5C4/Documents/Saline.sqlite";
}
Core Data migration only migrations from the existing store to the current model. It will not go through every model type. So you need to make sure you can migration from whatever the store is in to what the current model is.
Did you actually name an attribute FOK_REFLEXIVE? Is this perhaps a sqlite store being created by something other than Core Data?
What is the full error?
A migration will only occur when there is a store to migrate. I would test to see if there is a file on disk when you get an error as that would be the only thing that would kick off a migration.
UPDATE
This really points to an existing file. Since you are running in the simulator what happens if you just look using the console?
ls -la /Users/rob5408/Library/Application Support/iPhone Simulator/5.0/Applications/865AD162-49BC-4809-A63A-23E6D5A5E5C4/Documents
What do you see? Or try:
find /Users/rob5408/Library/Application Support/iPhone Simulator | grep sqlite
Another option is to turn on SQL debug and lets see what the output is. You can do this by adding the argument -com.apple.CoreData.SQLDebug 3 to your executable and watching the console output. I would be interested in seeing the results.
You always come up with a solution two seconds after posting...
I tried just changing the store type to binary thinking other store types probably don't have the concept of "columns" and may not be vulnerable to a column issue. So that worked, but I like using sqlite better because it's easier to debug (poor reasoning, i'm aware).
Therefore I'm still open to a solution to get reflexive relationships working under sqlite.
I created new version of CoreData model (added one new attribute to entity) and mapping from one to another. Then I open document created with old model. It opens normally but when I tried to save it I get alert window with "The document “Blahblahblah” could not be saved as “Blahblahblah.blah”. An error occurred while saving." and message in debug console
AppKit called rmdir("/private/var/folders/v9/y2tl4yh55zj1pcg0typksyrm0000gp/T/TemporaryItems/(A Document Being Saved By Document 3)"), it didn't return 0, and errno was set to 66.
Have you got ideas what is it meen?
PS. It is not my first experience in CoreData migration but it's first time I stuck so hard.
Your mileage may vary, but when I had this issue, it turned out to be a validation error.
My use case was an autosave (i.e. I hadn't explicitly called save). I verified this by explicitly putting a save call in and dumping the resultant error:
NSError* error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
Turns out (in my case), I had a required entity that wasn't being set.
Good luck.