sqlite error database is locked - iphone

I am have a sqlite database in the iPhone application I am writing. I get an error with following code that I am running in a background thread. In the background thread, I call this method:
- (BOOL) songIsInDatabase:(NSString *)songTitle
{
NSString *docsDir;
NSArray *dirPaths;
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
//Build the path to the database file
NSString *databasePath = [[NSString alloc] initWithString:[docsDir stringByAppendingPathComponent:#"Database.db"]];
const char *dbpath = [databasePath UTF8String];
sqlite3_stmt *statement;
if(sqlite3_open(dbpath, &DB) == SQLITE_OK){
NSString *insertSQL = [NSString stringWithFormat:#"select * from Bpm_Table where song_title = '%#'", songTitle];
const char *insert_stmt = [insertSQL UTF8String];
if(sqlite3_prepare_v2(DB, insert_stmt, -1, &statement, NULL) == SQLITE_OK)
{
while (sqlite3_step(statement) == SQLITE_ROW)
{
return YES;
break;
}
}else{
NSLog(#"the error is %s", sqlite3_errmsg(DB));
}
sqlite3_finalize(statement);
}
[databasePath release];
return NO;
}
Then I call this method:
- (void) addSongToDatabase: (NSString *) songTitle andBPM: (int)bpm andGenre: (NSString *) genre
{
NSString *docsDir;
NSArray *dirPaths;
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
//Build the path to the database file
NSString *databasePath = [[NSString alloc] initWithString:[docsDir stringByAppendingPathComponent:#"Database.db"]];
const char *dbpath = [databasePath UTF8String];
sqlite3_stmt *statement;
if(sqlite3_open(dbpath, &DB) == SQLITE_OK){
NSString *insertSQL = [NSString stringWithFormat:#"insert or replace into Bpm_Table (song_title, bpm, genre) values (\"%#\", \"%d\", \"%#\")", songTitle, bpm, genre];
const char *insert_stmt = [insertSQL UTF8String];
if(sqlite3_prepare(DB, insert_stmt, -1, &statement, NULL) == SQLITE_OK){
if(sqlite3_step(statement) == SQLITE_DONE)
{
} else {
NSLog(#"error: %s", sqlite3_errmsg(DB));
}
}sqlite3_finalize(statement);
}
[databasePath release];
}
If I run both these methods, one right after the other, I get an error that says database is locked. I added the sqlite3_finalize statements after poking around google in hopes that would fix the issue. If I comment out either of the methods, I don't get this error.
Does anyone know what's wrong?

you must always close the sqlite database after using it ... so add this line sqlite3_close(DB); just after sqlite3_finalize(statement);
Update -
You are returning YES in one of your while loop -
while (sqlite3_step(statement) == SQLITE_ROW)
{
return YES;
break;
}
so here you are nither finalizing nor closing the database.. you need to close if everytime you open it

But note that if you access the same SQLite DB from multiple threads, without some sort of separate synchronization (some way to know that you'll never have near-simultaneous accesses from multiple threads) then you're likely to get "database locked" errors even if you close everything correctly on each access.
SQLite does not provide any sort of "lock wait" mechanism.
Your basic options are:
Do all accesses from one thread.
Use a separate lock protocol around all accesses.
If you get a "database locked" error wait a brief period of time and retry.

your database is open close it using sqlite3_close(db)
if you dont close then the process which accessed your database will be running background
which will cause database is locked error.
if you want to remove database is locked error then follow these steps
1.copy your database file to some other location.
2.then replace the database with the copied database
3.this will dereference all processes which were accessing your database file

If you have tried sqlite3_close(DB) it's probably because your methods are trying to access the same database at the same time.
Try to place this line of code
sqlite3_busy_timeout(DB, 500);
somewhere between sqlite3_open and sqlite3_prepare_v2 in the method from where you get the "database is locked"-error message.
The database-connection in that method will then try to write/read in 500 milliseconds before it gives up, which is usually enough time to escape the locking.

Your database is open. Close it using sqlite3_close(db).
If you don't close, then the process accessing your database will be running background which will cause database is locked error.

For one thing, you're not closing the database connection with sqlite3_close() before you open a new connection

I just spent an hour with the same problem. The problem for me arose when i unwittingly killed a process by closing the shell (when the script was running a while loop and timer), while a db journal temp file was still opened. Windows does not kill the process even though you have killed it in Python, regardless if you have used db.close() or finalize in the script; if you exit all Python apps there will still be one running in the Task manager.
Hence you will need to end all Python processes in Windows Task Manager, restart them, and it should be fine(if that is actually the cause of the problem).

I had the same problem a while ago. I tried copying the database file itself, renamed it to different filename, then checked it - It actually WORKED!
I am using SQLite3.
I hope this will also work for you!

Related

Want advise on saving images in sqlite3 on ios

I am developing an app for iOS that uses SQLite3 database now i want to save some images in it, i have searched on net that people tells that it is not good idea to save images in SQLite3 like this one
Blob Data Type?
I am totally confuse that what to do so i am painting my whole situation in front of you please advice me what to do
i want to store 79 images in my SQLite database most of them are 2kb in size and very few are 20 to 25 kb totally all the images takes 384kb on disk so is it advisable to store all the images in my database or only use links in my database and filesystem for images
Please advise me as soon as possible
In my opinion Save imagePath in SQLite, and save image in your document directory. I do the same. May be this help you.
-(void)saveDataToDatabase:(NSString *)fileNameToSave
{
NSString *docsDir;
NSArray *dirPaths;
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
sqlite3_stmt *statement;
NSString *databasePath = [[NSString alloc]
initWithString: [docsDir stringByAppendingPathComponent:
#"scanner.db"]];
NSLog(#"%#",databasePath);
const char *dbpath = [databasePath UTF8String];
if (sqlite3_open(dbpath, &databaseHandle) == SQLITE_OK)
{
NSString *insertSQL = [NSString stringWithFormat: #"Insert into IMAGEINFO (NAME,IMAGEPATH) values ('%#','%#')",#"imageFirst",fileNameToSave];
const char *insert_stmt = [insertSQL UTF8String];
sqlite3_prepare_v2(databaseHandle, insert_stmt, -1, &statement, NULL);
if (sqlite3_step(statement) == SQLITE_DONE)
{
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"database"];
}
else
{
}
}
}
-(void)saveImage:(NSString *)fileName:(UIImage *)imageToSave
{
NSError *error;
NSString *fileNaToSave = [NSString stringWithFormat:#"Documents/%#.png",fileName];
NSString *pngPath = [NSHomeDirectory() stringByAppendingPathComponent:fileNaToSave];
// Write image to PNG
[UIImagePNGRepresentation(imageToSave) writeToFile:pngPath atomically:YES];
// Let's check to see if files were successfully written...
// You can try this when debugging on-device
// Create file manager
NSFileManager *fileMgr = [NSFileManager defaultManager];
// Point to Document directory
NSString *documentsDirectory = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents"];
// Write out the contents of home directory to console
NSLog(#"Documents directory: %#", [fileMgr contentsOfDirectoryAtPath:documentsDirectory error:&error]);
}
Well, i wont recommend to store images in the DB. The main reason is that can cause your db crash(i wont say always but, there are situations i really seen the db crash due to storing the images).
The very best way to cache your images is to save the images in to your documents directory. That's safe and Robust. Happy Coding. :-)
I agree that storing large blobs in a database can lead to performance issues for websites and other applications that have a lot of users. Database resources are wasted on long operations that could be handled by the file system or other servers.
However, with an iPhone app, you don't need to worry about database resource being wasted on blobs. Your app will only have 1 user accessing the database so it will be just as responsive if the images come from the file system or SQLite.

Creating an SQLite3 database file through Objective-C

I'm trying to create an SQLite3 database file through Objective-C at run time. I am trying to create a table called "tblStore". I want the field names to be called "strStoreNumber" and "strStoreReg". I'm new to iOS and SQLite, so I'm having a hard time finding the syntax to perform this. In addition to creating the table, I want the created table to reside NOT in the app bundle, but rather it would reside/be stored somewhere on the phone. The table needs to be readable/writeable. I've done some reading on "user sandbox" and also a "documents directory". I'm not sure I understand the difference between the two. Ideally, my app would use a button to take input from Text Fields. After the input from the texts fields is put into strings, a check would be done to see if my "tblStore" SQLite table exists, and if it doesn't, the table will be created.
To recap:
1. What is the syntax for Obj-C/SQLite to create a table called "tblStore" with the fields "strStoreNumber" and "strStoreReg"?
2. Where should the db file reside? I need to read from and write to the tblStore db file.
3. What is the difference between a "user sandbox" and a "documents directory"?
This is what I have currently:
-(IBAction)setInput:(id)sender
{
NSString *strStoreNumber;
NSString *strRegNumber;
NSString *tableName = #"tblStore";
NSString *dbStrStore = #"strStore";
NSString *dbStrReg = #"strReg";
strStoreNumber = StoreNumber.text;
strRegNumber = RegNumber.text;
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* documentsDirectory = [paths lastObject];
NSString* databasePath = [documentsDirectory stringByAppendingPathComponent:#"tblStore.sqlite"];
// NSString* databasePath = [[NSBundle mainBundle] pathForResource:#"tblStore" ofType:#"sqlite"];
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK)
{
NSLog(#"Opened sqlite database at %#", databasePath);
char *err;
NSString *sql = [NSString stringWithFormat:#"CREATE TABLE IF NOT EXISTS '%#' ('%#' TEXT PRIMARY KEY, '%#' TEXT);", tableName, dbStrStore, dbStrReg];
if (sqlite3_exec(database, [sql UTF8String], NULL, NULL, &err) != SQLITE_OK)
{
sqlite3_close(database);
NSAssert(0, #"Table failed to create.");
}
//...stuff
}
else
{
NSLog(#"Failed to open database at %# with error %s", databasePath, sqlite3_errmsg(database));
sqlite3_close (database);
}
NSString *querystring;
// create your statement
querystring = [NSString stringWithFormat:#"SELECT strStore, strReg FROM tblStore WHERE strStore = %# AND strReg = %#;", strStoreNumber, strRegNumber];
const char *sql = [querystring UTF8String];
NSString *szStore = nil;
NSString *szReg = nil;
sqlite3_stmt *statement = nil;
if (sqlite3_prepare_v2(database, sql, -1, &statement, NULL)!=SQLITE_OK) //queryString = Statement
{
NSLog(#"sql problem occured with: %s", sql);
NSLog(#"%s", sqlite3_errmsg(database));
}
else
{
// you could handle multiple rows here
while (sqlite3_step(statement) == SQLITE_ROW)
{
szStore = [NSString stringWithUTF8String:(char*)sqlite3_column_text(statement, 0)];
szReg = [NSString stringWithUTF8String:(char*)sqlite3_column_text(statement, 1)];
}
}
sqlite3_finalize(statement);
lblStoreNumber.text = szStore;
lblRegNumber.text = szReg;
}
When I run my app, I get the following errors:
2012-05-10 14:58:38.169 CCoDBTry[355:f803] Opened sqlite database at /Users/Matt****/Library/Application Support/iPhone Simulator/5.1/Applications/5DB7A218-A0F6- 485F-B366-91FD2F9BC062/Documents/tblStore.sqlite
2012-05-10 14:58:38.307 CCoDBTry[355:f803] sql problem occured with: SELECT strStore, strReg FROM tblStore WHERE strStore = 8053 AND strReg = 4;
2012-05-10 14:58:38.308 CCoDBTry[355:f803] no such column: strStore
I appreciate anyone who takes the time out to explain some of this stuff, as I am very new and have been unsuccessful in accomplishing some of the things I've tried. Thanks so much for the help!
// Create DB
-(NSString *) filePath
{
NSArray *paths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory=[paths objectAtIndex:0];
return [documentDirectory stringByAppendingPathComponent:#"LoginDatabase.sql"];
}
// Open DB
-(void)openDB
{
if(sqlite3_open([[self filePath]UTF8String], &db) !=SQLITE_OK)
{
sqlite3_close(db);
NSAssert(0, #"Database failed to Open");
}
}
// Create Table
-(void) createTableNamed:(NSString*)tableName withField1:(NSString*) field1 withField2:(NSString*) field2
{
char *err;
NSString *sql=[NSString stringWithFormat:#" CREATE TABLE IF NOT EXISTS '%#'('%#' TEXT PRIMARY KEY,'%#' TEXT);",tableName,field1,field2];
if(sqlite3_exec(db, [sql UTF8String], NULL, NULL, &err) !=SQLITE_OK)
{
sqlite3_close(db);
NSAssert(0, #"Table failed to create");
}
}
// Inserting records
-(void)insertrecordIntoTable:(NSString*) tableName withField1:(NSString*) field1 field1Value:(NSString*)field1Vaue andField2:(NSString*)field2 field2Value:(NSString*)field2Value
{
NSString *sqlStr=[NSString stringWithFormat:#"INSERT INTO '%#'('%#','%#')VALUES(?,?)",tableName,field1,field2];
const char *sql=[sqlStr UTF8String];
sqlite3_stmt *statement1;
if(sqlite3_prepare_v2(db, sql, -1, &statement1, nil)==SQLITE_OK)
{
sqlite3_bind_text(statement1, 1, [field1Vaue UTF8String], -1, nil);
sqlite3_bind_text(statement1, 2, [field2Value UTF8String], -1, nil);
}
if(sqlite3_step(statement1) !=SQLITE_DONE)
NSAssert(0, #"Error upadating table");
sqlite3_finalize(statement1);
}
// Retrieve data from table
-(void)getAllRowsFromTableNamed:(NSString *)tableName
{
NSString *field1Str,*field2Str;
NSString *qsql=[NSString stringWithFormat:#"SELECT * FROM %#",tableName];
sqlite3_stmt *statement;
if(sqlite3_prepare_v2(db, [qsql UTF8String], -1, &statement, nil)==SQLITE_OK)
{
while(sqlite3_step(statement) ==SQLITE_ROW)
{
char *field1=(char *) sqlite3_column_text(statement, 0);
char *field2=(char *) sqlite3_column_text(statement, 1);
field1Str=[[NSString alloc]initWithUTF8String:field1];
field2Str=[[NSString alloc] initWithUTF8String:field2];
NSString *str=[NSString stringWithFormat:#"%# - %#",field1Str,field2Str];
NSLog(#"%#",str);
}
}
}
In viewDidLoad call the methods
- (void)viewDidLoad
{
[self openDB];
[self createTableNamed:#"Login" withField1:#"USERNAME" withField2:#"PASSWORD"];
[self insertrecordIntoTable:#"Login" withField1:#"USERNAME" field1Value:username andField2:#"PASSWORD" field2Value:password];
}
Where username and password are NSString values;
sqlite is a pain if you dont know what you're doing. I also had some problems with sqlite c functions but then i decided to use sqlite wrapper.
FMDB and BWDB are good and easy to use sqlite wrappers for objective c. I suggest you use one of those .
Note that BWDB is in a lynda.com tutorial (this one) and if you don't find it on the web...leave a comment and i'll upload it somewhere.
edit: the only place you can write stuff in your app is in your documents directory...so..it plain terms...if the db is not in your documents directory..is read-only..also..when you read/write to your db..the OS copies the db in the documents directory..and does all the reading and writing there so you can have a db in your app bundle but you can't edit that one...so you'll end up with 2 db.I had the same problem myself..and i fixed it by merging the 2 db when i updated the app
edit2: i uploaded BWDB final project ( you have your wrapper there and project to see how it works)
You can use following code to get the database created at in Documents folder. Just pass a path in documents folder and the function will copy the sqlite database in Documents folder at the given path if required. You can then use this path to create and query database tables.
+ (NSString*) createDatabaseIfRequiredAtPath:(NSString*)databasePath {
if (databasePath == nil)
return nil;
NSString *path = [NSString stringWithFormat:#"%#/%#", databasePath, kMainDBName];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
if ([fileManager fileExistsAtPath:path] == NO)
{
// The writable database does not exist, so copy the default to the appropriate location.
NSString *defaultDBPath = [[NSBundle mainBundle] pathForResource:kMainDBName
ofType:nil];
BOOL success = [fileManager copyItemAtPath:defaultDBPath
toPath:path
error:&error];
if (!success)
{
NSCAssert1(0, #"Failed to create writable database file with message '%#'.", [ error localizedDescription]);
return nil;
}
}
return path;
Is there a specific reason you'd like to use SQLLite directly, as opposed to using CoreData? CoreData uses an SQLLite database but is itself a higher-level API, and particularly with table views and such, you get a lot of functionality and template methods that are already set up for it in Xcode. Defining data models is trivial, you get tons of boilerplate code, and it's all optimized.
http://www.raywenderlich.com/934/core-data-on-ios-5-tutorial-getting-started
CoreData is sometimes described as having a steep learning curve. I disagree. If you're considering writing SQL yourself in your app, you're not going to have any trouble with CoreData.
The term "sandbox" is an abstract term for the portion of the device's filesystem that your application has read/write access to. The "Documents" directory is a specific directory within your application's sandbox. There are other files in your sandbox than just the documents directory, but most applications that save data to the filesystem in iOS do so in the documents directory.
you can open terminal and cd to /Users/Matt**/Library/Application Support/iPhone Simulator/5.1/Applications/5DB7A218-A0F6-485F-B366-91FD2F9BC062/Documents/
then sqlite3 tblStore.sqlite
then use .schema tblStore should show your table schema and you can see if it was built correctly.
Your database needs to be in the documents directory for write access. If your database was only going to be read, never written to it could be in your application bundle. One way to accomplishing this is to create your database.sqlite file and add it to the bundle and copy it to the documents directory (if it doesn't already exist there) on launch.
maybe you forgot Table Insertions. Created Database and Table but its empty table .
you trying to read the records from Table which is empty.
I tried the above didn't work for me, mainly because the example given would usually point to the DB file but not really create it.
Upon observing how sqlite3 works using the command shell, here is how I resolved the issue:
(For testing purposes), to create a file using the Linux shell, do:
sqlite3 Heider.db
This created a new database filename (Heider.db), also starts the DB command prompt to create SQL queries, ... before doing any query, just exist without doing anything by entering:
.exit
In the Linux shell check the file created, do an:
ls -ltra
You should see there is a "Heider.db" file created with 0 bytes in size, this means that the sqllite3 tool is creating a blank file with no contents.
Now, to check whether this will work, go back into sqlite:
sqlite3 Heider.db
Now this time, create a new table using:
create table test(id int not null primary key);
Insert some stuff:
insert into test(id) values (123);
Notice the query executes nicely without any errors.
Now, exit and check:
.exit
ls -ltra
The file should have some data in it, this confirms that the tool in Linux works at least...
Now, away from the Linux/Shell and back into XCODE/Development (or whatever tool you are developing with)... this makes our lives so easy as all we need to do is to create a file manually "somehow" using code instead, and then let sqlite use it.
Therefore, here is what you need to do:
// Check if the filename already exists:
DBFileName = #"MyNewFile.db";
if (![[NSFileManager defaultManager] fileExistsAtPath: DBFileName])
{
NSLog([NSString stringWithFormat:#"Database does not exists, creating: %#", DBFileName]);
[[NSFileManager defaultManager] createFileAtPath:DBFileName contents:NULL attributes:NULL];
}
// Use SQLite to access the new file to do whatever you want:
int DBOpen = sqlite3_open([_DBFileName UTF8String], &_SqlLite);
if (DBOpen == SQLITE_OK)
{
NSLog([NSString stringWithFormat: #"Client: SQLite DB Opended Successfully, creating test table..."]);
// Heider: Notice I am using "self" here because I am having this as a
wrapper class (see at the bottom of this post the whole function), you don't have to do the same, as you can use your own
SQLITE3 object directly instead...
[self ExecQuery:#"create table Test(id int not null primary key);"];
[self ExecQuery:#"insert into Test(id) values (123);"];
}
The SELF's ExecQuery I am using above is also below in case you need it:
-(BOOL)ExecQuery:(NSString *)Query {
BOOL Successful = NO;
if (sqlite3_prepare_v2(_SqlDB, [Query UTF8String], -1, &_SqlQuery, NULL) == SQLITE_OK)
{
if (sqlite3_step(_SqlQuery) == SQLITE_DONE)
{
NSLog([NSString stringWithFormat: #"Client: SQL Query Successful: %#", Query]);
} else {
NSLog([NSString stringWithFormat: #"Client-Error Executing SQL Query: %#", Query]);
}
} else {
Log(3, [NSString stringWithFormat: #"Client-Error Preparing SQL Query: %#", Query]);
}
sqlite3_finalize(_SqlQuery);
return Successful;
}
The process is to create a blank file anyhow you like, and then point sqlite3 to it and then do your SQL in the good old way... should be simple.
Good luck, I hope this helps.
H

Cannot add to sqlite database even though there are no errors

I'm making a iphone app for a final project at school and I'm having some trouble. I have it connected to a sqlite database, but even though I'm not getting any errors in xcode and when I've stepped through the code with the debugger it indicates that it is working - but its not actually updating. I saw a few other threads with similar problems on the net but I was unable to solve the problem by looking at them. Here is the relevant code but if you think you need to see more I'd be happy to post it all:
first the createAndCheckCode method is called followed by the addRecipe method. I just through in a generic string until I can figure out how to get it working. i can view the database contents from a different program.
(void) addToDatabase
{
databaseName = #"recipeappdatabase.sqlite";
// Get the path to the documents directory and append the databaseName
NSArray *documentPaths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
databasePath = [documentsDir stringByAppendingPathComponent:databaseName];
// 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)
{
// 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];
}
// Setup the database object
sqlite3 *database;
// Open the database from the users filessytem
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK) {
// Setup the SQL Statement and compile it for faster access
const char *sqlStatement = "insert into Recipes (title) VALUES ('toor')";
sqlite3_stmt *compiledStatement;
if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK) {
sqlite3_bind_text(compiledStatement, 1, sqlStatement, -1, SQLITE_TRANSIENT);
}
//if (sqlite3_step(compiledStatement) != SQLITE_DONE)
//NSAssert1(0, #"Error updating table: %s", errorMsg);
sqlite3_finalize(compiledStatement);
}
sqlite3_close(database);
}
Edit/Delete Message

SQLite3 on iPhone - insert returns error code 8 attempt to write a readonly database

So, I have been beating my head against the wall on this for about a week now.
I'm writing an iPhone app that has an sqlite database in it. I'm able to open the database and read from it (I put some test data in there through via the command line / terminal), select specific data, etc. But, what I cannot do is insert into the database from the phone. When I execute sqlite3_exec(...) it returns error code 8 "attempt to write a readonly database."
I had read other questions on here saying that I was using the Main Bundle's database and not the users database, and that the simulator will often times just "let you do it" while on a live device you'll get an error. Well, that's not what's happening in my case - I'm getting this error while running on the simulator. And from what I can tell my code to check the database is exactly as a lot of others recommend it to be.
Here is the code I use to verify the database exists, and if it doesn't I copy it:
// initialize db (if not already)
+(void) checkDatabase {
// setup some variables
Boolean success;
dbName = #"daarma.sqlite";
NSArray *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDirectory, YES);
// I have tried this with both NSUserDirectory and NSUserDomainMask, desn't seem to make a difference
NSString *documentsDir = [documentPath objectAtIndex:0];
dbPath = [documentsDir stringByAppendingPathComponent:dbName];
// dbPath = [[NSBundle mainBundle] pathForResource:#"daarma" ofType:#"sqlite"];
// check to see if the database already exists
NSFileManager *fm = [NSFileManager defaultManager];
success = [fm fileExistsAtPath:dbPath];
if(success) {
NSLog(#"Database exists, returning.");
return;
}
// if not, we create it
NSLog(#"Creating database in user profile...");
NSString *dbPathFromApp = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:dbName];
[fm copyItemAtPath:dbPathFromApp toPath:dbPath error:nil];
[fm release];
[documentPath release];
[documentsDir release];
}
When I go to insert data using this:
sqlite3 *db;
int open = sqlite3_open_v2([dbPath UTF8String], &db, -1, NULL);
if(open == 0) {
NSLog(#"open, inserting");
NSString *sql = [NSString stringWithFormat:#"insert into affiliates values('1','2','3','4','5','6','7','8','9','10','11','12')"];
int exec = sqlite3_exec(db, [sql UTF8String], NULL, NULL, NULL);
NSLog(#"exec = %d",exec);
}
sqlite3_close(db);
exec returns with the above mentioned error code 8: ""attempt to write a readonly database."
I've also tried the usual restarts, cleaning the project, resetting the simulator data. I even went in to my Simulator directory and deleted all the application data out manually. When I tried to go back in, it recognized the database wasn't there and copied it over, but I still get that error.
EDIT:
I've just noticed that if I do this in the checkDatabase method:
NSError *error;
[fm copyItemAtPath:dbPathFromApp toPath:dbPath error:&error];
NSLog(#"error = %#",error);
it causes the simulator to crash the first go around (after doing a content reset), but each time after that it resumes the above error with no crashes. So maybe I am doing something wrong with my checkDatabase method. ?? :( It never tells me the output of the error message.
try to change the open function to this
sqlite3_open_v2([dbPath UTF8String], &db, SQLITE_OPEN_READWRITE , NULL);
or basically use the trivial open function
Sqlite3_open([dbPath UTF8String],&db);// this should do the job
hope that helps :)
I tried your code, and it failed with open = 21 (SQLITE_MISUSE) in the following line:
int open = sqlite3_open_v2([dbPath UTF8String], &db, -1, NULL);
Because you pass -1 to flags parameter. It should be SQLITE_OPEN_READWRITE.
I have some more comments.
[fm release];
[documentPath release];
[documentsDir release];
These release's are not necessary, because you don't alloc/init, retain, or copy them.
NSString *sql = [NSString stringWithFormat:#"insert into affiliates values('1','2','3','4','5','6','7','8','9','10','11','12')"];
int exec = sqlite3_exec(db, [sql UTF8String], NULL, NULL, NULL);
You should use sqlite3_prepare_v2 and bind parameters instead of stringWithFormat: for SQL.
if(open == 0) { ... }
Whenever possible, you should use symbolic constant (SQLITE_OK) instead of magic number (0).
NSError *error;
[fm copyItemAtPath:dbPathFromApp toPath:dbPath error:&error];
NSLog(#"error = %#",error);
You should initialize error = nil before calling copyItemAtPath: because error is not changed when the copy operation succeeds.

Inexplicable EXC_BAD_ACCESS in the iPhone simulator

I'm trying to build a class for handling all sqlite3 work and I've encountered an EXC_BAD_ACCESS which I just can't explain. I am new to Objective-C development and memory management in general so I apologize if this is a stupid question.
When initializing the class I get the path to the database file and keep it around:
NSArray * documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
databasePath = [[[documentPaths objectAtIndex:0] stringByAppendingPathComponent:#"Database.sql"] retain];
Then I try to use it and it crashes on me:
if (sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK) { // crashes
The odd thing is that the exact same line in a different function works perfectly. I tried adding the variable initialization to above the line, but to no avail:
NSArray * documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
databasePath = [[[documentPaths objectAtIndex:0] stringByAppendingPathComponent:#"Database.sql"] retain];
if (sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK) { // crashes
In both cases the retain count is 2.
However, putting any form of static text in there works fine:
databasePath = #"I have a balloon";
if (sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK) { // fine
// or
if (sqlite3_open([#"APPLES!" UTF8String], &database) == SQLITE_OK) { // fine
A little more experimentation revealed that it was the UTF8String function that was crashing, but I don't understand why.
Edit: Even more experimentation resulted in me being able to call the UTF8String function but not even use the results:
const char * test = [databasePath UTF8String];
NSLog(#"%#", [NSString stringWithUTF8String:test]); // fine
if (sqlite3_open(test, &database) == SQLITE_OK) { // fails
Edit: Messed around even more and discovered that the problem was in a parameter that I was passing to the function. I had breakpoints all over the place so I was 100% sure that the point it fails is at the line I specified, a line which doesn't use the passed variable, but somehow it caused everything to fail. Sometimes. I have no idea how it went wrong, but I rewrote the entire thing, changed my function call and now it works.
Thanks to all who answered.
Have you checked the contents of the databasePath variable before the open call is made? Add an NSLog statement, or use the debugger, to find its value – it could be either NULL, which may not be handled by the sqlite3_open function, or it could even be that the crash is because the path is valid, and the database it points to is corrupt.
My guess is that the database variable is not declared properly. It doesn't seem to have anything to do with the path to the database.
Did you declare database like this?:
sqlite3 *database;
I suspect you are chasing the wrong problem. You haven't shown a crash dump but claim that opening "APPLES!" is 'fine' which sounds odd - surely you don't have a database with that name?
Are you sure you don't have a bad database file lying around - ie, the database file you are trying to open is corrupted in some way?
I think you have to post the crash log, your crash makes no sense. The code you posted should work.
Try a copy instead of a retain on the variable - it'll make an immutable copy of the string so it shouldn't go changing or disappearing.
EDIT::
What happens when you simply type
if (sqlite3_open[#"myDatabase.sqlite" UTF8String], &database) == SQLITE_OK) {
Also are you sure that the
NSArray * documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
databasePath = [[[documentPaths objectAtIndex:0]
stringByAppendingPathComponent:#"Database.sql"] retain];
is actually returning where the database is?
The code I use to grab the database from the app directory where kFilename is the database file is:
- (NSString *)dataFilePath{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
return [documentsDirectory stringByAppendingPathComponent:kFilename];
}
and then to use it I do:
if(sqlite3_open([[self dataFilePath] UTF8String], &database) != SQLITE_OK){
This works on both my iPod touch and the iPhone simulator.
EDIT2::
I've just used your above method and it seems to work here - I know it sounds stupid but have you tried rebooting? Its possible something strange is happening with the simulator