FMDatabase iOS INSERT issues - ios

I'm making an application with database. For modal I use FMDatabase classes. Now I have to insert some new data in two tables:
-project(id_proj primary key autoince unique, name, baseImage)
-imagesforpro(id_img primkey autoincr unique, smallimage, largeimage, id_proj)
I have a method -(void)addProject:(Project*)proj for which I pass a Project object.
Then I have to insert projectName and baseImage into project table and then extract the id_proj of the record I've just inserted and insert small and larges images into imagesforpro table.How to remeber the id_proj(which is autoincrement) for the last inserted record? Moreover, how to use INSERT? DO I use it properly? how to put NSData into the database properly? thanks. Here is what I have so far:
-(void)addProject:(Project*)proj
{
[db open];
NSData *bmdata=UIImagePNGRepresentation(proj.baseImage);
if (proj==nil) {
NSLog(#"Dead Object");
}
NSLog(#" NEW name is %#", proj.projectName);
//DOESN'T WORK
//id_proj is autoincrement field that's why I just pass name and base image
[db executeUpdate:#"INSERT INTO project VALUES (?,?)", [proj.projectName UTF8String], bmdata];
//sqlite3_last_insert_rowid()
[db close];
sqlite3 *dataB;
int status=sqlite3_open([[self dbPath] UTF8String], &dataB);
if(status!=SQLITE_OK)
{
NSLog(#"Error");
//exit(1);
}
//RETURNS 0(
int lastindex=sqlite3_last_insert_rowid(dataB);
NSLog(#"Last inserted index %i", lastindex);
sqlite3_close(dataB);
[db open];
//Add images for the project (another table)
for(int i=0; i<[proj.largeImages count]; i++)
{
UIImage *smallImg=[proj.smallImages objectAtIndex:i];
UIImage *bigImg=[proj.largeImages objectAtIndex:i];
NSData *sidata=UIImagePNGRepresentation(smallImg);
NSData *bidata=UIImagePNGRepresentation(bigImg);
// Here I have to insert an image and id of the project it refers too
//How do I find out the last inserted id_proj?
[db executeUpdate:#"INSERT INTO imagesforpro VALUES (?,?,?)", sidata, bidata, lastindex];
}
[db close];
}

Related

Insert Query not working (FMDB)

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
}

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

how to use loop in sqlite for get Name from certain column

I to use sqlite database in my application. in my sqlite exist 10 records. I want read 4 data from this database and I want get this data until BroID in last data to be NULL (BroID is one of columns data) this is my code but I dont know how to use of loop in my code until BroID to be NULL.
-(NSMutableArray *)readInformationFromDatabase
{
array = [[NSMutableArray alloc] init];
// Setup the database object
sqlite3 *database;
// Open the database from the users filessytem
if(sqlite3_open([[self dataFilePath] UTF8String], &database) == SQLITE_OK)
{
// I want to use loop for certain work!!! (this work is get name from data base until BroID to be NULL )
NSString *sqlStatement_userInfo =[NSString stringWithFormat:#"Select * from table1"];
sqlite3_stmt *compiledStatement;
if(sqlite3_prepare_v2(database, [sqlStatement_userInfo UTF8String], -1, &compiledStatement, NULL) == SQLITE_OK)
{
// Loop through the results and add them to the feeds array
while(sqlite3_step(compiledStatement) == SQLITE_ROW)
{
// Init the Data Dictionary
NSMutableDictionary *_dataDictionary=[[NSMutableDictionary alloc] init];
NSString *_userName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 1)];
[_dataDictionary setObject:[NSString stringWithFormat:#"%#",_userName] forKey:#"Name"];
[array addObject:_dataDictionary];
}
}
else
{
NSLog(#"No Data Found");
}
please get me idea for done this code.
OK first the SELECT statement:
Change the statement from SELECT * ... to SELECT colname1, colname2, ... so you are then certain of the order in which columns are returned, and you won't have to refer to the schema in order to find out what order they come back in. This actually saves time.
BroID must be included in the columns being selected.
You'll want an ORDER BY clause in order to get consistent results.
You can probably get the database to only include rows WHERE BroID IS NOT NULL, which might suite your needs.
If you still need to use code to stop the fetching, then simply test for a NULL BroID column and break out of the while loop using:
while (sqlite3_step(compiledStatement) == SQLITE_ROW)
{
// Fetch other columns
if (sqlite_column_type(compiledStatement, INDEX) == SQLITE_NULL)
break;
}
Where INDEX is the column index of BroID.
It's not clear if you want the row where BroID IS NULL in the result set; if you don't then perform the sqlite_column_type() test before fetching the columns, else leave it as above.
Refer to the reference for details.

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