I have an application that uses Sqlite as the db. This app has worked for a few years with no problem. With the release of iOS 9, I cannot save any of the UITextviews to the database. Everything else still saves with no problems. The app returns no error when saving and it looks like everything works, but when I look in the database the fields have null values for any data that is a blob. Does anyone know what the issue is?
This is not much help because this problem is with all my apps now with iOS 9 and UITextview saving. Here's a snippet of code:
sqlite3_stmt *statement;
const char *dbpath = [databasePath UTF8String];
if (sqlite3_open(dbpath, &BratAppsDB) == SQLITE_OK) {
const char* sqliteQuery = "UPDATE MyDatabase SET TheTextView=? Where ID='1'";
if (sqlite3_prepare_v2(BratAppsDB, sqliteQuery, -1, &statement, NULL) == SQLITE_OK) {
sqlite3_bind_blob(statement, 1, [TheUITextView.text UTF8String], -1, SQLITE_TRANSIENT);
sqlite3_step(statement);
sqlite3_reset(statement);
} else {
NSLog(#"Error is: %s", sqlite3_errmsg(BratAppsDB));
}
sqlite3_finalize(statement);
sqlite3_close(MyDB);
}
Don't use "-1", put the length:
sqlite3_bind_blob(statement, 1, [TheUITextView.text UTF8String], (int)TheUITextView.text.length, SQLITE_TRANSIENT);
JP
Related
what is wrong here?
I need use sqlite bind, because I want add HTML tags to sqlite database.
sqlite3_stmt *stmt;
const char *sql = "INSERT INTO News (id,title,description,date,removed) VALUES (?,?,\"asd\",1,0);";
if (sqlite3_prepare_v2(database, sql, -1, &stmt, NULL) == SQLITE_OK) {
sqlite3_bind_int(stmt, 0, 2);
sqlite3_bind_text(stmt, 1, [#"test_binf" UTF8String], -1, SQLITE_TRANSIENT);
if (sqlite3_step(stmt) == SQLITE_DONE) {
}
else {
NSLog(#"Error on step: %i",sqlite3_errcode(database));
}
sqlite3_reset(stmt);
sqlite3_finalize(stmt);
}
else {
NSLog(#"Error on prepare: %i",sqlite3_errcode(database));
}
I got sqlite error: "Error on step: 19".
Please help me.
The documentation says:
The leftmost SQL parameter has an index of 1.
You must use indexes 1 and 2.
Guys I have an issue with my snippet. I must also say I'm a newbie. I'm trying to insert data in to sqlite. but I keeps failing as sqlite_step == sqlite_done returns false all the time. Am I doing something wrong here. I had done something similar before and it was working fine. following is the code
sqlite3_stmt *statement;
const char *dbpath = [_databasePath UTF8String];
if(sqlite3_open(dbpath, &_db) == SQLITE_OK){
NSString *insertSQL = [NSString stringWithFormat:#"INSERT INTO userInfo (name, email, username, password) VALUES (\"%#\",\"%#\",\"%#\",\"%#\")", self.txtName.text, self.txtEmail.text, self.txtUsername.text, self.txtPassword.text];
if([self validateRegistration])
{
const char *insert_statement = [insertSQL UTF8String];
sqlite3_prepare_v2(_db, insert_statement, -1, &statement, NULL);
if(sqlite3_step(statement) == SQLITE_DONE){
[self showUIAlertWithMessage:#"User added to the database" andTitle:#"Message"];
self.txtName.text = #"";
self.txtEmail.text = #"";
self.txtUsername.text = #"";
self.txtPassword.text = #"";
self.txtConfirmPassword.text = #"";
}else{
[self showUIAlertWithMessage:#"Failed to add the user" andTitle:#"Error"];
}
sqlite3_finalize(statement);
sqlite3_close(_db);
}
}
You must check the return value of sqlite3_prepare_v2.
If either sqlite3_prepare_v2 or sqlite3_step fails, you must get the actual error message with sqlite3_errmsg.
If you check the result of sqlite3_prepare_v2, it almost certainly is not SQLITE_OK. And if you look at sqlite3_errmsg, it will tell you precisely what is wrong:
if (sqlite3_prepare_v2(_db, insert_statement, -1, &statement, NULL) != SQLITE_OK) {
NSLog(#"insert failed: %s", sqlite3_errmsg(_db));
Unrelated, but you should not use stringWithFormat to build your SQL. You should use ? placeholders in the SQL and then manually bind the values with sqlite3_bind_text() (or whatever).
const char *insert_statement = "INSERT INTO userInfo (name, email, username, password) VALUES (?, ?, ?, ?)";
if (sqlite3_prepare_v2(_db, insert_statement, -1, &statement, NULL) != SQLITE_OK) {
NSLog(#"prepare failed: %s", sqlite3_errmsg(_db));
if (sqlite3_bind_text(statement, 1, [self.txtName.text UTF8String], -1, NULL) != SQLITE_OK)
NSLog(#"bind 1 failed: %s", sqlite3_errmsg(_db));
if (sqlite3_bind_text(statement, 2, [self.txtEmail.text UTF8String], -1, NULL) != SQLITE_OK)
NSLog(#"bind 2 failed: %s", sqlite3_errmsg(_db));
if (sqlite3_bind_text(statement, 3, [self.txtUsername.text UTF8String], -1, NULL) != SQLITE_OK)
NSLog(#"bind 3 failed: %s", sqlite3_errmsg(_db));
if (sqlite3_bind_text(statement, 4, [self.txtPassword.text UTF8String], -1, NULL) != SQLITE_OK)
NSLog(#"bind 4 failed: %s", sqlite3_errmsg(_db));
if(sqlite3_step(statement) == SQLITE_DONE) {
[self showUIAlertWithMessage:#"User added to the database" andTitle:#"Message"];
self.txtName.text = #"";
self.txtEmail.text = #"";
self.txtUsername.text = #"";
self.txtPassword.text = #"";
self.txtConfirmPassword.text = #"";
}else{
NSLog(#"step failed: %s", sqlite3_errmsg(_db));
[self showUIAlertWithMessage:#"Failed to add the user" andTitle:#"Error"];
}
If you find this cumbersome, I'd suggest you consider FMDB, a SQLite wrapper, that does all of the appropriate binding of values to ? placeholders for you.
I had this issue because I haven't updated my create table statement according to my insert statements as I had made some changed some values that I am inserting.
You can use sqlite3_exec() for this:
char *err;
int code = sqlite3_exec(_db,insert_statement,NULL,NULL,&err);
if (code != SQLITE_OK) {
NSLog(#"something went wrong: %s", err);
}
You then tend to use the prepare function for reading data like this:
sqlite3_stmt *stmt;
int code = sqlite3_prepare_v2(_db,_query,-1,&stmt,NULL);
if (code == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
// Retrieve data here e.g.
// int num = sqlite3_column_int(stmt, 0);
}
}
See the documentation here for sqlite3_exec()
I am trying to delete a row from my database "Code mentioned below", however it never deletes the row and the prepare statement also never seems to be called.
I can insert and update ok but for the life of me can't figure this out..
barcode is a string variable passed to the method from a editable tableview.
I can see the correct values when I log the deletSQL string but record is actually never removed.
For further info the barcode in the database is a primary key.
Also database paths are all correct and its calling the right db file.
Its just the delete function that is not working...
-(void)delete:(NSString *)barcode{
const char *dbpath = [databasePath3 UTF8String];
if (sqlite3_open(dbpath, &database3) == SQLITE_OK)
{
NSLog(#"Opened OK");
NSString *deleteSQL = [NSString stringWithFormat: #"delete from assets where assetBarcode=\"%#\'",barcode];
NSLog(#"%#",deleteSQL);
const char *sql = [deleteSQL UTF8String];
if(sqlite3_prepare_v2(database3, sql, 1, &deleteStatement, NULL) == SQLITE_OK)
{
NSLog(#"SQL OK?");
if (sqlite3_step(deleteStatement) == SQLITE_ROW)
{
sqlite3_bind_text(deleteStatement, 1, [barcode UTF8String], -1, SQLITE_TRANSIENT);
}
}
else{
NSLog(#"WHAAAATTTTT");
}
}
sqlite3_step(deleteStatement);
sqlite3_reset(deleteStatement);
sqlite3_close(database3);
}
the barcode columne in the database appears to be of type varchar
try this
-(void)delete:(NSString *)barcode {
if (sqlite3_open([dbPath UTF8String], &database) == SQLITE_OK)
{
if(deleteStatement == nil)
{
const char *sql = "DELETE FROM assets WHERE assetBarcode = ?";
if(sqlite3_prepare_v2(database, sql, -1, & deleteStatement, NULL) != SQLITE_OK)
NSAssert1(0, #"Error while creating delete statement. '%s'", sqlite3_errmsg(database));
}
sqlite3_bind_text(deleteStatement, 1, [barcode UTF8String], -1, SQLITE_TRANSIENT);
if(SQLITE_DONE != sqlite3_step(deleteStatement))
NSAssert1(0, #"Error while deleting data. '%s'", sqlite3_errmsg(database));
else
//SQLite provides a method to get the last primary key inserted by using sqlite3_last_insert_rowid
//noteID = sqlite3_last_insert_rowid(database);
//Reset the delete statement.
sqlite3_reset(deleteStatement);
sqlite3_close(database);
deleteStatement = nil;
}
else
sqlite3_close(database);
}
Regarding the SQL string...
delete from assets where assetBarcode=\"%#\'
The parameter is being opened with a quote and closed with a single quote. Use either double or single quotes for both. Perhaps this is related to your issue?
I am trying to build a converter-like app between different measurement units. At the moment, I am using SQLite and I've got a table that includes different units alongside corresponding rates.
Inside my app I've got the following functions to retrieve rate values depending on selected units:
+(float)rateFrom:(NSString *)from to:(NSString *)to{
if (sqlite3_open([sqlPath UTF8String], &database) == SQLITE_OK) {
const char *sql = [[NSString stringWithFormat:#"SELECT %# FROM Units WHERE code = '%#'", to, from] UTF8String];
sqlite3_stmt *selectstmt;
if(sqlite3_prepare_v2(database, sql, -1, &selectstmt, NULL) == SQLITE_OK) {
return (float)sqlite3_column_double(selectstmt, 0);
}
}
else{
sqlite3_close(database);
}
return -1;
}
The problem is that the return rate is 0.00
I've tried the constructed query directly on my Terminal window and returns correct values
I've tried NSNumber and initWithFloat: with no success too.
Could anyone explain me what am I doing wrong here?
Thanks in advance.
You're never actually executing the query. sqlite3_prepare_v2 "compiles" the query, but doesn't actually run it. You're probably going to want to call sqlite3_step to actually execute the query. You're also not calling sqlite3_finalize on your compiled statement, so you're also leaking memory with this code. This might help:
+ (float)rateFrom:(NSString *)from to:(NSString *)to
{
float retVal = -1;
if (sqlite3_open([sqlPath UTF8String], &database) == SQLITE_OK) {
const char *sql = [[NSString stringWithFormat:#"SELECT %# FROM Units WHERE code = '%#'", to, from] UTF8String];
sqlite3_stmt *selectstmt;
if(sqlite3_prepare_v2(database, sql, -1, &selectstmt, NULL) == SQLITE_OK &&
sqlite3_step(selectstmt) == SQLITE_ROW) {
retVal = (float)sqlite3_column_double(selectstmt, 0);
}
if (selectstmt) {
sqlite3_finalize(selectstmt);
}
sqlite3_close(database);
database = NULL;
}
return retVal;
}
Also, while were at it, if the strings in to and from come from user input, you probably don't want to use them literally in queries, as this is vulnerable to SQL injection attacks.
You could also consider using FMDB which is a nice Objective-C wrapper for the SQLite API.
In my application i keep some data in local Sqlite db. Sqlite open, insert all working fine. but when i try to update data of a particular row/record if fails with ASSERTION FAILURE message..
* Assertion failure in -[gss_databaseHandler updateRecord::::], /Users/gss/Desktop/SalesPro copy/SalesPro/../gss_databaseHandler.m:210
NSString *databasePath;
// Method to open a database, the database will be created if it doesn't exist
-(void)initDatabase
{
// Create a string containing the full path to the sqlite.db inside the documents folder
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
databasePath = [documentsDirectory stringByAppendingPathComponent:#"contact1"];
// Check to see if the database file already exists
bool databaseAlreadyExists = [[NSFileManager defaultManager] fileExistsAtPath:databasePath];
// Open the database and store the handle as a data member
if (sqlite3_open([databasePath UTF8String], &databaseHandle) == SQLITE_OK)
{
// Create the database if it doesn't yet exists in the file system
if (!databaseAlreadyExists)
{
// Create the contactList table
const char *sqlStatement = "CREATE TABLE IF NOT EXISTS contactList (sapCustId TEXT, sapContactId TEXT, record_ID NUMERIC PRIMARY KEY, timestamp TEXT)";
char *error;
if (sqlite3_exec(databaseHandle, sqlStatement, NULL, NULL, &error) == SQLITE_OK)
{
NSLog(#"Database and tables created.");
}
else
{
NSLog(#"Error: in creating/opening database");
}
}
}
}
- (void) updateRecord:(int)recordID:(NSString *)sapCustId:(NSString *)sapContactId:(NSString *)timestamp {
[self initDatabase];
sqlite3_stmt *updateStmt = nil;
if(updateStmt == nil) {
const char *sql_stmt = "update contactList Set sapCustId = ?, sapContactId = ?, timestamp = ? Where record_ID = ?";
if(sqlite3_prepare_v2(databaseHandle, sql_stmt, -1, &updateStmt, NULL) != SQLITE_OK)
NSAssert1(0, #"Error while creating update statement. '%s'", sqlite3_errmsg(databaseHandle));
}
sqlite3_bind_text(updateStmt, 0, [sapCustId UTF8String], -1, SQLITE_TRANSIENT);
sqlite3_bind_text(updateStmt, 1, [sapContactId UTF8String], -1, SQLITE_TRANSIENT);
sqlite3_bind_int(updateStmt, 2, recordID);
sqlite3_bind_text(updateStmt, 3, [timestamp UTF8String], -1, SQLITE_TRANSIENT);
if(SQLITE_DONE != sqlite3_step(updateStmt))
NSAssert1(0, #"Error while updating. '%s'", sqlite3_errmsg(databaseHandle));
sqlite3_finalize(updateStmt);
sqlite3_close(databaseHandle);
//Reclaim all memory here.
[sapContactId release];
[sapCustId release];
}
Pls help!..
So, a couple of thoughts:
As Prateek and Lefteris suggested, you should change the order of the bind calls so that they match the order of the fields in the SQL. They also start with an index of one, not zero. I've always found it odd that the index for sqlite3_bind calls start at one, whereas the index for sqlite3_column calls start at zero, but it's just the way it works.
While it's probably fine, if you ever do an assertion, you should generally make sure you first (a) finalize any successfully prepared statements; and (b) close the database. You don't want to risk leaving your database in an inconsistent state.
You probably should not release sapContactId or sapCustId here. You didn't retain or do anything to increase their retain count, so it's probably not prudent to be releasing them here. If you run your code through the static analyzer (press shift+command+B or choose "Analyze" from the product menu), you'll undoubtedly see suggestions/warnings there.
As outlined in the official SQLite An Introduction To the SQLite C/C++ Interface, your sequence of open, prepare_v2, step, finalize, and close is perfectly correct. You do not need to call sqlite3_reset. The purpose of reset is to allow you to reuse a previous prepared sqlite3_stmt, but you're not doing that here, so reset is unnecessary here and achieves nothing.
At one point in your comments, you said that you received an error "database locked". If you're still getting that having addressed the above items, let us know where you're getting it (at open? during the prepare of a SQL statement?), as there can be different sources of this problem. Certainly multi-threaded database operations can cause this. You need help us diagnose this by showing us precisely which line you're getting this locked message on, and describe what other database operations (beyond the code in your original question) that you might be doing, especially if you're doing anything in a background queue/thread.
The challenge in solving "database locked" problems is that the problem invariably is not in the code in which you're experiencing the "database locked" error, but in some previous database interaction that you failed to handle properly. Technically, a failure to call sqlite3_finalize could cause the "database locked" problem, but in practice that's rarely the case. I see "database locked" errors usually occurring because of other issues, but we really need to know whether you're experiencing it during the opening of the database, or while trying to prepare or step a SQL statement. You need to tell us which for us to go further in the diagnosis. But, like I said, the problem undoubtedly rests in other/prior database interactions, so you might need to share more about what other database operations you're doing prior to the "database locked" error.
If you're interested in a discussion regarding the rewrite of the code in your original question, see below.
A minor suggestion, but I'd suggest altering your initDatabase method to make sure you log an error message if the sqlite3_open call fails, such as:
-(void)initDatabase
{
// Create a string containing the full path to the sqlite.db inside the documents folder
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *databasePath = [documentsDirectory stringByAppendingPathComponent:#"contact1.sqlite"];
// Check to see if the database file already exists
bool databaseAlreadyExists = [[NSFileManager defaultManager] fileExistsAtPath:databasePath];
// Open the database and store the handle as a data member
if (sqlite3_open([databasePath UTF8String], &databaseHandle) == SQLITE_OK)
{
// Create the database if it doesn't yet exists in the file system
if (!databaseAlreadyExists)
{
// Create the contactList table
const char *sqlStatement = "CREATE TABLE IF NOT EXISTS contactList (sapCustId TEXT, sapContactId TEXT, record_ID NUMERIC PRIMARY KEY, timestamp TEXT)";
char *error;
if (sqlite3_exec(databaseHandle, sqlStatement, NULL, NULL, &error) == SQLITE_OK)
{
NSLog(#"Database and tables created.");
}
else
{
NSLog(#"%s: error creating table: %s", __FUNCTION__, sqlite3_errmsg(databaseHandle));
}
}
}
else
{
NSLog(#"%s: error opening database: %s", __FUNCTION__, sqlite3_errmsg(databaseHandle));
}
}
Second, you look at insertRecord:
You want to fix the order that you use the four bind statements.
You can also eliminate the redundant if statement.
If you use assertions or return from the middle of a method doing database interactions, you always want to do a graceful cleanup. For example, you'll see if my prepare failed, I'll just close, but if the step failed, then I make sure to finalize and close.
You probably should get rid of those inappropriate release statements, moving them to the right place in the code.
You should change the method signature to have named parameters (because in the General Rules section of the Coding Guidelines, Apple says that we should name our method keywords.
Thus:
- (void) insertRecord:(int)recordID
sapCustId:(NSString *)sapCustId
sapContactId:(NSString *)sapContactId
timestamp:(NSString *)timestamp
{
[self initDatabase];
sqlite3_stmt *statement = nil;
const char *sql_stmt = "INSERT INTO contactList (sapCustId, sapContactId, timestamp, record_ID) VALUES (?, ?, ?, ?)";
if(sqlite3_prepare_v2(databaseHandle, sql_stmt, -1, &statement, NULL) != SQLITE_OK)
{
NSLog(#"%s: insert prepare error: '%s'", __FUNCTION__, sqlite3_errmsg(databaseHandle));
sqlite3_close(databaseHandle);
NSAssert(0, #"insert prepare error");
}
sqlite3_bind_text(statement, 1, [sapCustId UTF8String], -1, SQLITE_TRANSIENT);
sqlite3_bind_text(statement, 2, [sapContactId UTF8String], -1, SQLITE_TRANSIENT);
sqlite3_bind_text(statement, 3, [timestamp UTF8String], -1, SQLITE_TRANSIENT);
sqlite3_bind_int(statement, 4, recordID);
if (sqlite3_step(statement) != SQLITE_DONE)
{
NSLog(#"%s: insert step error: '%s'", __FUNCTION__, sqlite3_errmsg(databaseHandle));
sqlite3_finalize(statement);
sqlite3_close(databaseHandle);
NSAssert(0, #"insert step error");
}
sqlite3_finalize(statement);
sqlite3_close(databaseHandle);
}
Try this
You are missing sqlite3_reset, and numbering should start from 1
- (void) updateRecord:(int)recordID:(NSString *)sapCustId:(NSString *)sapContactId:(NSString *)timestamp {
[self initDatabase];
sqlite3_stmt *updateStmt = nil;
if(updateStmt == nil) {
const char *sql_stmt = "update contactList Set sapCustId = ?, sapContactId = ?, timestamp = ? Where record_ID = ?";
if(sqlite3_prepare_v2(databaseHandle, sql_stmt, -1, &updateStmt, NULL) != SQLITE_OK)
NSAssert1(0, #"Error while creating update statement. '%s'", sqlite3_errmsg(databaseHandle));
}
sqlite3_bind_text(updateStmt, 1, [sapCustId UTF8String], -1, SQLITE_TRANSIENT);
sqlite3_bind_text(updateStmt, 2, [sapContactId UTF8String], -1, SQLITE_TRANSIENT);
sqlite3_bind_text(updateStmt, 3, [timestamp UTF8String], -1, SQLITE_TRANSIENT);
sqlite3_bind_int(updateStmt, 4, recordID);
if(SQLITE_DONE != sqlite3_step(updateStmt))
NSAssert1(0, #"Error while updating. '%s'", sqlite3_errmsg(databaseHandle));
sqlite3_reset(updateStmt);
sqlite3_finalize(updateStmt);
sqlite3_close(databaseHandle);
//Reclaim all memory here.
[sapContactId release];
[sapCustId release];
}
Hope this helps you..
EDIT :-
You have not added sqlite3_finalize(); in any of your previous function and so you are getting database lock error..