I am doing some database operations in IOS. Basically I want to do this in a background thread. I tried using GCD. But the issue for me is I want to get some values from this process after it is finished. Say before inserting an item to database I check whether the item already exists. Please see code below
__block Boolean isExisting = false;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
NSString *path = [SqliteManager initDatabase];
if(sqlite3_open([path UTF8String], &database) == SQLITE_OK)
{
NSString *query = [NSString stringWithFormat:#"select count (campaignId) from POTC where Id='%#' and taskid='%#' and pocId='%#' and userId='%#'",[submission.campaignId stringRepresentation],[submission.taskId stringRepresentation],[submission.pocId stringRepresentation],[[UUID UUIDWithString:submission.userId] stringRepresentation]];
const char *sql = [query cStringUsingEncoding:NSASCIIStringEncoding];
sqlite3_stmt *selectStatement;
if (sqlite3_prepare_v2(database, sql, -1, &selectStatement, NULL) == SQLITE_OK)
{
while (sqlite3_step(selectStatement) == SQLITE_ROW)
{
if (sqlite3_column_int(selectStatement, 0) >0)
{
isExisting = true;
break;
}
}
sqlite3_finalize(selectStatement);
}
sqlite3_close(database);
}
return isExisting;
});
But the above code with return statement wont work as dispatch-async is expecting a void code block. How can i achieve the same in IOS? Is there something similar to animation completion block in IOS?
The block has to have a return type of void because there is nowhere to return the value to in an asynchronous block.
The variable isExisting is qualified __block which means it will be set whenever the block assigns to it. Unfortunately, your main thread won't have access to it once it has exited the scope. The wary to do this is for your block to call another method (or function, or block) that sets a variable or property that you know will still be around when the asynchronous block has finished.
e.g. you could have a method on the app delegate to invoke on completion.
// in your appDelegate implementation
-(void) updateUIAfterDatabaseUpdate: (bool) isExisting
{
if (isExisting)
{
// e.g. display an error
}
else
{
// successful update
}
}
// The update code
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
bool isExisting = false;
NSString *path = [SqliteManager initDatabase];
if(sqlite3_open([path UTF8String], &database) == SQLITE_OK)
{
// Snipped for clarity
}
dispatch_async(dispatch_get_main_queue(), ^(void) {
[appDelegate updateUIAfterDatabaseUpdate: isExisting] ;
});
});
The dispatch on the main queue ensures that the method is called in the main thread so it can do UI updates.
Maybe you should create a function with a completion block.
I define mines like this :
typedef void (^myBlock)(type1 param1,
type2 param2);
-(void)myAsyncFunctionWithParam:(id)paramSend withCompletionBlock:(myBlock)returningBlock
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//here everything you want to do
// especially defining 2 parameters to return (let them be p1 & p2)
dispatch_async(dispatch_get_main_queue(), ^{
returningBlock(p1,p2);
});
});
}
and you can use it like :
[self myAsyncFunctionWithParam:ps
withCompletionBlock:^(type1 paramToUse1,
type2 paramToUse2)
{
//You can use here paramToUse1 and paramToUse2
}
];
You can use whatever type you want for type in the block : NSString, NSDate,... (don't forgive * if needed)
You don't have to return something because isExisting will become true and if you access its value after completion of block execution, it returns true.
The way you are thinking loses the benefit of the background thread :). You should restructure your program to fit better with the asynchronous paradigm. You launch your background work asynchronously and then when it's finished then you can send a message to a receiver to do another work. In your code for example you can use notification or a simple message send. like this :
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
NSString *path = [SqliteManager initDatabase];
if(sqlite3_open([path UTF8String], &database) == SQLITE_OK)
{
NSString *query = [NSString stringWithFormat:#"select count (campaignId) from POTC where Id='%#' and taskid='%#' and pocId='%#' and userId='%#'",[submission.campaignId stringRepresentation],[submission.taskId stringRepresentation],[submission.pocId stringRepresentation],[[UUID UUIDWithString:submission.userId] stringRepresentation]];
const char *sql = [query cStringUsingEncoding:NSASCIIStringEncoding];
sqlite3_stmt *selectStatement;
if (sqlite3_prepare_v2(database, sql, -1, &selectStatement, NULL) == SQLITE_OK)
{
while (sqlite3_step(selectStatement) == SQLITE_ROW)
{
if (sqlite3_column_int(selectStatement, 0) >0)
{
// Here you can send the notification with the data you want.
break;
}
}
sqlite3_finalize(selectStatement);
}
sqlite3_close(database);
}
return isExisting;
});
Related
I had debugged code but don't know why While statement is not executing . After putting NSlog I came to know it's mismatching the result. 101 and SQLITE_ROW is equal to 100.
But query has result. If I fetch Select * from table name its returning me result and while statement executes. If I fetch result
sql_stmt = [NSString stringWithFormat:#"SELECT * FROM DeviceType WHERE upper(TypeName) = upper('%#')", deviceType];
While statement never execute so please help. don't know where is the problem.
Here is the code:
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);
NSString* documentsDirectory = [paths objectAtIndex:0];
NSString* path = [documentsDirectory stringByAppendingPathComponent:#"ClientDB.sqlite"];
NSString* sql_stmt = [self searchStringForLocalData];
BOOL found = NO;
if (sqlite3_open([path UTF8String], &database) == SQLITE_OK)
{
const char* sql1 = [sql_stmt UTF8String];
sqlite3_stmt *statement;
NSNumber *typeCount;
typeCount = 0;
if (sqlite3_prepare_v2(database, sql1, -1, &statement, NULL) == SQLITE_OK)
{
NSLog(#" %d, %d ",sqlite3_step(statement),SQLITE_ROW);
while (sqlite3_step(statement) == SQLITE_ROW) {
// The second parameter indicates the column index into the result set.
int primaryKey1 = sqlite3_column_int(statement, 0);
NSNumber *typeId = [[NSNumber alloc] initWithInt:primaryKey1];
NSString *typeName =[NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 1)];
NSString* sql_stmt1 = [NSString stringWithFormat:#"SELECT COUNT(*) FROM Devices WHERE DeviceTypeId =%d",primaryKey1];
const char *sql1 = [sql_stmt1 UTF8String];
sqlite3_stmt *statement;
NSNumber *typeCount;
typeCount = 0;
As already mentioned in a comment, the code calls sqlite3_step once for the NSLog, and then a second time in the condition of the while loop.
If your query returns exactly one record, the first sqlite3_step call will step to that record, and the second sqlite3_step call will then report (correctly) that no further results are available.
Just remove that NSLog call.
Alternatively, call sqlite3_step only once, and use the same return value for both the logging and the loop:
int rc;
...
while (1) {
rc = sqlite3_step(statement);
NSLog(#" %d, %d ", rc, SQLITE_ROW);
if (rc != SQLITE_ROW)
break;
...
}
if (rc != SQLITE_DONE)
NSLog(#"error: %s", sqlite3_errmsg(database));
Try to use the following syntax
while(sqlite3_step(stmt)!=SQLITE_DONE)
{
//do ur processing
for(i=0;i<NO_OF_COLS;i++)
{
char * tmp1=sqlite3_column_text(stmt,i);
if(tmp1!=NULL)
{
//use the value
}
else
{
//use a default
}
}
}
It shows that your table is totally empty. You may want to check your insert whether it works correctly. If you're using sqlite3_prepare_v2 to prepare your statement with some ? marks, remember to execute your query after all. Simply like this:
if(sqlite3_step(stmt) == SQLITE_DONE) NSLog(#"INSERTED");
else NSLog(#"NO INSERTED");
Note: It will display error only if your query is wrong, but not your prepared statement was executed or not.
I'm trying to get data from my DB but I have some problem.
Here is my code:
NSString *action=[[NSString alloc]init];
NSString *queryStatement = [NSString stringWithFormat:#"SELECT ACTIONNAME FROM ACTIONS WHERE ACTIONSYMBOL = '%#'", symbol];
// Prepare the query for execution
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(database, [queryStatement UTF8String], -1, &statement, NULL) == SQLITE_OK)
{
// Create a new address from the found row
while (sqlite3_step(statement) == SQLITE_ROW) {
action = [NSString stringWithUTF8String:(char*)sqlite3_column_text(statement, 1)]; // fails on this line
}
sqlite3_finalize(statement);
return action;
}
(the parameter symbol come from outside)
When I run this, it fails at the line with the call to stringWithUTF8String with sqlite3_column_text results.
You probably want to collect the results in an NSMutableArray:
NSMutableArray *action = [[NSMutableArray alloc] init];
...
while (sqlite3_step(statement) == SQLITE_ROW) {
[action addObject:[NSString stringWithUTF8String:(char*)sqlite3_column_text(statement, 0)]];
}
...
You can then see what was collected later:
for (NSString *s in action) {
NSLog(#"%#", s);
}
EDIT As pointed out in #Rob's answer, the first column is 0, not 1.
In your call to sqlite3_column_text, you're using the index 1, but it takes a zero-based index. Use 0 instead of 1. See the SQLite sqlite_column_XXX documentation, which says:
The leftmost column of the result set has the index 0.
By the way, since stringWithUTF8String throws an exception if you pass it a NULL value, it's often safer to check the result if sqlite3_column_text is not NULL before proceeding, and handle the error gracefully otherwise. Also, you might want to check for sqlite3_step and sqlite3_prepare_v2 errors, like so:
NSString *queryStatement = [NSString stringWithFormat:#"SELECT ACTIONNAME FROM ACTIONS WHERE ACTIONSYMBOL = '%#'", symbol]; // note, it can be dangerous to use `stringWithFormat` to build SQL; better to use `?` placeholders in your SQL and then use `sqlite3_bind_text` to bind the `symbol` value with the `?` placeholder
if (sqlite3_prepare_v2(database, [queryStatement UTF8String], -1, &statement, NULL) == SQLITE_OK)
{
int rc;
while ((rc = sqlite3_step(statement)) == SQLITE_ROW) {
const unsigned char *value = sqlite3_column_text(statement, 0); // use zero
if (value) {
NSString *action = [NSString stringWithUTF8String:(const char *)value];
// now do whatever you want with `action`, e.g. add it to an array or what
} else {
// handle the error (or NULL value) gracefully here
}
// make sure to check for errors in `sqlite3_step`
if (rc != SQLITE_DONE)
{
NSLog(#"%s: sqlite3_step failed: %s", __FUNCTION__, sqlite3_errmsg(database));
}
}
}
else
{
NSLog(#"%s: sqlite3_prepare_v2 failed: %s", __FUNCTION__, sqlite3_errmsg(database));
}
Incidentally, as the above illustrates, to correctly perform all of the error checking is a little cumbersome. This is where FMDB can be useful, simplifying the above to (where db is an FMDatabase object that has been opened):
FMResultSet *rs = [db executeQuery:#"SELECT ACTIONNAME FROM ACTIONS WHERE ACTIONSYMBOL = ?", symbol];
if (!rs) {
NSLog(#"%s: executeQuery failed: %#", __FUNCTION__, [db lastErrorMessage]);
return;
}
while ([rs next]) {
NSString *action = [rs stringForColumnIndex:0];
// do whatever you want with `action` here
}
[rs close];
And if you use ? placeholders (rather than using stringWithFormat to build your SQL, which is dangerous) the benefits of using FMDB are even more compelling.
I am deleting records from the database.and it is working fine .But it is crashing when there is no data in the database. The code is given Below
int noOfRecordsDeleted;
[self openDatabaseConnection];
// query = [NSString stringWithFormat:#"delete from %# where %#",query];
NSLog(#"del query=%#",query);
const char *sql = [query cStringUsingEncoding:NSUTF8StringEncoding];
sqlite3_stmt *statement = nil;
if(sqlite3_prepare_v2(dataBaseConnection,sql, -1, &statement, NULL)!= SQLITE_OK)
{
NSAssert1(0,#"error preparing statement",sqlite3_errmsg(dataBaseConnection));
}
else
{
int success = sqlite3_step(statement);
NSLog(#"%d",success);
}
sqlite3_finalize(statement);
noOfRecordsDeleted = sqlite3_changes(dataBaseConnection);
[self closeDatabaseConnection];
return noOfRecordsDeleted;
It is working fine. But if i am adding data i empty database it is crashing
[self openDatabaseConnection];
NSString *query;
query = [NSString stringWithFormat:#"insert into Userdetail(aboutMe,anniversary,areacode,birthdate,device_id,chat_id,emailid,gender ,image,last_login,latitute,longitude,Looking_For,mobilenumber,mobilenumber1 ,mobilenumber2,mood,name,password,place,profileviews,statusmessage) values ('%#','%#','%#','%#','%#','%#','%#','%#','%#','%#','%#','%#','%#','%#','%#','%#','%#','%#','%#','%#','%#','%#')",aboutMe,anniversarydate,areacode,birthdate,
device_id,chat_id,email_id,gender,image,last_login,latitude,longitude,looking_for,mobilenumber,mobilenumber1,mobilenumber2,mood,name,password,place,profileviews,statusmessage];
NSLog(#"saveNewTemplateData query=%#",query);
const char *sql = [query cStringUsingEncoding:NSUTF8StringEncoding];
sqlite3_stmt *statement = nil;if(sqlite3_prepare_v2(dataBaseConnection,sql , -1, &statement, NULL)!= SQLITE_OK)
{
NSAssert1(0,#"error preparing statement",sqlite3_errmsg(dataBaseConnection));
}
else
{
sqlite3_step(statement);
}
sqlite3_finalize(statement);
[self closeDatabaseConnection];
above is the code
First of all check the Database is empty or not by using the following statement
if(sqlite3_column_text(statement, 0) != nil){//Data exists}
Then in the if method add the code to delete the dataBase.
It works fine.
query = [NSString stringWithFormat:#"delete from %# where %#",query];
what does this query means I think its the mistake of your query please doublecheck it
if the error persists programmatically check
by using if(sqlite3_column_text(statement, 0) != nil)
One issue I saw that you are calling sqlite3_finalize(statement); regardless the state of sqlite3_prepare_v2. You only need to call sqlite3_finalize if the sqlite3_prepare_v2 executed successfully.
Change that to:
if(sqlite3_prepare_v2(dataBaseConnection,sql, -1, &statement, NULL)!= SQLITE_OK)
{
NSAssert1(0,#"error preparing statement",sqlite3_errmsg(dataBaseConnection));
}
else
{
int success = sqlite3_step(statement);
NSLog(#"%d",success);
sqlite3_finalize(statement);
}
if (condition for check database empty (select * from table)){
}else{
//Perform Delete operation
}
I have created two tables(vcards, viewdownloads) in sqlite3 database in iOS. At the beginning of my app starts Iam inserting my address book contacts to the sqlite3 database in to "vcards" table using background process.
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
[self setNotificationForReachability];
dispatch_queue_t loadQueue = dispatch_queue_create("Image loader", NULL);
dispatch_async(loadQueue, ^{
// Your code to run in the background here
GVDBManager *objDB = [GVDBManager getSharedInstance];
[objDB getContactsFromAddBook];
[objDB syncPhoneBookWithVcardsTable];
});
}
in the middle of this process of inserting contacts into "vcards" table, I have called the another background process to update some values using update query in same "vcards" table
after this process completion again Iam calling one more background process to insert the some contacts to the "viewdownloads" table and displaying the contacts using UI.
my questions:
1.After click on one button, Reading the contacts from database and this is taking some time to load into array(aim reading 800 contacts). Iam using background process to do this.
2.Every time iam opening the database and closing it while inserting and updating the tables using background process.This causes my app crash and this is the major issue iam facing.
can any explain clearly how to handle multiple actions(inserting, updating and searching) on same database.
thanks in Advance.
1) Are you sure your method doesn't need to work on main thread? maybe some instruction need it.
2) Detect specific exeption ( or EXC_BAD_ACCESS ). I suggest you to enable All breakpoints. see image below:
3) Here main functions to open query and close db with sqlite. I use something like these without any problem
Sample function for open db connection
- (NSError *) openDatabase {
NSError *error = nil;
sqlite3* db = self.db; // temp ref
const char *dbpath = NULL;
dbpath = [self.databaseName UTF8String]; // disk-based db
// DB opening
int result = sqlite3_open(dbpath, &db);
if (result != SQLITE_OK) {
// const char *errorMsg = sqlite3_errmsg(db);
// ....
}else
self.db = db;
return error;
}
Sample function for close db connection
- (NSError *) closeDatabase {
NSError *error = nil;
if (self.db != nil) {
if (sqlite3_close(self.db) != SQLITE_OK){
// const char *errorMsg = sqlite3_errmsg(self.db);
// ...
}
self.db = nil;
}
return error;
}
Sample function for query DB using PDO (not SELECT clause)
- (NSError *)doPDOQuery:(NSString *)sql withParams:(NSArray *)params {
//NSError *closeError = nil;
__block NSError *errorQuery = nil;
dispatch_sync(self.fetchQueue, ^{
NSError *openError = nil;
//Check if database is open and ready.
if (self.db == nil) {
openError = [self openDatabase];
}
if (openError == nil) {
sqlite3_stmt *statement;
const char *query = [sql UTF8String];
sqlite3_prepare_v2(self.db, query, -1, &statement, NULL);
// BINDING
int count =0;
for (id param in params ) {
count++;
if ([param isKindOfClass:[NSString class]] )
sqlite3_bind_text(statement, count, [param UTF8String], -1, SQLITE_TRANSIENT);
if ([param isKindOfClass:[NSNumber class]] ) {
const char* paramObjType = [param objCType];
if (!strcmp(paramObjType, #encode(float))) // f
sqlite3_bind_double(statement, count, [param doubleValue]);
else if (!strcmp(paramObjType, #encode(double))) // d
sqlite3_bind_double(statement, count, [param doubleValue]);
else if (!strcmp(paramObjType, #encode(int))) // i
sqlite3_bind_int(statement, count, [param intValue]);
else if (!strcmp(paramObjType, #encode(unsigned int))) // I
sqlite3_bind_int(statement, count, [param unsignedIntValue]);
else if (!strcmp(paramObjType, #encode(char))) // c (eg: 1,0)
sqlite3_bind_int(statement, count, [param intValue]);
else if (!strcmp(paramObjType, #encode(long))) // q
sqlite3_bind_int64(statement, count, [param longLongValue]);
else if (!strcmp(paramObjType, #encode(unsigned long))) // Q
sqlite3_bind_int64(statement, count, [param unsignedLongLongValue]);
else
NSLog(#"SQLite binding - unknown NSNumber");
}
}
int result = sqlite3_step(statement);
if (result == SQLITE_ERROR ||
result == SQLITE_CONSTRAINT) {
// const char *errorMsg = sqlite3_errmsg(self.db);
// ...
}
sqlite3_finalize(statement);
}
else
errorQuery = openError;
});
return errorQuery;
}
Hi i have made a function in my app delegate to remove the redundancy of my database , I am not sure do i have coded right as i have to perform nesting of SQL statements to find out the error in DB.
Can any body suggest me where i am wrong because the application is running well in simulator and crashing in Device.
I am even Not Sure where to Put finalize and sqlite3_close . Kindly help
-(void) removeRedundancy2
{
NSArray *docPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
dbPathString = [[docPaths objectAtIndex:0] stringByAppendingPathComponent:#"turfnutritiontool_ver_99.db"];
sqlite3_stmt *selectStmt;
sqlite3_stmt *selectStmt1;
BOOL isMyFileThere = [[NSFileManager defaultManager] fileExistsAtPath:dbPathString];
if (isMyFileThere)
{
if (sqlite3_open([dbPathString UTF8String], &database1)==SQLITE_OK)
{
// TO REMOVE FROM from tnt_scenario_product when NO ProductID Found
NSString *querySql2= [NSString stringWithFormat:#"SELECT productid from tnt_scenarioproduct"];
const char* query_sql2 = [querySql2 UTF8String];
if(sqlite3_prepare_v2(database1, query_sql2, -1, &selectStmt, NULL) == SQLITE_OK)
{
while (sqlite3_step(selectStmt) == SQLITE_ROW)
{
int productid = sqlite3_column_int(selectStmt, 0);
// NSLog(#"ProductId1 =%d",productid);
NSString *querySql21= [NSString stringWithFormat:#"SELECT productid from tnt_productcontent WHERE productid = %d",productid];
const char* query_sql21 = [querySql21 UTF8String];
if(sqlite3_prepare_v2(database1, query_sql21, -1, &selectStmt1, NULL) == SQLITE_OK)
{
if (sqlite3_step(selectStmt1) == SQLITE_ROW)
{
// DO NOTHING
}
else
{ // to delete scenario without product id
NSLog(#"Delete this Product from TPC 2 %d",productid);
NSString *querydelete2= [NSString stringWithFormat:#"DELETE from tnt_scenarioproduct WHERE productid = %d",productid];
const char* query_delete2 = [querydelete2 UTF8String];
char *error;
sqlite3_exec(database1, query_delete2, NULL, NULL, &error);
NSLog(#"error=%s ",error);
sqlite3_finalize(selectStmt1);
}
}
sqlite3_finalize(selectStmt1);
sqlite3_close(database1);
}
sqlite3_finalize(selectStmt);
}
sqlite3_close(database1);
}
sqlite3_close(database1);
}
}
After calling sqlite3_open, you must call sqlite3_close for that connection exactly once.
After calling sqlite3_prepare_v2, you must call sqlite3_finalize for that statement exactly once.
if (sqlite3_open([dbPathString UTF8String], &database1)==SQLITE_OK)
{
...
if(sqlite3_prepare_v2(database1, query_sql21, -1, &selectStmt1, NULL) == SQLITE_OK)
{
...
}
sqlite3_finalize(selectStmt);
....
}
sqlite3_close(database1);
Furthermore, you should not reuse the same variable (selectStmt) for two different queries to prevent confusion about its lifetime.