How to keep existing database on app update? - iphone

I have an iPhone app that use an Sqlite database to store some data and some user configurations. The problem that I'm having is that when I submit an update of my application, the existing database on the user installation is overwrite with the empty database and the users lost their configurations. I'm sure it can not be too difficult to avoid this, but I don't know how to do it.
This is my code of the method that create the copy of the db:
// Creates a writable copy of the bundled default database in the application Documents directory.
- (void)createEditableCopyOfDatabaseIfNeeded {
// First, test for existence.
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSString *writableDBPath = [self databasePath];
success = [fileManager fileExistsAtPath:writableDBPath];
if (!success) {
// The writable database does not exist, so copy the default to the appropriate location.
//NSLog(dbName);
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:dbName];
success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
}
This method is called form:
- (BOOL)openDatabase {
BOOL success = true;
if (!database) {
[self createEditableCopyOfDatabaseIfNeeded];
if (sqlite3_open([[self databasePath] UTF8String], &database) != SQLITE_OK) {
success = false;
// Even though the open failed, call close to properly clean up resources.
sqlite3_close(database);
NSAssert1(0, #"Failed to open database with message '%s'.", sqlite3_errmsg(database));
}
}
return success;
}
- (NSString*)databasePath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:dbName];
return path;
}
Maybe I forgot something in my code?
Can some one help me to solve this out? Thank you!

How about copying the sqlite database from your main bundle to the application's document directory, but only if it does not already exist?

If you are using Core Data, or using sqlite - you are probibly storing your data in the "Documents" directory. This will not be wiped-out when updating your app.

I don't know much about sqlite databases, except that they are in-memory databases. It is not possible to 'keep' the in-memory databases. You have two options:
1) find a way to configure your sqlite to use a file instead of running in-memory (I don't know if this is possible, I looked but couldn't find a way quickly)
2) switch to a different database provider. If the pc is yours, you can install xampp or wamp (lamp on linux), containing a pre-configured, ready-to-run MySql database.
A final way would be to temporarily store the sqlite data when exiting and then reload it on startup, but that doesn't seem very optimal!
If you don't really need a database, you could also consider alternate storing such as xml or a flatfile

Related

Copy Updated Database into device in ios sdk

In my app, i used the sqlite DB. I submitted the 1.0 version of the app to app store. But after that i made some changes into DB like Inserting some new rows.
Then submitted 1.1 version of the app. But i will not able to get updated DB because the old DB already exists in device.
How can i solve this?
If i delete the app & then install the new version of app, then i got the updated DB.
//My code for copy of DB is as follow:
- (void)createEditableCopyOfDatabaseIfNeeded
{
// First, test for existence.
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"xxx.sqlite"];
success = [fileManager fileExistsAtPath:writableDBPath];
if (success) return;
// The writable database does not exist, so copy the default to the appropriate location.
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"xxx.sqlite"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
I called this method in appDidFinishLaunching method.
How can i get updated DB without deleting the app when new version is available
You can use the NSUserDefaults for doing this.
In your new version add this code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if(![[NSUserDefaults standardUserDefaults] boolForKey:#"1.1"]])
{
[self removeDatabase];
[self createEditableCopyOfDatabaseIfNeeded];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"1.1"];
}
//Other stuffs
}
Method for removing the old database file:
- (void)removeDatabase
{
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *removeDBPath = [documentsDirectory stringByAppendingPathComponent:#"xxx.sqlite"];
success = [fileManager fileExistsAtPath:writableDBPath];
if(success)
{
[fileManager removeItemAtPath:writableDBPath error:&error]
}
}
You will need to perform a database migration so that old users of your app do not lose their data.
This means checking which version of the database already exists, and executing the necessary SQL statements to update the schema and data.
The MTMigration framework (MIT license) can help you execute code once per version of your app, for example:
[MTMigration migrateToVersion:#"1.1" block:^{
// migrate the SQL database here
}];
But you may wish to detect the version of your database differently.
It would be a good idea to write some unit tests for this procedure. Screwing up user data is the last thing you want to do.
You have to write Data migration script:-
In case when you need to make changes to an exiting table which contains some data then you need to do some steps as follows:
1) Rename the existing table
Say TableName was 'TestTable', So Rename the table to 'TestTableOld'
2) Create a new table called 'TestTable' which has the new columns you want and other altercations made.
3) Copy data from 'TestTableOld' to 'TestTable' with the query like:
Insert into TestTable('col1',col2'...'coln') Values Select col1, col2, col3,...'coln' From TestTableOld;
And newly added column set value as blank, as for new entries will use as such.
4) Drop the table 'TestTableOld'
Follow the above four step procedure as this would work as an alter statement and also it would preserve all your data from your original table.
If you are using User_Version, then would helpfull to identify old DB with new DB

