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

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?

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.

How to open unzip files in a unique folder each time on iOS

On my iOS application I'm unziping files in "app/temp" folder like this:
NSString *unzipFolder = [[CommonFunctions getCachePath] stringByAppendingPathComponent:#"/temp/"];
and once im done with it im deleting item with:
[[NSFileManager defaultManager] removeItemAtPath:unzipFolder error:&e];
Issue is that bcz im creating mulital copy of unzip files some of the files Images name are same and displaying wrong images, and i dont find error on why my deleting function does not work!
IS there any way that i can do unziping of folder on diffrent path for each message that open by user?
Thanks :)
It sounds like all you're asking is how to generate a unique name for your unzipFolder each time.
Just don't use a hardcoded name. Almost anything will do. For example:
NSString *unzipFolderTemplate = [[CommonFunctions getCachePath] stringByAppendingPathComponent:#"temp.XXXXXX"];
char *template = strdup([template fileSystemRepresentation]);
if (mkdtemp(template)) {
NSString *unzipFolder = [NSString stringWithFileSystemRepresentation:template
length:strlen(template)];
free(template);
// do the work
[[NSFileManager defaultManager] removeItemAtPath:unzipFolder error:&e];
}
The nice thing about mkdtemp is that it creates the directory for you, and guarantees there are no race conditions, somehow-left-over directories, or other problems. It's also more secure against, say, someone writing a crack or other jailbreak hack that exploits your code by predicting the path. The downside is, of course, that you have to drop down to C strings (which means an explicit free). But, as I said, there are many possibilities, and almost anything will do.
Also, note that I'm using #"temp.XXXXXX", not #"/temp.XXXXXX/". That's because -[stringByAppendingPathComponent:] already adds any necessary slashes for you (that is, in fact, the whole point of the method), and directory-creation functions don't need a trailing slash, so both of the slashes are unnecessary.
Meanwhile, I'm still a bit confused by what you're trying to do. If you need to keep a unique folder around for each message, and delete the folder when you're done with that message, and you could have multiple messages open at once, you need some way to remember which folder goes with which message.
For that, create an NSMutableDictionary somewhere, and right after the free(template) you'll want to do something like [tempFolderMap addObject:unzipFolder forKey:messageName]. Then, when closing a message, you'll do [tempFolderMap objectForKey:messageName] and use the result to the removeItemAtPath:error: message (and then you can also remove the key from tempFolderMap).

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]);

How to remove data before update

I have newer version app to update to app store, but since there were some obsolete data: such as database and some files that I don't need in the newer build or some databases which are use same name as newer build but the DB structure is different, so is there a way to remove all personal data of last version when user do the update? Then after user updated my app the app feels like a brand-new one.
Deleting the entire contents of your application's sandbox in one fell swoop is really something that only can be accomplished by the user removing and re-installing your application.
There are two things to consider here:
If you know where the database/files reside, you can use the NSFileManager methods to remove them explicitly. Especially removeItemAtPath:error:. The Class Reference is quite helpful.
You should proceed with caution when clearing out the entire application's state. Do your users know this is going to happen? Are they going to lose sensitive data?
There is a pattern that you can follow.
Let's say you db is called MySqliteDatabase.sqlite. There is some other data in Documents directory but you dont want to use it and delete it on first install of next version.
With the new version, rename the db to MySqliteDatabasev2.sqlite. On appstartup, use NSFileManager to look for MySqliteDatabasev2.sqlite in the documents directory. If it doesn't exist, blow away the documents directory and copy My SqliteDatabasev2.sqlite there.
For any subsequent launches, the above logic will find the v2 data and will do nothing with the documents directory.
For any subsequent version upgrades, you can follow this scheme to update database name.
Have caution: make sure to change rest of the code to use the new name as well. Best practice for that is to define it in some commons header file (i have a commons.h in every project)
#define DB_NAME #"MySqliteDbv2.sqlite"
and then make sure everywhere you are just using DB_NAME and nothing anything else. So whenever version changes:
1. rename the db file in xcode navigator.
2. rename the DB_NAME define.
That's it.
On app start up you can simply remove the entire store file with something like this.
NSString *path = <path to your file>
[[NSFileManager defaultManager] removeItemAtPath:[self persistentStorePath] error:nil];
You can use this code to get the path to the documents directory, if that is where you store the data.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [[[paths lastObject] stringByAppendingPathComponent:#"subdirectoryIfAny"]
stringByAppendingPathComponent:#"name of your .sql or whatever"];
If you do not want to blow away all the data, then you have read in the records you want to delete and then manually remove them.

Making a sqlite file stay existent between runs of the program

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.