Making a sqlite file stay existent between runs of the program - iphone

I'm having a problem with some sqlite code for an iPhone program in Xcode. I was opening my database like this:
int result = sqlite3_open("stealtown.db", &database);
Which is how they had it in a book I was looking at while I type the program. But then, that way of opening a database it only works when you run in simulator, not on device. So I finally figure out I need to do this:
NSString *file = [[NSBundle mainBundle] pathForResource:#"stealtown" ofType:#"db"];
int result = sqlite3_open([file UTF8String], &database);
And that works on device, EXCEPT one thing: Each time you launch the program, it starts as if you had never created the database, and when you stick an entry in the table, it's the ONLY entry in that table.
When I used the first code on the simulator, I could open my program 6 times, each time adding 1 entry to a table, and at the end, I had 6 entries in that table. With the second code, I do exact same thing but each time there is only 1 entry in that table. Am I explaining this okay, I hope so, it's hard sometimes for me.
Anyone maybe know why this would be?

On the device, the app bundle is not writable. Therefore, your database file cannot be changed. You have to create the database in your app's Documents directory on first launch. Or, if your app comes with prepopulated data, you have to copy the database file from your app bundle to the Documents directory first and then open the copy.

Related

sqlite prepare statement error - no such table

I'm having some difficulty with my sqlite prepare statement. I get an error saying my table does not exist, although I've checked in multiple places for it, and it does exist, so I'm confuzzled.
The file is in the correct iPhone Simulator Application folder
The file is added to my project and viewable in the project navigator
It is also in my build phases- Copy Bundle Resources area.
I've cleaned and started running again.
The database exists and running my sql statement gets me just the
results I expected.
- (NSMutableArray *) getMyWorkout{
NSMutableArray *workoutArray = [[NSMutableArray alloc] init];
#try {
NSFileManager *fileMgr = [NSFileManager defaultManager];
NSString *dbPath = [[[NSBundle mainBundle] resourcePath ]stringByAppendingPathComponent:#"IOSDB.sqlite"];
NSLog(#"Db path is %#",dbPath);
BOOL success = [fileMgr fileExistsAtPath:dbPath];
if(!success) {
NSLog(#"Cannot locate database file '%#'.", dbPath);
}
if(!(sqlite3_open([dbPath UTF8String], &db) == SQLITE_OK)){
sqlite3_close(db);
NSLog(#"Failed to open database with message '%s'.", sqlite3_errmsg(db));
}
const char *sql = "SELECT Id, type, difficulty, duration, description FROM workoutTbl";
sqlite3_stmt *sqlStatement;
if(sqlite3_prepare(db, sql, -1, &sqlStatement, NULL) != SQLITE_OK){
NSLog(#"%s Prepare failure '%s' (%1d)", __FUNCTION__, sqlite3_errmsg(db), sqlite3_errcode(db));
} //...
When I run it, I get the file path and the following error
2013-02-01 18:07:08.060 TriShake[9251:c07] -[MyWorkoutList getMyWorkout] Prepare failure 'no such table: workoutTbl' (1)
I've checked out these other questions, but have been unable to find a solution
Sqlite Prepare Failed: no such table<tablename>
Sqlite3 gives "no such table" error on iPhone
I understand sqlite3_open() creates an empty database for you if the database path does not exist, but i know it exists, so frustration ensues. Any help or guidance you could give me would be much appreciated.
In terms of your immediate problem, it's going to be something simple.
You say you've "cleaned and built again", but have you actually removed the old app from your simulator? Either remove the app manually, or, easier, just reset the simulator entirely by choosing "reset content and settings" from the "iOS Simulator" menu. Sometimes Xcode is not good about knowing what files to copy over (particularly in a case like this where your running it on the device may be changing the timestamp of the file in the simulator's bundle!)
Run the app again.
If the app doesn't work as expected, open up the database in the simulator folder from the Mac and check out the database to make sure the table is there and precisely as you expected it to be. So navigate to the app, open the bundle (you may have to choose the "show package contents" option), confirm the existence of the database, but just as importantly, open it up this particular copy of the database in your favorite Mac sqlite3 tool of choice and confirm the existence of the table there.
Let us know what you find. Again, it's got to be something simple such as:
Perhaps the process of rebuilding the app was not reinstalling everything; I've occasionally had problems where Xcode elected to not re-copy something during the install on my simulator;
Perhaps your database in your project was accidentally put in a subdirectory, worse, you might have two copies sitting in different directories;
Perhaps the database in your Xcode project is missing (or has a typo or (esp in the case of the device) has incorrect filename capitalization) in the name of the table or file;
Etc.
For a lot of these errors, you won't notice the problem until you completely reset the simulator itself. There are a million little things it could be, but hopefully completely resetting the simulator and starting over will help you find the issue. It's always something simple when it comes to these sorts of issues.
Some other minor observations:
You probably should not be opening databases from the bundle. Programmatically copy it from the bundle to the Documents folder, and open the database from there. I know it seems unnecessary, but it's important for a myriad of reasons (if db changes during operation of the app, if db accidentally gets created on you, don't let Xcode get confused about things that changed (even if only file timestamps) in the bundle changing behind Xcode's back, etc.)
You should, if you need the database to be there, use sqlite3_open_v2, using either SQLITE_OPEN_READWRITE or SQLITE_OPEN_READONLY for flags (but do not include SQLITE_OPEN_CREATE). It causes headaches to ever give sqlite a chance to create a blank database for you, or otherwise modify it, so never give it an opportunity to so.
I have encounter the same problem as yours. If the IOS can not find the designated database file, defaultly it will create one for you instead of throwing an error. So you must open the database file IOS created for you which is blank so it off course contain the table you expected.
what I deal with it :
1 you have to bundle the resource file named *.sqlite3 into your project
2 Then You have to use [NSBundle mainBundle] pathFordirectory...... function to search your proper database file.
then you can open the database file you expected and can operate it properly
Best regards,
Not enough rep to comment on Jack's post, but that helped me.
In my case, I had mistyped my path for resource extension:
// Wrong
NSString *sqLiteDb = [[NSBundle mainBundle] pathForResource:#"productList"
ofType:#"sqlite3"];
// Should have been (added db ext)
NSString *sqLiteDb = [[NSBundle mainBundle] pathForResource:#"productList"
ofType:#"db"];
I would always get past the:
if (sqlite3_open([sqLiteDb UTF8String], &_database) == SQLITE_OK))
because it was automatically creating a db file for me.

writing NSMutable array to plist file via writeToFile

I have a plist file called playerData that includes a number object at index 0 indicating the highest level completed. After loading the view I read this object's integer value that is used throughout the game logic. If the player wins the game I would like to increment this number and write it to the plist file. here is the code I have (contained in an if statement)
levelNumber++;
NSNumber *levelNSNum = [[NSNumber alloc] initWithInteger:levelNumber];
[playerData replaceObjectAtIndex:0 withObject:levelNSNum];
[playerData writeToFile:[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:#"PlayerData.plist"] atomically:YES];
NSLog(#"%i should be written to file", levelNumber);
the log works so I know the conditions of the if statement have been met and the value is not the same as the one that was previously in the plist file, but for some reason this data is not being written over that data.
I am relatively new to this so I could be making an easy, stupid mistake I just can't seem to track down an answer. Thank you for your help!
You're trying to write to your bundle, which is read-only after the app is installed. You should write to somewhere in your app sandbox instead, 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.)

new to sqlite3, finally come here after tried thousands ways to solve it

I'm new to Xcode and trying to learning myself, my problem may be naive but I really need helps.
I'm learning creating an ebook by learning from a finished project with source code.
It works perfectly about recording bookmark through sqlite3.
But when I create a new project with different name and path, built pharse of libsqlite3.dylib correctly and copied almost everything of the code, the else statement is called and crashes when I'm trying to visit bookmark window.
Here's some question I asked myself,
did I miss anything, or failed on changing names or paths? answered: no.
as the one runs perfectly and the other one with almost same code crashes, what's the most possible mistake? answered: the very few differences from these two project,etc path, name, content in file.
Then I changed the database file name of the good project, and it becomes crashing.
I couldn't believe, because I heard that sqlite3_open([dbPath UTF8String], &dataBase); will create file if it doesn't exist, why won't it just create a new file with the new name after it was told to access the new file?
Second test I did is manually changing the db file path of the new project to the db file path of the original one. like the path is no more [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; but it is #"/Users/~(path of the original one)~/Documents/archive" and omg it works.
But of course I can't leave it like that cause that path means nothing on a real iPhone and it still depends the db file of the original one.
At least I know the path really matters and I seems really missing some thing like db file in my new project.
But the thing is I didn't find any db file in the path of the original project, it seems exist and important, but doesn't show in finder.
I even tried to copy the file to my new path programmaticlly, like calling [self createEditableCopyOfDatabaseIfNeeded]; with the new and the old path.
That's all I tried so far.
The strange thing is it somehow record font and color after an unknown attempt, but still crashes on going to bookmark window.
That's all I tried in three days. It may help you answer the question or at least know how I was upset after thousands attempts.
Waiting for help and thank you very much!
const char *createSQL = "CREATE TABLE IF NOT EXISTS BOOKMARK(pos REAL PRIMARY KEY,\fontsize REAL,\bookpath TEXT)";
newly added: As the program automatically issue the crash when the database is not prepared, the error meg, as someone said, is useless. All I need is maybe making sqlite3_prepare(dataBase, [querySQL UTF8String], -1, &statement, nil) == SQLITE_OK not false..
The SQL you're using to create the table in the new DB appears to be messed up. Try removing the \'s
Try this instead:
const char *createSQL = "CREATE TABLE IF NOT EXISTS BOOKMARK(pos REAL PRIMARY KEY, fontsize REAL, bookpath TEXT)";
Additionally you should take a look at what sqlite3_prepare actually returns. The SQLite documentation gives a list of error codes to check against. An example of logging the error code:
int ret = sqlite3_prepare(dataBase, [querySQL UTF8String], -1, &statement, nil);
if (ret != SQLITE_OK) {
NSLog(#"Error calling sqlite3_prepare: %d", ret);
}
Why don't you print out the value written to errorMsg? That will at least tell you why you're not creating the database.
NSLog(#"%#",[NSString stringWithUTF8String:errorMsg]);

UPDATE doesn't run with FMDB, runs fine with sqlite command line

I'm using FMDB to interface with an SQLite database. I have it inserting rows fine, but when I try and UPDATE one of them, the UPDATE does not occur, but no error is reported.
Here's what I'm doing:
userHistoryDB.logsErrors = YES;
userHistoryDB.traceExecution = YES;
NSString *query = [NSString stringWithFormat:
#"INSERT OR IGNORE INTO phrase_history (code, %#) VALUES (\"%#\", 0); "
#"UPDATE OR FAIL phrase_history SET %# = %# + 1 WHERE code = \"%#\";",
countTypeColumn,
phraseCode,
countTypeColumn,
countTypeColumn,
phraseCode];
BOOL rc = [userHistoryDB executeUpdate:query];
rc is YES, to indicate nothing is wrong. Here is an example call:
<FMDatabase: 0x5303af0> executeUpdate: INSERT OR IGNORE INTO phrase_history (code, presented) VALUES ("grapefruit", 0); UPDATE OR FAIL phrase_history SET presented = presented + 1 WHERE code = "grapefruit";
As far as FMDB is concerned, this has executed fine. However, the record does not increment. There is nothing wrong whatsoever with the SQL - if I paste it into the sqlite3 command line tool, it runs perfectly.
Any ideas as to why it's not running? Things I have tried to no avail:
Enclosing the line in a transaction
Running using executeQuery
Running just the update (with the entry already manually inserted).
Slamming head against desk.
You have two sqlite statements there, and FMDB won't automatically convert your single compound line into two separate statements. You need to run them separately, which is what the sqlite3 command line tool is doing.
where your database is stored? in the bundle? or in other location?
if you have your database in your project folder, when you compile, Xcode will create the app bundle in build/Debug or build/Release, this is where your database will be located, not the one in your project folder. Check if the database in build/Debug(Release) is updated? The database in your project folder will not be updated, since you may be request the location of your app bundle.
when you check the database by the command line, did you check the database in your project bundle or in the build/Debug(Release) folders?

NSHomeDirectory returns different path each time iPhone App is restarted

I noticed that NSHomeDirectory returns a different path each time i restart the App with Xcode, and apparently even if i click the icon manually since it doesn't load the file's contents. I'm stunned that it gives me a different directory each time i restart the app. This happens on both simulator and device and even if i use the "ForUser" method. My calls look like this:
NSString* fullPath = [NSHomeDirectory() stringByAppendingPathComponent:#"test.file"];
or
NSString* fullPath = [NSHomeDirectoryForUser(#"MrX") stringByAppendingPathComponent:#"test.file"];
or even
NSString *docsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *path = [docsDirectory stringByAppendingPathComponent:#"test.file"];
And the log of two app starts looks like this, notice how the GUID part has changed:
loadCharacters: /var/mobile/Applications/ABE7E33E-439B-4258-8FC1-127A3CD00D87/test.file
loadCharacters: /var/mobile/Applications/71C02507-6347-4693-8CC1-537BE223179E/test.file
What am i doing wrong, or what am i missing?
SOLUTION:
At first i was saving to NSHomeDirectory itself, not the "Documents" subdirectory. Since the App directory changes with each deployment but only the files in the "Documents" directory get copied, the saved file was lost after each deployment. Then i used the "Documents" folder but someone suggested i should probably put it in a subfolder of "Documents" i did just that but forgot to create the folder, hence the file wasn't saved. I double-failed on this one. :)
Simply saving to NSHomeDirectory() + "Documents/test.file" works.
You're doing this correctly. Each time an app is installed into the simulator or device, it's placed into a different directory (I'm not sure why, but that's what happens). However, all of its directory structure is moved to the new position. Thus, your RELATIVE paths will remain unchanged.
The GUID portion of an app's home directory appears to be just a random UUID (as indicated by the 3rd group of hex digits starting with a '4' and the 4th group starting with '8','9','A' or 'B'. See the wikipedia entry on 'universally unique identifier' as a good starting place if you want to learn more about UUIDs and unique IDs in general. The reason for using a unique id in the directory path is just to separate one app (and multiple deploments of the same app) from one another as part of sandboxing (IMHO).