SQLite update column if another column exists - ios

Okay, so I'm not sure exactly how to name this question but I can explain what my issue is. I'm creating a webbrowser, and using an sqlite database to store the history. When the page finished loading my method:
-(void)addHistory:(NSString*)title address:(NSString*)url{
sqlite3_stmt *statement;
const char *dbpath = [_databasePath UTF8String];
if (sqlite3_open(dbpath, &_historyDB) == SQLITE_OK)
{
NSString *updateSQL = [NSString stringWithFormat:
#"UPDATE HISTORY SET title='%#' WHERE url='%#'", title, url];
const char *update_stmt = [updateSQL UTF8String];
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(_historyDB, update_stmt, -1, &stmt, NULL)==SQLITE_OK) {
NSLog(#"Updated");
}else{
NSString *insertSQL =
[NSString stringWithFormat:
#"INSERT INTO HISTORY (title, url, visits, date, search) VALUES (\"%#\", \"%#\", \"%#\", \"%#\", \"%#\")",
title, url, #"todo", #"todo", [NSString stringWithFormat:#"%# %#", title, url]];
const char *insert_stmt = [insertSQL UTF8String];
sqlite3_prepare_v2(_historyDB, insert_stmt,
-1, &statement, NULL);
if (sqlite3_step(statement) == SQLITE_DONE)
{
NSLog(#"Added Entry");
} else {
NSLog(#"Coundn't Add Entry");
}
sqlite3_finalize(statement);
}
sqlite3_close(_historyDB);
}
}
I assumed that if the update failed (because the url is not present in the table) it would move to the else statement and add the key. The main idea behind this is to update the title of the webpage, and once that is working, update other fields such as last visited, amount of times viewed and so on..
I've experimented with a variety of different methods but I just can't seem to achieve the desired results.

To execute the update statement, you must call sqlite3_step for that statement.
To find out how many records were updated, call sqlite3_changes.
And you must handle any errors returned by the sqlite3_prepare_v2 calls.
And you must call sqlite3_finalize for stmt.
And any website with ' in its URL or title will corrupt your SQL statements; to avoid SQL injection attacks, use parameters.

I ended up making the switch to FMDB, a brilliant Objective-C wrapper for SQLite. I then performed a SELECTstatement where the url matched the current one, if it returned a value I updated the database, if not, I simply added to it.

Related

Primary Key Insertion - SQLite with Objective-C

I'm trying to write user information to an SQLite database using Objective-C. I read on the SQLite website FAQ (http://www.sqlite.org/faq.html) to enter NULL in place of the primary key, which I have done. However, I get the error code 20 (data type mismatch) when I perform NSLog(#"%d",sqlite3_step(sqlStatement). Below is the relevant code.
Before anyone asks, I have appropriately opened the database in previous code, so that isn't the issue. writableDBPath is the path of the database, verified to be valid.
sqlite3_stmt *sqlStatement;
NSString *momentInsertSQL = [NSString stringWithFormat:#"INSERT INTO MomentTbl (momentID, albumID, title, timestamp, author, latitude, longitude, canvas) VALUES (\"%#\", \"%d\", \"%#\", \"%#\", \"%#\", \"%#\", \"%#\", \"%#\")",NULL, alb.ID, alb.title, m.timestamp, #"user", m.latitude, m.longitude, m.moment];
const char *query_stmt = [momentInsertSQL UTF8String];
const char *dbPath = [writableDBPath UTF8String];
if(sqlite3_open(dbPath, &db) == SQLITE_OK) {
if(sqlite3_prepare(db, query_stmt, -1, &sqlStatement, NULL) == SQLITE_OK)
{
sqlite3_prepare_v2(db, query_stmt, -1, &sqlStatement, NULL);
NSLog(#"%d", sqlite3_step(sqlStatement));
if(sqlite3_step(sqlStatement) == SQLITE_DONE)
NSLog(#"OK");
else
NSLog(#"Problem");
sqlite3_finalize(sqlStatement);
sqlite3_close(db);
}
}
When I replaced NULL with a number, the code returned was 101, which should be SQLITE_DONE, but NSLog still showed "Problem." Any help is appreciated, as someone who is new to SQLite I really need it.
SQL NULL is different from C NULL.
Replace the first \"%#\" in your values with literal NULL and remove the NULL from the format args, e.g:
NSString *momentInsertSQL = [NSString stringWithFormat:#"INSERT INTO MomentTbl (momentID, albumID, title, timestamp, author, latitude, longitude, canvas) VALUES (NULL, \"%d\", \"%#\", \"%#\", \"%#\", \"%#\", \"%#\", \"%#\")", alb.ID, alb.title, m.timestamp, #"t-panda", m.latitude, m.longitude, m.moment];
Alternatively you could just leave out the primary key momentID altogether. sqlite inserts a NULL in columns you didn't specify a value for.
I think it's because you have two sqlite3_step calls without a sqlite3_reset in between.
The one in NSLog returns 101 (SQLITE_DONE), but the one in the if statement would probably return SQLITE_MISUSE (http://www.sqlite.org/c3ref/step.html).

Using NSString/parameters into SQLite statement iOS

I have my code below. My values _emailVaue _passwordValue and _nameValue are taken from UITextFields in my app. I was under the impression that passing these values into parameters in my SQLite code would allow me to have special characters in those values such as double quotation marks ( " ), however, if I put them in, the SQLite crashes. Is there something I'm doing wrong?
I'm aware that it's probably best to use something like FMDB, but I was hoping that there might be a quicker fix to get me through an upcoming demo.
I'd appreciate any help, thanks!
if (sqlite3_open(dbpath, &_contactDB) == SQLITE_OK)
{
NSString *insertSQL = [NSString stringWithFormat:
#"INSERT INTO CONTACTS (email, password, name) VALUES (\"%#\", \"%#\", \"%#\")",
_emailValue, _passwordValue, _nameValue];
NSLog(insertSQL);
const char *insert_stmt = [insertSQL UTF8String];
sqlite3_prepare_v2(_contactDB, insert_stmt,
-1, &statement, NULL);
if (sqlite3_step(statement) == SQLITE_DONE)
{
} else {
}
sqlite3_finalize(statement);
sqlite3_close(_contactDB);
}
I hope it's OK to answer my own question if it seems to work. Hopefully someone will find it useful. Would appreciate any feedback on where I might be going wrong.
sqlite3_stmt *statement;
const char *dbpath = [_databasePath UTF8String];
const char *insertSQL;
if (sqlite3_open(dbpath, &_contactDB) == SQLITE_OK)
{
insertSQL = "INSERT INTO CONTACTS (email, password, name) VALUES (?, ?, ?)";
if(sqlite3_prepare_v2(_contactDB, insertSQL, -1, &statement, NULL) == SQLITE_OK)
{
sqlite3_bind_text(statement, 1, [_emailValue UTF8String], -1, SQLITE_TRANSIENT);
sqlite3_bind_text(statement, 2, [_passwordValue UTF8String], -1, SQLITE_TRANSIENT);
sqlite3_bind_text(statement, 3, [_nameValue UTF8String], -1, SQLITE_TRANSIENT);
}
if (sqlite3_step(statement) == SQLITE_DONE)
{
//worked
} else {
//didn't work
}
sqlite3_finalize(statement);
sqlite3_close(_contactDB);
}
I'll try to explain what happened with your code and how it could be improved so that the crash would not occur. I totally agree with the usage of bound arguments, this answer is posted only as it represents an answer to how your crash can be fixed, and might help people that don't have the time to switch to bound statements.
Here's what happened:
sqlite3_prepare_v2() failed as your query string was invalid due to the fact that your strings contained the " characted
due to the above, statement was either NULL or contained a garbage value
sqlite3_step() crashed as an invalid pointer was passed as argument.
Here's the fix:
escape all your strings, by replacing " by \", this will generate a valid query; if you were using ' in your query, then you would have to replace ''s by \'
Example for email:
NSString *escapedEmail = [_emailValue stringByReplacingOccurrencesOfString:#"\"" withString:#"\\\""];
even if you're sure the query is correct, is still mandatory to check the result of sqlite3_prepare_v2() before using the statement in any other sqlite calls.
As a general note, you need to code very defensively when dealing with C API's as C doesn't forgive you if you forget to check for NULL pointers. Objective-C is more soft, as it allows you to send messages to nil objects.

How to update an entry in sqlite3?

Im super new to ios (Objective-C) and sql and Im following a tutorial of sqlite (specifically sqlite3) found here: http://www.tutorialspoint.com/ios/ios_sqlite_database.htm
and i implemented everything correctly. The problem is that if I try to enter information with the same reg id (The primary key which is used to find elements in the database), it complains about the key not being unique.
- (BOOL) saveData:(NSString*)registerNumber name:(NSString*)name
department:(NSString*)department year:(NSString*)year;
{
const char *dbpath = [databasePath UTF8String];
if (sqlite3_open(dbpath, &database) == SQLITE_OK)
{
NSString *insertSQL =
[NSString stringWithFormat:#"insert into studentsDetail (regno,name, department, year) values (\"%d\",\"%#\", \"%#\", \"%#\")",[registerNumber integerValue], name, department, year];
const char *insert_stmt = [insertSQL UTF8String];
sqlite3_prepare_v2(database, insert_stmt,-1, &statement, NULL);
int s = sqlite3_step(statement);
const char *errmsg = sqlite3_errmsg(database);
if (s == SQLITE_DONE)
{
sqlite3_reset(statement);
return YES;
}
else {
sqlite3_reset(statement);
return NO;
}
}
return NO;
}
How do I go about making the saveData: action actually update the entry inside the database instead of just being rejected?
Thanks!
"I want to extend it so that if I try to enter an existing reg id, it
will overwrite the remaining properties with the new ones"
If this is what you are trying to accomplish then you should not be using an INSERT statement. The purpose of the primary key is to protect the database from duplicate rows with the same primary key. When you look at it that way, the rejection is appropriate.
What you really want is to use INSERT OR REPLACE
If you're a beginner to both sql and objective c, I'd recommend a sqlite wrapper I created that is aimed at beginners: TankDB
Edit:
Code example:
INSERT OR REPLACE INTO studentsDetail (regno,name, department, year) values (\"%d\",\"%#\", \"%#\", \"%#\")"
Refer to this question to make sure that you don't actually mean to use 'UPSERT'. Beware that INSERT OR REPLACE does not update the existing row with the new information. It will completely delete the existing row and replace it.
Edit2: Open, prepare, step, finalize, close
const char *dbpath = [_databasePath UTF8String];
if(sqlite3_open(dbpath, &_database) == SQLITE_OK) {
const char *command = [query UTF8String];
// Perform the query
if (sqlite3_prepare_v2(_database, command, -1, &selectstmt, NULL) == SQLITE_OK) {
// For each row returned
while (sqlite3_step(selectstmt) == SQLITE_ROW) {
// Do stuff
}
}
sqlite3_finalize(selectstmt);
}
sqlite3_close (_database);

sqlite query with multiple condition giving error : library routine called out of sequence

I am creating an app and using sqlite 3 for storing data.
Here is my code I am executing to fetch data
sqlite3_stmt *statement;
NSString *querySQL = [NSString stringWithFormat: #"SELECT ID, HolderName, AccountNumber, BankName, Location FROM Accounts WHERE UserID='%#'",userID];
if(![name isEqualToString:#""])
{
querySQL = [NSString stringWithFormat: #"SELECT ID, HolderName, AccountNumber, BankName, Location FROM Accounts WHERE UserID='%#' and HolderName='%#'",userID, name];
}
const char *query_stmt = [querySQL UTF8String];
if (sqlite3_prepare_v2(db, query_stmt, -1, &statement, NULL) == SQLITE_OK)
{
while(sqlite3_step(statement) == SQLITE_ROW)
{
....
}
}
What is happening here, if name is empty then query(only with one condition in where clause) runs properly and fetches proper data but its giving error : library routine called out of sequence if name is not empty and query contains two conditions in where clause.
I am not sure what is wrong with it, I have searched a lot but found that query runs perfect with multiple conditions.
Please suggest me what should I do here.

Error with SQL update Statement

I want to update something in a sql database, but every time it gives me an error ...
This is my code:
// UPDATE SQL
- (void)sqlId:(int)sqlId text:(NSString *)text time:(NSString *)time preis:(NSString *)preis
{
[self openDb];
if (sqlite3_open([dbPath UTF8String], &database) == SQLITE_OK) {
NSString *insertSQL = [NSString stringWithFormat:
#"UPDATE `webdesign` SET data='%#', time='%d', preis='%#' WHERE id='%d')",
text, [time intValue], preis, sqlId];
const char *insert_stmt = [insertSQL UTF8String];
sqlite3_prepare_v2(database, insert_stmt, -1, &statement, NULL);
if (sqlite3_step(statement) == SQLITE_DONE) {
NSLog(#"updated");
} else {
NSLog(#"Error");
}
sqlite3_finalize(statement);
}
[self closeDb];
}
The console says:
2013-08-31 12:07:21.366 Webdesign[6519:a0b] database opened
2013-08-31 12:07:21.367 Webdesign[6519:a0b] Error
2013-08-31 12:07:21.367 Webdesign[6519:a0b] database closed
maybe it's a simple problem, but I can't find it.
without knowing the table structure of your SqLite3 database, it's not possible to provide you with a 100% correct answer, but I suspect the line
UPDATE `webdesign` SET data='%#', time='%d', preis='%#' WHERE id='%d')
to be erroneous. In fact, you put decimals between single quotes, you put the name of the table between single quotes, ...
I tend to use an NSLog to write out the created statement, and copy/paste that statement in my sqlite3 command line tool (or in Valentina Studio, which I use often) to check if the created statement is correct. This has saved me lots of time.
Kind regards,
PB
DAMN I've found the error ...
#"UPDATE `webdesign` SET data='%#', time='%d', preis='%#' WHERE id='%d')"
The ")" is the mistake ! :D
always such a small error -.-

Resources