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!..
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..
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);
}