How to do database migration if SQLite is used? - iphone

My app is on App Store and in its new version I have updated the schema, like number of columns has been changed and name of columns has been changed. But now when I uploaded the new version on App Store, the app is crashing.
I came to know that this is happening because I have changed the schema. I have use SQLite so is there any solution to this problem?

SQLite supports a 'version' when you open (or create) the DB. This is a unique value that you decide. When you change the schema you should change the 'version' string so that the old database is abandoned for a new one. In the same way you would handle creating the database on first setup, you must then also manually migrate any important data from the previous database to the new one.
Unfortunately there's no way to role back an app to the last good version, the best you can do is to remove your app from the store for a few days until you get a fix into place.

Try like this.
Check the version
If new then - if you want Fetch all data from old DB
and remove the old DB. else
no changes
Create new db
Insert data what get from old DB
NSString *dbPath = [documentsDirectory stringByAppendingPathComponent:dbName];
BOOL dbSuccess = [fileManager fileExistsAtPath:dbPath];
if (dbSuccess) {
NSDictionary *oldDetailsDict = [NSDictionary dictionaryWithContentsOfFile:writablePath];
if(![[oldDetailsDict valueForKey:#"version"] isEqualToString:Current_APP_VERSION]){
NSString *oldVersion = [oldDetailsDict valueForKey:#"version"];
if ([oldVersion isEqualToString:Current_APP_VERSION]) {
//no code change needed
}else{
//Get data from old db
//Data moved to array
NSArray *oldDataArray = [Product getDataFromDatabase];
//Remove the old Database
NSString *deletePath = [documentsDirectory stringByAppendingPathComponent:#"sample.sqlite"];
[fileManager removeItemAtPath:deletePath error:nil];
//Create the new DB for new version
[self createdatabase];
//Again Insert the old data to new db
for (Product *pro in oldDataArray) {
[pro insertToDB];
}
}
}
}

Related

Core Data move data into shared container

I have an already published app that use core data.
Now I want to add support for watch kit and today extension.
I need to move core data into shared container without lose previous user saved data, how can I do that in the best way?
You can migrate a Core Data Stack. A fuller answer can be found here, but the short version is:
Check if the old non-group copy of the data exists
If it does, set up a Core Data stack using that file. Then use migratePersistentStore:toURL:options:withType:error: to move it to the new location. Then remove the old copy.
If the old version doesn't exist, just set up Core Data with the new copy as usual.
(The problem with Stephen's answer is that it assumes that the Core Data stack is a single SQLite file, which isn't always true.)
Here is how I moved core data to the shared container in my app. I do this when the app launches.
NSUserDefaults* sharedDefs = [GPMapCore sharedCore].sharedUserDefaults;
if (![sharedDefs boolForKey:#"CoreDataMovedToExtension"])
{
NSURL* oldLocation = GET_LOCATION_OF_CORE_DATA_SQLITE_FILE();
NSURL* newLocation = GET_LOCATON_TO_MOVE_THE_SQLITE_FILE_TO();
if ([[NSFileManager defaultManager] fileExistsAtPath:[oldLocation filePathString]])
{
//Check if a new file exists. This can happen when the watch app is run before
//Topo Maps+ runs and move the core data database
if ([[NSFileManager defaultManager] fileExistsAtPath:[newLocation filePathString]])
{
[[NSFileManager defaultManager ] removeItemAtURL:newLocation error:nil];
}
[[NSFileManager defaultManager] moveItemAtURL:oldLocation toURL:newLocation error:nil];
}
[sharedDefs setBool:YES forKey:#"CoreDataMovedToExtension"];
[sharedDefs synchronize];
}

Retrieve core-data model version from appDelegate

Anybody knows how to get the core-data model version when the app starts?
I've implemented the lightweight migration and it works fine but I need to check if the sqlite db is based to the old model version before the migration process starts.
In the new model I added a new entity but I need to populate it with value of an entity of the old model. I want to do that only one time.
Is there a way to do it in the appdelegate?
Thanks,
Max
I solved this by setting the DB-version in my preferences (.plist). So if I change the DB, I set the new DB-version in there and store the version from the last start in the NSUserDefaults.
NSNumber *newDbVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"DBVersion"];
NSNumber *oldDbVersion = [[NSUserDefaults standardUserDefaults] objectForKey:#"DBVersion"];
if (oldDbVersion == nil)
{
// part without handling, so you know the max db version.
}else
{
if([oldDbVersion intValue] < [newDbVersion intValue])
{
// try the liteweight migration and if that failed, migrate by hand
}
}

what is the best way to take the backup of data of sqlite3 database of iphone application?

i want to take the backup of all the data in the database of my iphone app in dropbox and restore it later.here the main problem is there is too much data in the app so if i am using sql queries to fetch data from sqlite3 database and storing it into the file and again reading data from that file and inserting it to database.can anyone suggest that how can i improve that or what is the best way to do that?
I presume that you mean that you want to be able to restore the data after user has somehow deleted the application and re-installed it again. Since each application has its influence inside the bounds of its sandbox, the only sane option would be to use server. In regards to simply answering your question - there is no way to do it as you've described.
You can use iCloud.
Go to your app manager on Itunes Connect to generate a iCloud Key.
So, all data will be stored on icloud.
You can use too a uuid as Key for your webservice. So, if the use delete app and when he reinstall, you cad download all data with key identifier.
For example:
uuid = [[UIDevice currentDevice] uniqueGlobalDeviceIdentifier];
With Class for UUID
Important: Use the global mode because it allows to use the uuid between applications.
my dropbox-based Sync API solution:
- (IBAction)onTouchDropboxBackup:(id)sender {
DBAccount *account = [[DBAccountManager sharedManager] linkedAccount];
if (account) {
if (![sharedDelegate filesystem]) {
DBFilesystem *filesystem = [[DBFilesystem alloc] initWithAccount:account];
[DBFilesystem setSharedFilesystem:filesystem];
[sharedDelegate setFilesystem:filesystem];
[filesystem release];
}
DBPath *destination = [[DBPath root] childPath:kDataDBFileName];
DBError *error = nil;
//check presense
DBFile *file = [[DBFilesystem sharedFilesystem] openFile:destination
error:&error];
if (error){//not present
[[DBFilesystem sharedFilesystem] createFile:destination
error:&error];
file = [[DBFilesystem sharedFilesystem] openFile:destination
error:&error];
}
if (!error) {
[file writeContentsOfFile:[DOCUMENTS_DIR stringByAppendingPathComponent: kDataDBFileName]
shouldSteal:NO
error:&error];
[file update:&error];
[file close];
}
}else
[[DBAccountManager sharedManager] linkFromController:self];
}

import csv to coredata sqlite for iphone

how can I import csv to sqlite (core data for iphopne)
I have tried using SQLite manager, but it imports the csv to a new table, also I need to import some dates,
so how to import the data to my sqlite database? I have 3 entitys with different properties, and in the csv I have all the values in one csv (so I could format it or change it as needed), but how to impor it?
also what is the date format that coredata likes?
thanks in advance!
I assume you have setup your CoreData and that one is running fine. You don't really want to work directly with the sqlite DB of core-data. It is possible, but also a bit messy.
Use one of the CSV scanners floating around to read your CSV data into fields.
Map the CSV fields to your entities and to their attributes as needed.
You might want to use the CSV header to verify that your mapping CSV-column-to-attribute is ok.
Loop through the rows of the CSV and update your entities row by row.
Depending on data volume you might want to save your context at regular intervals.
Core-Data likes NSDate. Whatever the CSV file uses in the data column you are best off converting the CSV value into a NSDate. Using NSDate in your App will reduce the number of headaches later.
I would suggest, first create sqlite Database inside application using Core Data, then use database created by Core data to import csv data.
you can use the command line commands for importing data in the sqlite file.
Add the sqlite file to project.
replace and add this code inside the persistentStoreCoordinator method..
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"yourDatabase.sqlite"];
/*
Set up the store.
For the sake of illustration, provide a pre-populated default store.
*/
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn’t exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"yourDatabase" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
Change the method
- (NSString *)applicationDocumentsDirectory {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}

iPhone sqlite3 locked, can only read, not write

So I have been working on this project for a short while now. I have no problems with reading data from the DB, and formatting it into UITableViews and what not. But now I am wanting also to write to the DB as well. The problem is I keep getting a "Database is Locked" error from sqlite. After messing around with the original version I had the face-palm moment by realizing my database was in the bundle and therefore not writable. So I relocated the DB to the Apps Documents folder, which is writeable. But now I still get the same "Database is Locked" sql error. I only open and close the DB when necessary. And as far as I can tell, I don't leave it open anywhere. Below is the code where I am wanting to do updates. Any thoughts?
- (BOOL) loanBookTo:(NSString *)newborrower{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"books.sqlite"];
if([[NSFileManager defaultManager] fileExistsAtPath:path]){
NSLog(#"File Exists at: %#", path);
}
if (sqlite3_open([path UTF8String], &database) == SQLITE_OK) {
NSString *mySQL = #"UPDATE BOOKS SET LOANED = 1, BORROWER = \"<BORROWER>\" where ISBN = \"<ISBN>\"";
mySQL = [mySQL stringByReplacingOccurrencesOfString:#"<ISBN>" withString:self.isbn];
mySQL = [mySQL stringByReplacingOccurrencesOfString:#"<BORROWER>" withString:newborrower];
const char *sql = [mySQL UTF8String];
char* errmsg;
sqlite3_exec(database, sql, NULL, NULL, &errmsg);
// Q. Database is locked. Why?
// A. Because it is in the Bundle and is ReadOnly.
// Need to write a copy to the Doc folder.
// Database is Locked error gets spit out here.
printf(errmsg);
sqlite3_close(database);
}
return NO;
}
Open the database once at the start of your app, then close it in applicationWillTerminate of the AppDelegate.
After every exec you will want to do a reset to clear the connection state.
Take a look at the SQLiteBooks sample app, it is coded this way.
How do you move the DB to the documents folder from the bundle? You need to check that it is there, and if not copy it. I get the feeling that either you have copied it some other way, but retained a read-only attribute, or more likely, you are still referencing the original in the bundle.
For details see
Where would you place your SQLite database file in an iPhone app?
or as joshperry says, the SQLiteBooks sample has all the code you need.
How are you getting to loanBookTo? If the database is open and then you call loanBookTo, it may not throw an error of the open, however the database is holding the state from where you came.
Also, at times, the database retains the locked state upon closing and exiting the application, so you could be 'inheriting' a locked state from your previous failures. Deleting the app from the simulator should give you a clean copy.