Insert Query not working (FMDB) - ios

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
}

Related

Cocoapods FMDB/SQLCipher 2.7.5 can not decrypt

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];
}
}

Load large number of data from sqlite db

I have more than 10000+ record in database. I want to load it on a table view. It will take 3 to 4 seconds to fetch all data from database and than load it on table view. Is there is more efficient way to increase the response and load the data in a uitableview?
Here is my code for get All Data fro database
- (void)getAllData {
NSString * convertInttoStr = [NSString stringWithFormat:#"%d", rowNumber];
// Getting the database path.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSString *dbPath = [docsPath stringByAppendingPathComponent:#"iandroidquran_database 3.sqlite"];
FMDatabase *database = [FMDatabase databaseWithPath:dbPath];
[database open];
NSString *sqlSelectQuery = [NSString stringWithFormat:
#"SELECT * FROM qSurahContent WHERE surahID=%#" ,
convertInttoStr];
// Query result
FMResultSet *resultsWithNameLocation = [database executeQuery:sqlSelectQuery];
while([resultsWithNameLocation next]) {
NSString *strID = [NSString stringWithFormat:#"%d",[resultsWithNameLocation intForColumn:#"surahID"]];
NSString *strName = [NSString stringWithFormat:#"%#",[resultsWithNameLocation stringForColumn:#"surahContentArabic"]];
NSLog(#"surahID = %#, surahName = %#",strID, strName);
[surahId addObject:strID];
[surahContentArabic addObject:strName];
}
[self.tblView reloadData];
[database close];
}
Any solution?? Thanks
Try to fetch Data from DB in main Thread as following :
dispatch_async(dispatch_get_main_queue(), ^{
[self getAllData];
});
This main thread has high priority and will execute faster than ever. So try this one.
Hope it helps..

Warning: I could not find the column named 'rowid'

I use FMDB in my iOS project. But when I read ROWID with FMDB,the Xcode log "Warning: I could not find the column named 'rowid'."
...
//Create database
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *dbPath = [documentsPath stringByAppendingPathComponent:#"bookmarksDatabase.sqlite"];
BOOL needCreateTable = ![[NSFileManager defaultManager] fileExistsAtPath:dbPath];
FMDatabase *db = [FMDatabase databaseWithPath:dbPath];
[db open];
if (needCreateTable) {
[db executeUpdate:#"CREATE TABLE bookmarks (title TEXT ,url TEXT ,folderID INTEGER ,locationIndex INTEGER)"];
}
else
{
[self reloadBookmarkDatabase:db];
}
[db close];
...
//Read database. Only "ROWID" column can't find.
FMResultSet *results = [db executeQuery:#"SELECT * FROM bookmarks ORDER BY locationIndex"];
while([results next]) {
BookmarkData *temp = [[BookmarkData alloc] initWithID:[results intForColumn:#"ROWID"] title:[results stringForColumn:#"title"] url:[results stringForColumn:#"url"] folderID:[results intForColumn:#"folderID"] locationIndex:[results intForColumn:#"locationIndex"]];
[self.bookmarkArray addObject:temp];
NSLog(#"bookmark id:%d, title:%#, url:%#, folderID:%d, locationIndex:%d",temp.ID,temp.title,temp.url,temp.folderID,temp.locationIndex);
}
SELECT * returns only the columns from the table definition.
If you want to get the rowid column, you have to list it explicitly:
SELECT *, rowid FROM ...
And if you actually need to use this column, it would be a better idea to make it part of the table definition:
CREATE TABLE bookmarks (
ID INTEGER PRIMARY KEY, -- same as rowid
title TEXT,
...
)

Deleting Table from sqlite database

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".

fmdb path and using db 2nd time from another viewController

I am using the code below to create db in one class and using it in the viewController and same code again in another class in newViewController .
Is it correct ? Or does it create new db in its place or uses if there is an existing db ?
or Do I need to use only the path ?
When I tried extern NSString *path in the example below I received Chinese letters and could not open it.
Basically in the 2nd contoller I am only reading the data.
So I am trying to access db via path,database with path and database open.
How do it if I have used the code below to create it and I would like to open it 2nd time from another viewcontoller.
Please help.
Thanks in Advance.
In 1st Contoller:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSString *path = [docsPath stringByAppendingPathComponent:#"test.db"];
FMDatabase *database;
database = [FMDatabase databaseWithPath:path];
[database open];
[database executeUpdate:#"DROP TABLE IF EXISTS **"];
[database executeUpdate:#"CREATE TABLE "];
//Select query for single row
FMResultSet *s = [database executeQuery:#"SELECT COUNT(*) FROM table"];
if ([s next]) {
int Count = [s intForColumnIndex:0];
}
//DO Something
[database close];
Above code is working perfectly in viewcontroller 1 .
Things I tried in view controller 2 .And it still did not work.
extern NSString *path;
FMDatabase *database;
database = [FMDatabase databaseWithPath:path];
[database open];
I get an error here since the path is in What looks like Chinese language.
Error is EXC_BAD_ACCESS code .
Also tried global database by :
extern FMDatabase *database;
and using it as
[database open];
I still get the same error.
Finally it worked with the following code in the 2nd controller.
I just wanted to see if thats the right way to do it or can I use something as db open .
But I tried that and its not working.Also I am not using any error handler as I dont know how to do it in FMDB .
Thanks in Advance.
Please let me know if the following implementation is correct ? :
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSString *newpath = [docsPath stringByAppendingPathComponent:#"test.db"];
FMDatabase *database2 ;
database2 = [FMDatabase databaseWithPath:newpath];
[database2 open];
//DO Something
[database2 close];
As an example of how you should be checking for the success of the FMDB methods, here is an example:
#import "FMDatabase.h"
#import "FMDatabaseAdditions.h"
- (void)performDatabaseDemonstration
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSString *path = [docsPath stringByAppendingPathComponent:#"test.db"];
// open db
FMDatabase *database = [FMDatabase databaseWithPath:path];
FMResultSet *rs;
if ([database open])
{
// drop table
if (![database executeUpdate:#"DROP TABLE IF EXISTS test"])
NSLog(#"%s: drop error: %#", __FUNCTION__, [database lastErrorMessage]);
// create table
if (![database executeUpdate:#"CREATE TABLE test(col1 INTEGER, col2 TEXT)"])
NSLog(#"%s: create error: %#", __FUNCTION__, [database lastErrorMessage]);
// insert a row of data
if (![database executeUpdate:#"INSERT INTO test VALUES(1, 'Rob')"])
NSLog(#"%s: insert 1 error: %#", __FUNCTION__, [database lastErrorMessage]);
// insert another row of data
if (![database executeUpdate:#"INSERT INTO test VALUES(2, 'Rachel')"])
NSLog(#"%s: insert 2 error: %#", __FUNCTION__, [database lastErrorMessage]);
// Select query for single row
if (!(rs = [database executeQuery:#"SELECT count(*) FROM test"]))
NSLog(#"%s: select error: %#", __FUNCTION__, [database lastErrorMessage]);
if ([rs next]) {
int count = [rs intForColumnIndex:0];
NSLog(#"%s: count = %d", __FUNCTION__, count);
}
[rs close];
// if getting single value from single row, you can use FMDatabaseAdditions.h methods:
NSInteger count2 = [database intForQuery:#"SELECT col1, col2 FROM test"];
NSLog(#"%s: count2 = %d", __FUNCTION__, count2);
// let's actually retrieve data
if (!(rs = [database executeQuery:#"SELECT col1, col2 FROM test"]))
NSLog(#"%s: select error: %#", __FUNCTION__, [database lastErrorMessage]);
while ([rs next]) {
NSLog(#"%s: col1 = %#; col2 = %#", __FUNCTION__, [rs objectForColumnIndex:0], [rs objectForColumnIndex:1]);
}
[rs close];
//close
[database close];
}
else
{
NSLog(#"%s: open error: %#", __FUNCTION__, [database lastErrorMessage]);
}
}
In terms of how to open a database from different controllers there are a ton of options. But I might suggest that above and beyond just retrieving some shared path/filename variable from two controllers, you might want to actually encapsulate not only the filename, but shared FMDB code, too, in an object.
Personally, whenever I find myself writing the same or similar code in two different controllers, I ask myself whether I want to abstract that code out into its own class. In this case, for example, rather than debating how these two classes access the database filename, I would create my own own model object (a NSObject subclass), that encapsulates your SQL, does the opening of the database, etc. Then I could have my two view controllers invoke the methods from that method object, and really minimize not only redundancy in the filenames, but in the code itself, too. For example, the creating of the database path, opening the database, checking the success of the open, etc., are identical. So why not have one method in some model object, that does all of that for you.
To make this model object accessible from both controllers, you have a bunch of options:
Make it a property of your first view controller, and have it pass it to your second controller during prepareForSegue;
Create a singleton object for it; or
Make that a property of your app delegate, and both of your controllers can get it from there.
You should put the database path defined as global variables, the first time to use the closing operation, the second use of time to open, open the database, it will automatically judge whether there are database, if there is no will automatically create.If present, it will directly open the. Used you to judge whether the database open success:
if (! [db open]) {
/ / error
return;
}
/ / some operation
/ /...
[db close];

Resources