How to overwrite a SQLite DB in iOS

i have created an App in where i am using a SQLlite Database...I copy that Database, if needed, in the NSCaches Directory at the first start of the app with the following method:
- (void) copyDatabaseIfNeeded {
//Using NSFileManager we can perform many file system operations.
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSString *dbPath = [self getDBPath];
BOOL success = [fileManager fileExistsAtPath:dbPath];
if(!success) {
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"datenbankSpeed"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:dbPath error:&error];
if (!success)
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
- (NSString *) getDBPath {
//Search for standard documents using NSSearchPathForDirectoriesInDomains
//First Param = Searching the documents directory
//Second Param = Searching the Users directory and not the System
//Expand any tildes and identify home directories.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory , NSUserDomainMask, YES);
NSString *documentsDir = [paths lastObject];
return [documentsDir stringByAppendingPathComponent:#"datenbankSpeed"];
}
My problem is, if i change sth in the Database- file and create a new App File for my Customers, they install the new App over the old App but the old Database is still in use, which will result in a crash!
I'm assuming the problem is only when you change your db schema. Create a version table in the sqlite database and add a column schema_version and populate it with a value from your code. Now, whenever you change your sql schema, update schema_version number in your code. In copyDatabaseIfNeeded, check if you have existing db file, open it and read schema_version. If this version is the same as your current version, then you're fine. Else, you need to migrate the schema. You'll probably also want to migrate the data into the new schema as well.
EDIT: To clarify - in copyDatabaseIfNeeded, do the following:
int version = ... // read schema_version from db
if (version != kCurrentSchemaVersion)
{
// convert the schema into new version preserving data if required.
// this can be a multi-step process. Eg. if user directly upgrades to schema_version 3
// after schema_version 1, first you'll convert from 1->2, then 2->3.
}
You might also want to take a look at PRAGMA user_version as mentioned by #Martin in the comments.

Can't open sqlite db on ios

Hi I'm trying to create an sqlite database in ios.
I use the method sqlite3_open with the params needed but i always get the error 14 (SQLITE_CANTOPEN 14 /* Unable to open the database file */).
It does't work even with its simplest declaration
NSFileManager *fileMgr = [NSFileManager defaultManager];
NSString *dbPath = [[[NSBundle mainBundle] resourcePath ]stringByAppendingPathComponent:#"data.sqlite3"];
BOOL success = [fileMgr fileExistsAtPath:dbPath];
sqlite3 *db;
if(!success)
{
NSLog(#"Cannot locate database file '%#'.", dbPath);
}
if(!(sqlite3_open([dbPath UTF8String], &db) == SQLITE_OK))
{
NSLog(#"An error has occured.");
}
else{
NSLog(#"Ok");
}
Any ideas what's happening?
Thanks.
You can't get write access to a sqlite database in your application bundle. Instead, copy the database in the bundle into the "file" system, in an appropriate place (given iCloud concerns), and then open it there. Have your app look for the database in the file system first and only copy it if it's not there.
use document directory to store the database file and then create sqlite.... it works

UPDATE query only works in simulator

I am using the firefox sqlite manager to help me building an iphone app. In which I include an UPDATE query, which works perfectly in the iPhone simulator. However, it fails when I run from the real machine (the iphone). There is no error, it just does not update the db.
I am thinking of two possible causes:
1) The db it updates is not the one as in simulator
2) The db I read is not the one that is updated
Does anyone have similar experience?
Code as follows:
NSFileManager *fileMgr = [NSFileManager defaultManager];
NSString *dbPath = [[[NSBundle mainBundle] resourcePath ]stringByAppendingPathComponent:#"Exercises.sqlite"];
BOOL success = [fileMgr fileExistsAtPath:dbPath];
if(!success)
{
NSLog(#"Cannot locate database file '%#'.", dbPath);
}
if(!(sqlite3_open([dbPath UTF8String], &db) == SQLITE_OK))
{
NSLog(#"An error has occured.");
}
NSString *ranID = [#"UPDATE Status SET money = " stringByAppendingFormat:#"%d", money + 100];
const char *sql2 = [ranID UTF8String];
sqlite3_stmt *sqlStatement2;
if(sqlite3_prepare(db, sql2, -1, &sqlStatement2, NULL) != SQLITE_OK)
{
NSLog(#"Problem with prepare statement 2");
}
if (sqlite3_step(sqlStatement2)==SQLITE_ROW)
NSLog(#"succeed");
Thanks in advance.
What happening is that you are trying to write inside the database that is in your bundle, you dont have write access to files in your bundle, you will need to copy it to Documents directory if you want to update the database
To copy the database file
NSString *documentsPath = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents/Exercises.sqlite"];
[[NSFileManager defaultManager] copyItemAtPath:dbPath toPath:documentsPath error:NULL];
Then always use the new file path (documentsPath)to access the database
Files in your app's bundle are read-only on device.
You should copy it to somewhere in your app's sandbox first, such as in your Library/Application Support directory. (You can use - [NSFileManager URLsForDirectory:inDomains:] with NSApplicationSupportDirectory to find this path; be sure to create the directory before you try to write to it.)
This sounds like you are not committing our changes. See similar posts here and here

Sqlite doesn't seem to like my delete statement

I've got the following iphone code, which seems to be failing:
sqlite3_stmt *dbps;
NSString *sql = #"delete from days where day=?1;insert into days(disabled,recipe_id,day) values(?2,?3,?1)";
int rc = sqlite3_prepare_v2(db, sql.UTF8String, -1, &dbps, NULL);
...
The 'rc' return code is 1, meaning SQLITE_ERROR (SQL error or missing database, according to the sqlite site). Not sure what i've done wrong? The database 'db' is indeed open, and other queries seem to work fine.
Thanks a lot guys
Remove the insert statement from your string. It is not compiled anyway since sqlite3_prepare_v2 will "only compile the first statement in zSql."
Perhaps you should use a trigger to do your (optional) delete, or use insert or replace.
Are you sure you have copied the database in Documents directory before opening it? iPhone OS only allow write permissions in documents directory. Here is the code for copying database to Documents directory -
//function to copy database in Documents dir.
-(void) checkAndCreateDatabase{
// Check if the SQL database has already been saved to the users phone, if not then copy it over
BOOL success;
// Create a FileManager object, we will use this to check the status
// of the database and to copy it over if required
NSFileManager *fileManager = [NSFileManager defaultManager];
// Check if the database has already been created in the users filesystem
success = [fileManager fileExistsAtPath:databasePath];
// If the database already exists then return without doing anything
if(success) return;
// If not then proceed to copy the database from the application to the users filesystem
// Get the path to the database in the application package
NSString *databasePathFromApp = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:databaseName];
// Copy the database from the package to the users filesystem
[fileManager copyItemAtPath:databasePathFromApp toPath:databasePath error:nil];
[fileManager release];
}
// open the database and fire the delete query...
sqlite3 *database;
NSString *sqlStatement = #"";
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
databasePath = [documentsDir stringByAppendingPathComponent:databaseName];
NSLog(#"%#",databasePath);
[serlf checkAndCreateDatabase];
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK)
{
// here you can fire the delete query...
}
Silly me, i just had an old copy of the schema in my Documents folder, which didn't have the 'days' table in it. So i followed the instructions here: Cleaning up the iPhone simulator, and then it copied the new schema over, and it started working again.
Thanks for the help guys.