I have one method which accepts va_list like this:
+(NSUInteger) addObjectToDB:(NSString*)dbFilePath withSQL:(NSString*)sql, ... {
va_list args;
va_start(args, sql);
FMDatabaseQueue* dbQ = [FMDatabaseQueue databaseQueueWithPath:dbFilePath];
__block NSUInteger result = -1;
[dbQ inDatabase:^(FMDatabase *db) {
[db open];
if ([db executeUpdate:sql, args]) {
result = (NSUInteger) [db lastInsertRowId];
}
[db close];
}];
va_end(args);
return result;
}
EDIT1:
I want to pass va_list(args) to the 'executeUpdate:' method of FMDatabase like this:
NSString* sql = [NSString stringWithFormat:#"Insert Into Table_Name Values (NULL, '%#', ?, ?)", #"string"];
[CXDBHelper addObjectToDB:self.dbFilePath withSQL:sql, NSData, NSData];
but the execution stopped in the method (FMDatabase.m) :
- (void)bindObject:(id)obj toColumn:(int)idx inStatement:(sqlite3_stmt*)pStmt {
if ((!obj) || ((NSNull *)obj == [NSNull null])) {
sqlite3_bind_null(pStmt, idx);
}
// FIXME - someday check the return codes on these binds.
else if ([obj isKindOfClass:[NSData class]]) {
at the last line without error message.
EDIT 2:
Thanks to #rmaddy's tips, I check the code executeUpdate:, it is defined as:
- (BOOL)executeUpdate:(NSString*)sql, ...
I realize that the problem is I cannot pass a va_list to it.
EDIT 3:
At last, I use another method which accept va_list: executeUpdate: withVAList: and everything is OK now.
Related
I've added FMDB/SQLCipher (2.7.5) to my app by cocoapods.
use_frameworks!
pod 'FMDB/SQLCipher'
And perform the code bellow:
- (void)decryptDBAtPath:(NSString *)path withKey:(NSString *)key {
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:path];
[queue inTransaction:^(FMDatabase * _Nonnull db, BOOL * _Nonnull rollback) {
BOOL result = [db setKey:key];
NSLog(#"decrypt db %#", result ? #"success" : #"failed");
//it always return YES.
NSString *sql = #"SELECT * FROM PTypeImage";
FMResultSet *rs = [db executeQuery:sql];
NSDictionary *dic = [rs resultDictionary];
NSLog(#"query test with result : %#", dic);
//query can not by executed. 'file is not a database'.
}];
}
It's always return decrypt success, but query failed.
Any wrong in my code?
I also tryed encryption by FMDB. It seems failed. Returns encrypt success, but can be open without key.
- (void)enryptDBAtPath:(NSString *)path withKey:(NSString *)key {
FMDatabase *db = [FMDatabase databaseWithPath:path];
if ([db open]) {
BOOL result = [db rekey:key];
//always return YES.
NSLog(#"encrypt db %#", result ? #"success" : #"failed");
[db close];
}
}
Here is the code which I'm using to insert a record into a sqlite table
FMDatabase *dataBase = [self openDatabase];
[dataBase open];
if ([dataBase open] != YES) {
NSLog(#"DB Error %d: %#", [dataBase lastErrorCode], [dataBase lastErrorMessage]);
//VERY IMPORTANT
}
BOOL success= [dataBase executeUpdate:#"Insert into CrewUnits (pkCrewUnits,fkUnits,fkAgencyVehicles,fkLookupCodes_PrimaryRole, fkLookupCodes_LevelOfCare, PrimaryUnit) values (?, ?, ?, ?, ?, ?);", pkCrewUnits, fkUnits, fkAgencyVehicle, fkLookupCodes_PrimaryRole, fkLookupCodes_LevelOfCare, [NSNumber numberWithInt:1]];
NSLog(success ?#"YES" :#"NO");
NSLog(#"Error %d: %#", [dataBase lastErrorCode], [dataBase lastErrorMessage]);
FMResultSet*resultSet= [dataBase executeQuery:#"select * from CrewUnits"];
NSLog(#"ResultSet : %#", [resultSet resultDictionary]);
[dataBase close];
Database Path
- (FMDatabase *)openDatabase
{
NSLog(#"Open Database");
NSString *documents_dir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *db_path = [documents_dir stringByAppendingPathComponent:[NSString stringWithFormat:#"HPSix_05BD.db"]]; // DatabasePath
FMDatabase *db = [FMDatabase databaseWithPath:db_path];
if (![db open])
NSLog(#"Failed to open database!!!!!");
return db;
}
I'm using the same logic to fetch data from table that works fine for me. But I can't insert a record. I don't know what I'm doing wrong here.
Two problems:
You are calling open three times. Once in openDatabase and twice in the code that called it.
So, have your open routine open the database if it can, but return nil if it can't:
- (FMDatabase *)openDatabase
{
NSLog(#"Open Database");
NSString *documents_dir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *db_path = [documents_dir stringByAppendingPathComponent:[NSString stringWithFormat:#"HPSix_05BD.db"]]; // DatabasePath
FMDatabase *db = [FMDatabase databaseWithPath:db_path];
if (![db open]) {
NSLog(#"Failed to open database!!!!!");
return nil;
}
return db;
}
Then check that result:
FMDatabase *dataBase = [self openDatabase];
if (!dataBase) {
// quit; note, no point in checking error message as it only returns meaningful messages if you have an open database
}
After calling executeQuery, you have to call next.
FMResultSet *resultSet = [dataBase executeQuery:#"select * from CrewUnits"];
if ([resultSet next]) {
NSLog(#"ResultSet : %#", [resultSet resultDictionary]);
} else {
NSLog(#"No data found");
}
I think I see the problem. You are committing after running the SELECT query.
You have to commit right after the UPDATE query. When you do it like you have now when selecting, the UPDATE query has not been committed and thus will not be returned.
[dataBase beginTransaction];
BOOL success= [dataBase executeUpdate:#"Insert into CrewUnits (pkCrewUnits,fkUnits,fkAgencyVehicles,fkLookupCodes_PrimaryRole, fkLookupCodes_LevelOfCare, PrimaryUnit) values (?, ?, ?, ?, ?, ?);", pkCrewUnits, fkUnits, fkAgencyVehicle, fkLookupCodes_PrimaryRole, fkLookupCodes_LevelOfCare, [NSNumber numberWithInt:1]];
[dataBase commit]; // commit the query
NSLog(success ?#"YES" :#"NO");
NSLog(#"Error %d: %#", [dataBase lastErrorCode], [dataBase lastErrorMessage]);
// now at this point the transaction has been committed and can be selected
FMResultSet*resultSet= [dataBase executeQuery:#"select * from CrewUnits"];
NSLog(#"ResultSet : %#", [resultSet resultDictionary]);
Also according to FMDB docs:
The parameters must start with a colon. SQLite itself supports other
characters, but internally the Dictionary keys are prefixed with a
colon, do not include the colon in your dictionary keys.
Thus this line of code must be rewritten to:
BOOL success= [dataBase executeUpdate:#"Insert into CrewUnits (pkCrewUnits,fkUnits,fkAgencyVehicles,fkLookupCodes_PrimaryRole, fkLookupCodes_LevelOfCare, PrimaryUnit) values (:pkCrewUnits,:fkUnits,:fkAgencyVehicles,:fkLookupCodes_PrimaryRole, :fkLookupCodes_LevelOfCare, :PrimaryUnit);", pkCrewUnits, fkUnits, fkAgencyVehicle, fkLookupCodes_PrimaryRole, fkLookupCodes_LevelOfCare, [NSNumber numberWithInt:1]];
Lastly according to the docs you use next for looping through the results:
while ([resultSet next]) {
//retrieve values for each record
}
I am unable to delete the tables from sqlite database using DROP TABLE query. Have tried all possible solutions such as [database openCloseResultSet], [resultSet close] and so on. But adding these lines results in out of memory error and if i dont write these lines then my program just stops at the drop table statement. There is no warning or error given too.Even if i put the code in debug, my debug point just goes away once this line is executed.I am using FMDatabase library while working with process related to sqlite.I am unable to find out the cause of this issue.
Have tried these links too but they are not working in my case.
1)FMDB executeUpdate DROP command does halt the app
2)How to remove all data from table using FMDB
Here is my code where the issue prevails.
+ (NSString *) deleteTable:(NSInteger) index{
NSString *returnFlag = #"success";
FMDatabase *database = nil;
#try {
NSString *query = #"select name from sqlite_master where type = 'table'";
database = [FMDatabase databaseWithPath:[DBOperator getDataBasePath:DATABASENAME]];
if(database != nil && [database open])
{
FMResultSet *resultSet = [database executeQuery:query];
while ([resultSet next]) {
NSString *temp = [resultSet stringForColumnIndex: 0];
if (index == DELETE_TYPE_ONE &&
([temp equalsIgnoreCase: TBUPW] ||
[temp equalsIgnoreCase: TBCVR] ||
[temp equalsIgnoreCase: TBCNTRLNO])) {
[database executeUpdate:[NSString stringWithFormat:#"DROP TABLE %#", temp]];
}else if(index == DELETE_TYPE_TWO && [temp hasPrefix:#"txn"]){
[database executeUpdate:[NSString stringWithFormat:#"DROP TABLE %#", temp]];
}else if(index == DELETE_TYPE_THREE &&
([temp hasPrefix:#"t"] ||
[temp hasPrefix:#"T"] ||
[temp hasPrefix:#"ob"] ||
[temp hasPrefix:#"fb"] ||
[temp hasPrefix:#"cp"])){
NSlog("This is printed in console");
[database executeUpdate:[NSString stringWithFormat:#"DROP TABLE %#", temp]];
NSLog("This is not printed in console");
}else if(index == DELETE_TYPE_FOUR && [temp equalsIgnoreCase:#"TBPLAN"]){
[database executeUpdate:[NSString stringWithFormat:#"DROP TABLE %#", temp]];
}
}
}
}
#catch (NSException *exception) {
returnFlag = #"error";
}
#finally {
[database close];
}
return returnFlag;
}
you can use this query.
DROP TABLE YOUR_TABLE_NAME;
or you can use this
DELETE FROM YOUR_TABLE_NAME;
As for me following code works, maybe it'll give you a hint:
#property (nonatomic, strong, readonly) FMDatabaseQueue *database;
__block BOOL finishedSuccessfully = YES;
[database inTransaction:^(FMDatabase *db, BOOL *rollback) {
// firstly let's remove the table:
[db closeOpenResultSets];
FMResultSet *deleteResultSet = [db executeQuery:#"DROP TABLE myTableName"];
if ([deleteResultSet next]) {}
[deleteResultSet close];
finishedSuccessfully = !db.lastErrorCode && finishedSuccessfully; // breakpoint after this line
// then let's perform anything on the db:
FMResultSet *select = [db executeQuery:#"SELECT row FROM myTableName WHERE anotherRow = ?", #{2}];
finishedSuccessfully = !db.lastErrorCode && finishedSuccessfully; // breakpoint after this line
}
I don't have an error after the first part, but second part returns something like "there is no such table as myTableName".
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 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;
});