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];
Related
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
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.
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;
My problem appears in the device but not in the simulator.
nDBres=sqlite3_prepare_v2(databaseConn, sqlStatement, -1, &compiledStatement,NULL)
It's an insert query that I'm trying to run. In the simulator it returns 0, whereas in the device it returns 8. After this whenever I try to run any other write operation, the app crashes.
I'm going nuts over this.
You can consult the list of sqlite3 error codes as part of the sqlite3 API documentation (http://www.sqlite.org/c3ref/c_abort.html. Error code 8 is SQLITE_READONLY ("Attempt to write a readonly database").
As you probably know, iOS sandboxes applications running on the device, so you must make sure to create your database in one of the areas that the OS exposes for creating application writable files.
There's a decent tutorial on how to set up a sqlite3 project on iOS http://icodeblog.com/2008/08/19/iphone-programming-tutorial-creating-a-todo-list-using-sqlite-part-1/.
From that tutorial, the most important part for your issue is probably the createEditableCopyOfDatabaseIfNeeded method in the app delegate. This illustrates how you can ensure that you create an editable database file when your app launches for the first time:
(Note, this isn't my code... I'm reproducing it from the tutorial on icodeblog.com, where they explain it in detail)
- (void)applicationDidFinishLaunching:(UIApplication *)application {
[self createEditableCopyOfDatabaseIfNeeded];
[self initializeDatabase];
// Configure and show the window
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}
// 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;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"todo.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:#"todo.sqlite"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
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