How to overwrite a SQLite DB in iOS - iphone

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.

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

refresh SQLITE3 (Core Data) on device and app store

I have an app leveraging Core Data SQLITE3 that works perfectly in the simulator. However i do not understand how to update the DB on the device, which i guess is the same as in app-store.
I update the DB from .txt files in the app and create the DB, this function is there only for creating the DB and will be removed in the final version. My idea is to create the DB in the simulator, lock the update part of the code and then distribute the package with an updated database.
However, when i rebuild my app on the device it still have the old data in the DB.
I have been looking around but i am afraid i do not fully understand how to solve this. I did find this thread: Can't refresh iphone sqlite3 database
I would very much appreciate if some nice person could share some light on this and help me to solve this.
Cheers
Have you copied the db file from the bundle directory (which is read only) to a writable one? (like the documents directory of each application?).
When trying to save in the device did you get a sqlite error like this?
SQLITE_READONLY 8 /* Attempt to write a readonly database */
EDIT:
All the files in the main bundle are read only, so if you need to modify one/some of them, you need to copy the files in a location that is writable. Assuming you have called the db mydb.sqlite here is some code that copies the db (only if it does not exists) to the documents directory.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDirectory = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
NSFileManager *fm = [NSFileManager defaultManager];
NSString *docPath = [docDirectory stringByAppendingPathComponent:#"mydb.sqlite"];
if (![fm fileExistsAtPath:docPath]) { // file does not exists, copy it
NSString *bundlePath = [[NSBundle mainBundle] pathForResource:#"mydb" ofType:#"sqlite"];
NSError *error = nil;
BOOL res = [fm copyItemAtPath:bundlePath toPath:docPath error:&error];
if (!res) {
// do something with error
}
}
Actually to use .db file inside the Bundle - it's a very bad idea.
Every thime, when I am using .db file, i am checking, if it allready exists inside my Application document directory, and then I will rewrite it.
#define DB_SHOULD_BE_REWRITTEN YES //You should update database and change allready existing db file to file from bundle
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"db.sqlite"];
BOOL success = [fileManager fileExistsAtPath:writableDBPath];
if (!success || DB_SHOULD_BE_REWRITTEN)
{
// The writable database does not exist, so copy the default to the appropriate location.
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"db.sqlite"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
dbPath = writableDBPath;

iPhone app database

So I can access a read-only SQLite database inside an iPhone / iPod app (Objective-C), but I'm writing a new app that will have a writable database. Obviously, the r/w file has to be in the user-writable directory. My question is, should I ship an empty database with the application and copy it over to the r/w location, or create the r/w database on the fly the first time the app launches?
You can do either, but it will be much easier (and less error prone) for you to create an empty database, put it in your bundle and then do this in your AppDelegate.m file:
- (void)prepareDatabase
{
//add Database Versioning check to see if the resources database is newer
// generally as simple as naming your database with a version on the end
NSFileManager *filemanager = [NSFileManager defaultManager];
NSString *databasePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingString:#"/YOURDATABASE.s3db"];
if(![filemanager fileExistsAtPath:databasePath]) {
//Database doesn't exist yet, so we copy it from our resources
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:#"/YOURDATABASE.s3db"];
if([filemanager copyItemAtPath:defaultDBPath toPath:databasePath error:nil]) {
NSLog(#"Database Copied from resources");
} else {
NSLog(#"Database copy FAILED from %# to %#",defaultDBPath,databasePath);
}
}
}
Then in your applicationDidFinishLaunching: method call this:
[self prepareDatabase];

How to keep existing database on app update?

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

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.