How to insert UIImage as blob using sqlite3_exec in objective-c - objective-c

I'm trying to cache some images in sqlite as nsdata and I'm having an issue when I attempt to insert the byte array using sqlite3_exec and a raw SQL string (as NSString)
NSData *imgData = UIImagePNGRepresentation(img);
NSString* sql = [NSString stringWithFormat:#"INSERT INTO persistedimg (imgx,idvalx) VALUES (%#,'%#')", imgData, idValue];
rc = sqlite3_exec(db, [sql UTF8String], callbackFunction, (void*)contextObject, &zErrMsg);
But the problem with the above is I'm adding NSData to the sql string directly instead of the bytes.
I wanted to do something like this
... [imgData bytes], [imgData length]
But because I'm not using the typical "_bind_blob" like approach I'm not sure how to do it w/ a raw string
Update
I'm using a wrapper that I'd like to stick w/ and simply write a new method to support image insert / query commands
the below is my entire wrapper class so far
**
#import "SQLiteAccess.h"
#import <sqlite3.h>
#implementation SQLiteAccess
+ (NSString *)pathToDB {
NSString *dbName = #"test123";
NSString *originalDBPath = [[NSBundle mainBundle] pathForResource:dbName ofType:#"db"];
NSString *path = nil;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *appSupportDir = [paths objectAtIndex:0];
NSString *dbNameDir = [NSString stringWithFormat:#"%#/test123", appSupportDir];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isDir = NO;
BOOL dirExists = [fileManager fileExistsAtPath:dbNameDir isDirectory:&isDir];
NSString *dbPath = [NSString stringWithFormat:#"%#/%#.db", dbNameDir, dbName];
if(dirExists && isDir) {
BOOL dbExists = [fileManager fileExistsAtPath:dbPath];
if(!dbExists) {
NSError *error = nil;
BOOL success = [fileManager copyItemAtPath:originalDBPath toPath:dbPath error:&error];
if(!success) {
NSLog(#"error = %#", error);
} else {
path = dbPath;
}
} else {
path = dbPath;
}
} else if(!dirExists) {
NSError *error = nil;
BOOL success =[fileManager createDirectoryAtPath:dbNameDir attributes:nil];
if(!success) {
NSLog(#"failed to create dir");
}
success = [fileManager copyItemAtPath:originalDBPath toPath:dbPath error:&error];
if(!success) {
NSLog(#"error = %#", error);
} else {
path = dbPath;
}
}
return path;
}
+ (NSNumber *)executeSQL:(NSString *)sql withCallback:(void *)callbackFunction context:(id)contextObject {
NSString *path = [self pathToDB];
sqlite3 *db = NULL;
int rc = SQLITE_OK;
NSInteger lastRowId = 0;
rc = sqlite3_open([path UTF8String], &db);
if(SQLITE_OK != rc) {
NSLog(#"Can't open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return nil;
} else {
char *zErrMsg = NULL;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
rc = sqlite3_exec(db, [sql UTF8String], callbackFunction, (void*)contextObject, &zErrMsg);
if(SQLITE_OK != rc) {
NSLog(#"Can't run query '%#' error message: %s\n", sql, sqlite3_errmsg(db));
sqlite3_free(zErrMsg);
}
lastRowId = sqlite3_last_insert_rowid(db);
sqlite3_close(db);
[pool release];
}
NSNumber *lastInsertRowId = nil;
if(0 != lastRowId) {
lastInsertRowId = [NSNumber numberWithInteger:lastRowId];
}
return lastInsertRowId;
}
static int singleRowCallback(void *queryValuesVP, int columnCount, char **values, char **columnNames) {
NSMutableDictionary *queryValues = (NSMutableDictionary *)queryValuesVP;
int i;
for(i=0; i<columnCount; i++) {
[queryValues setObject:values[i] ? [NSString stringWithFormat:#"%s",values[i]] : [NSNull null]
forKey:[NSString stringWithFormat:#"%s", columnNames[i]]];
}
return 0;
}
+ (NSString *)selectOneValueSQL:(NSString *)sql {
NSMutableDictionary *queryValues = [NSMutableDictionary dictionary];
[self executeSQL:sql withCallback:singleRowCallback context:queryValues];
NSString *value = nil;
if([queryValues count] == 1) {
value = [[queryValues objectEnumerator] nextObject];
}
return value;
}
+ (NSNumber *)insertWithSQL:(NSString *)sql {
sql = [NSString stringWithFormat:#"BEGIN TRANSACTION; %#; COMMIT TRANSACTION;", sql];
return [self executeSQL:sql withCallback:NULL context:NULL];
}
+ (void)updateWithSQL:(NSString *)sql {
sql = [NSString stringWithFormat:#"BEGIN TRANSACTION; %#; COMMIT TRANSACTION;", sql];
[self executeSQL:sql withCallback:NULL context:nil];
}
#end
**
Any help with this solution would be huge!

I think a large part of the issue you are running into here is that you are trying to simplify the SQLite3 APIs too much. The APIs are not just for executing textual SQL queries; prepared statements and bind parameters exist for a reason. You shouldn't be trying to insert binary data in a string. That's just asking for problems, especially if your binary data has nulls in it.
To insert blobs, you really do need to use sqlite3_bind_blob with sqlite3_prepare_v2. When you bind the blob, you will need to also use [imgData bytes] as the blob data.
Are you perhaps looking for help reconstructing your API to make this sort of thing easier for this particular image caching use case?
Edit
Here's a simple example using bind to insert binary data. Assume there is a table called my_table with 2 columns: name of type VARCHAR and data of type BLOB. Please note that I have not tested or even tried compiling this, so there may be typos or errors.
sqlite3 *database;
// Open a connection to the database given its file path.
if (sqlite3_open("/path/to/sqlite/database.sqlite3", &database) != SQLITE_OK) {
// error handling...
}
// Construct the query and empty prepared statement.
const char *sql = "INSERT INTO `my_table` (`name`, `data`) VALUES (?, ?)";
sqlite3_stmt *statement;
// Prepare the data to bind.
NSData *imageData = UIImagePNGRepresentation([UIImage imageNamed:#"something"]);
NSString *nameParam = #"Some name";
// Prepare the statement.
if (sqlite3_prepare_v2(database, sql, -1, &statement, NULL) == SQLITE_OK) {
// Bind the parameters (note that these use a 1-based index, not 0).
sqlite3_bind_text(statement, 1, nameParam);
sqlite3_bind_blob(statement, 2, [imageData bytes], [imageData length], SQLITE_STATIC);
// SQLITE_STATIC tells SQLite that it doesn't have to worry about freeing the binary data.
}
// Execute the statement.
if (sqlite3_step(statement) != SQLITE_DONE) {
// error handling...
}
// Clean up and delete the resources used by the prepared statement.
sqlite3_finalize(statement);
// Now let's try to query! Just select the data column.
const char *selectSql = "SELECT `data` FROM `my_table` WHERE `name` = ?";
sqlite3_stmt *selectStatement;
if (sqlite3_prepare_v2(database, selectSql, -1, &selectStatement, NULL) == SQLITE_OK) {
// Bind the name parameter.
sqlite3_bind_text(selectStatement, 1, nameParam);
}
// Execute the statement and iterate over all the resulting rows.
while (sqlite3_step(selectStatement) == SQLITE_ROW) {
// We got a row back. Let's extract that BLOB.
// Notice the columns have 0-based indices here.
const void *blobBytes = sqlite3_column_blob(selectStatement, 0);
int blobBytesLength = sqlite3_column_bytes(selectStatement, 0); // Count the number of bytes in the BLOB.
NSData *blobData = [NSData dataWithBytes:blobBytes length:blobBytesLength];
NSLog("Here's that data!\n%#", blobData);
}
// Clean up the select statement
sqlite3_finalize(selectStatement);
// Close the connection to the database.
sqlite3_close(database);

Related

Sqlite3 step == SQLITE_ROW not working for me

As i am new to Xcode so no idea to fix this issue. Please help me out to figure the actual issue.
Database connection is build up, the query is execute, but I always get the message as : Result not found, after execution the query. :(
- (void)viewDidLoad
{
NSString *MyDirectory;
NSArray *DirectoryPaths;
// Get the documents directory
DirectoryPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
MyDirectory = [DirectoryPaths objectAtIndex:0];
// Build the path to the database file
MyDBPath = [[NSString alloc]initWithString: [MyDirectory stringByAppendingPathComponent:#"mydb.db"]];
//Status.text = MyDBPath;
const char *Database = [MyDBPath UTF8String];
if (sqlite3_open(Database, &MyConnection) == SQLITE_OK)
{
Status.text = #"Build Connection Successfully...!";
}
else
{
Status.text = #"Failed to open database...!";
}
[super viewDidLoad];
}
- (void) Find
{
const char *Database = [MyDBPath UTF8String];
sqlite3_stmt *SQLStatement;
NSString *Query;
if (sqlite3_open(Database, &MyConnection) == SQLITE_OK)
{
Query = [NSString stringWithFormat:#"SELECT EmpName FROM EmpLogin WHERE EmpID=\"%#\"",MyText.text];
if (sqlite3_prepare_v2(MyConnection, [Query UTF8String], 0, &SQLStatement, nil) == SQLITE_OK)
{
while (sqlite3_step(SQLStatement) == SQLITE_ROW)
{
NSString *Name = [[NSString alloc]
initWithUTF8String:(const char *) sqlite3_column_text(SQLStatement, 0)];
TextResult.text = Name;
}
if (TextResult.text.length > 0)
{
Status.text = #"Result found";
}
else
{
NSLog(#"%s", sqlite3_errmsg(MyConnection));
Status.text = #"Result not found";
TextResult.text = #"";
}
sqlite3_finalize(SQLStatement);
}
else
{
//NSString *decode = [[NSString alloc]initWithCString:Query_Stmt encoding:NSUTF8StringEncoding];
//Status.text = decode;
Status.text = #"Query execution Failed...!";
}
sqlite3_close(MyConnection);
}
}
Sqlite3_step(SQLStatement) == SQLITE_ROW __ nor working
Yes i resolved the issue…!!!!
actually i just change the path of the directory to direct to my DB path.
here is my final and working code… anyway thanks #HotLicks,,, #CL. :) :)
This works like a charm … :)
- (void) Find
{
MyDBPath = #"/Users/zaibi/Documents/IOSProjects/mydb.db";
MyDatabase = [MyDBPath UTF8String];
sqlite3_stmt *SQLStatement;
NSString *Query;
//NSString *decode = [[NSString alloc]initWithCString:Database encoding:NSUTF8StringEncoding];
//NSLog(#"%#",decode);
if (sqlite3_open(MyDatabase, &MyConnection) == SQLITE_OK)
{
Query = [NSString stringWithFormat:#"SELECT EmpName FROM EmpLogin WHERE EmpID=\"%#\"",MyText.text];
if (sqlite3_prepare_v2(MyConnection, [Query UTF8String], -1, &SQLStatement, nil) == SQLITE_OK)
{
if (sqlite3_step(SQLStatement) == SQLITE_ROW)
{
NSString *Name = [[NSString alloc]
initWithUTF8String:(const char *) sqlite3_column_text(SQLStatement, 0)];
TextResult.text = Name;
Status.text = #"Result found";
}
else
{
NSLog(#"%s", sqlite3_errmsg(MyConnection));
Status.text = #"Result not found";
TextResult.text = #"";
}
sqlite3_finalize(SQLStatement);
}
else
{
NSLog(#"%s", sqlite3_errmsg(MyConnection));
Status.text = #"Query execution Failed...!";
}
sqlite3_close(MyConnection);
}
}
while this way of accessing the DB is just confusing… :/
NSString *MyDirectory;
NSArray *DirectoryPaths;
// Get the documents directory
DirectoryPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
MyDirectory = [DirectoryPaths objectAtIndex:0];
// Build the path to the database file
MyDBPath = [[NSString alloc]initWithString: [MyDirectory stringByAppendingPathComponent:#"mydb.db"]];
//Status.text = MyDBPath;
Sometimes it helps to read the documentation:
If the nByte argument is less than zero, then zSql is read up to the
first zero terminator. If nByte is non-negative, then it is the
maximum number of bytes read from zSql.

sqlite_prepare_v2 does not return SQLITE_OK

I have been trying to save highscore into database and have been failing for past week, and I have no clue why it is not working. I keep receiving "Problem with prepare statement" and refuses to insert info into database. I have checked with database manager to make sure there is not a typo with sql statement, and when query is run on manager, it works fine - it's just the iphone that's giving me the problem. If anyone could please look over quickly and see something wrong with it and could let me know, I would really appreciate it!
- (NSMutableArray *) saveLocal {
NSLog(#"save local database");
#try {
[self checkDB];
sqlite3_stmt *sqlStatement2;
NSString *sqlS = [NSString stringWithFormat:#"INSERT INTO localHighscore (difficulty, score, uname, puzzles, multiplier, oneshots, hints) VALUES (%i,%i,\"%#\",%i,%i,%i,%i)",[[MySingleton sharedMySingleton] goDifficulty],[[MySingleton sharedMySingleton] goScore],_player, [[MySingleton sharedMySingleton] goPuzzles], [[MySingleton sharedMySingleton] goMultiplier], [[MySingleton sharedMySingleton] goOneshots], [[MySingleton sharedMySingleton] goHints]];
NSLog(#"%#",sqlS);
const char *sql = [sqlS UTF8String];
if(sqlite3_prepare_v2(localHighscore, sql, -1, &sqlStatement2, NULL) == SQLITE_OK)
{
sqlite3_step(sqlStatement2);
sqlite3_reset(sqlStatement2);
sqlite3_finalize(sqlStatement2);
NSLog(#"save complete");
} else {
NSLog(#"Problem with prepare statement");
}
sqlite3_close(localHighscore);
}#catch (NSException *exception) {
NSLog(#"An exception occured: %#", [exception reason]);
}#finally{
NSLog(#"DB Loaded!");
}
}
and here is checkDB method which checks if database exists and creates one if it does not
- (void)checkDB {
NSString *docsDir;
NSArray *dirPaths;
// Get the documents directory
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = [dirPaths objectAtIndex:0];
// Build the path to the database file
databasePath = [[NSString alloc] initWithString: [docsDir stringByAppendingPathComponent: #"localHighscore.sqlite"]];
NSFileManager *filemgr = [NSFileManager defaultManager];
if ([filemgr fileExistsAtPath: databasePath ] == NO)
{
const char *dbpath = [databasePath UTF8String];
NSLog(#"file was not found");
if (sqlite3_open(dbpath, &localHighscore) == SQLITE_OK)
{
NSLog(#"db open");
char *errMsg;
const char *sql_stmt = "CREATE TABLE IF NOT EXISTS localHighscore(pk INTEGER PRIMARY KEY AUTOINCREMENT, difficulty TINYINT, score MEDIUMINT, uname VARCHAR(255), puzzles TINYINT, multiplier TINYINT, oneshots TINYINT, hints TINYINT)";
if (sqlite3_exec(localHighscore, sql_stmt, NULL, NULL, &errMsg) != SQLITE_OK)
{
NSLog(#"Failed to create table");
}
sqlite3_close(localHighscore);
} else {
NSLog(#"Failed to open/create database");
}
}
[filemgr release];
}
Thanks in advance for the help!
A couple of thoughts:
You don't appear to call sqlite3_open before trying to use the database.
Whenever you get an error, you should look at sqlite3_errmsg, e.g.
if (sqlite3_exec(localHighscore, sql_stmt, NULL, NULL, &errMsg) != SQLITE_OK)
{
NSLog(#"Failed to create table: %s", sqlite3_errmsg(localHighscore));
}
Probably unrelated to your problem, but you should generally not build a SQL statement using stringWithFormat (at least if you have any text fields). Use ? placeholders in your SQL and then use sqlite3_bind_xxx functions.
const char *sql = "INSERT INTO localHighscore (difficulty, score, uname, puzzles, multiplier, oneshots, hints) VALUES (?,?,?,?,?,?,?)";
if(sqlite3_prepare_v2(localHighscore, sql, -1, &sqlStatement2, NULL) == SQLITE_OK)
{
if (sqlite3_bind_int(sqlStatement2, 1, [[MySingleton sharedMySingleton] goDifficulty]) != SQLITE_OK) {
NSLog(#"bind 1 failed: %s", sqlite3_errmsg(localHighscore));
}
if (sqlite3_bind_int(sqlStatement2, 2, [[MySingleton sharedMySingleton] goScore]) != SQLITE_OK) {
NSLog(#"bind 2 failed: %s", sqlite3_errmsg(localHighscore));
}
if (sqlite3_bind_text(sqlStatement2, 3, [_player UTF8String], -1, NULL) != SQLITE_OK) {
NSLog(#"bind 3 failed: %s", sqlite3_errmsg(localHighscore));
}
// repeat this bind process for each variable
if (sqlite3_step(sqlStatement2) != SQLITE_DONE) {
NSLog(#"step failed: %s", sqlite3_errmsg(localHighscore));
}
// reset not needed (doesn't hurt, but not needed unless you're going to re-use it
// sqlite3_reset(sqlStatement2);
sqlite3_finalize(sqlStatement2);
NSLog(#"save complete");
} else {
NSLog(#"Problem with prepare statement: %s", sqlite3_errmsg(localHighscore));
}
sqlite3_close(localHighscore);
If you find this syntax unwieldy, then maybe consider using FMDB, which simplifies your SQL interaction. But be very wary of stringWithFormat with SQL (if the inserted string had a quotation mark, the sqlite3_prepare will fail, theoretically, your app is exposed to SQL injection attacks, etc.).
As an aside, you should not [filemgr release], as you don't own it.
I saw that the "sqlite3_prepare_v2 ()" function, returns a 'generic error' (error code = 1) when the SQL statement contains conditions like "booleanfield=false" instead of "booleanfield=0". The same SQL statement executed in the SQL box of SQLiteStudio program gives good results using indifferently the first or the second form of the comparison.

IOS sqlite update query issues

I am very new to IOS programming so I am not quite sure how this issue can be best solved.
I have a database of events that are being displayed on a tableview, when you select one of those items, it shows details along with the option to save it as a "favorite" which just updates a column in the db and sets it to 1.
I have a second table view that looks for all instances that have their "favorite" set to 1.
The problem I have run into is that I have only figured out how to get the "updateItem" database query to function off of the path of the item that you selected. Since each tableview shows the same item on different paths, the "updateItem" query is updating the incorrect item in the database when you are using the "favorites" table view.
I understand that in the UpdateItemAtID method, it is using the aryDatabase, and the favorites query uses the aryDatabaseFav. I had to create the second array the get around the favorites tableview, maybe there is a better way to have everything I want with just the original aryDatabase array rather than having 2 different arrays.
Here is the code I have for the two select statements as well as the update query, is there a better way that I can get around needing the path of the item?
-(void)readItems {
if (!database) return; // earlier problems
if (!selStmt)
{
const char *sql = "SELECT * FROM Events;";
if (sqlite3_prepare_v2(database, sql, -1, &selStmt, NULL) != SQLITE_OK)
{
selStmt = nil;
}
}
if (!selStmt)
{
NSAssert1(0, #"Can't build SQL to read items [%s]", sqlite3_errmsg(database));
}
// loop reading items from list
[aryDatabase removeAllObjects]; // clear list for rebuild
int ret;
while ((ret=sqlite3_step(selStmt))==SQLITE_ROW)
{ // get the fields from the record set and assign to item
//bindings omitted
[aryDatabase addObject:se]; // add to list
[se release]; // free item
}
sqlite3_reset(selStmt); // reset (unbind) statement
selStmt = nil;}
-(void)readFavItems {
if (!database) return; // earlier problems
if (!selStmt)
{
const char *sql = "SELECT * FROM Events WHERE mySchedule = 1;";
if (sqlite3_prepare_v2(database, sql, -1, &selStmt, NULL) != SQLITE_OK)
{
selStmt = nil;
}
}
if (!selStmt)
{
NSAssert1(0, #"Can't build SQL to read items [%s]", sqlite3_errmsg(database));
}
// loop reading items from list
[aryDatabaseFav removeAllObjects]; // clear list for rebuild
int ret;
while ((ret=sqlite3_step(selStmt))==SQLITE_ROW)
{ // get the fields from the record set and assign to item
//bindings omitted
[aryDatabaseFav addObject:se]; // add to list
[se release]; // free item
}
sqlite3_reset(selStmt); // reset (unbind) statement
selStmt = nil;}
-(void)updateItemAtID:(NSIndexPath *)path {
singleEvent *i = (singleEvent *)[aryDatabase objectAtIndex:path.row];
int ret;
const char *sql = "UPDATE events SET mySchedule = ? WHERE _id = ?;";
if (!updStmt)
{ // build update statement
if ((ret=sqlite3_prepare_v2(database, sql, -1, &updStmt, NULL))!=SQLITE_OK)
{
NSAssert1(0, #"Error building statement to update items [%s]", sqlite3_errmsg(database));
}
}
// bind values to statement
NSInteger m = i.intId;
sqlite3_bind_int(updStmt, 1, m);
NSInteger p = i.intPk;
sqlite3_bind_int(updStmt, 2, p);
if ((ret=sqlite3_step(updStmt)) != SQLITE_DONE)
{
NSAssert1(0, #"Error updating values [%s]", sqlite3_errmsg(database));
}
sqlite3_reset(updStmt);
}
I've updated the code and getting a result=0 on sqlite3_bind_text. Does mean it was updated with no error or no record was updated at all? Not sure what else to do at this point...
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"bh.sqlite"];
success = [fileManager fileExistsAtPath:writableDBPath];
NSLog(writableDBPath);
if(success)
{
const char *dbPath= [writableDBPath UTF8String];
//[G msgBox:writableDBPath title:#""];
//const char *dbpath = [databasePath UTF8String];
sqlite3_stmt *statement;
sqlite3 *contactDB = NULL;
if (sqlite3_open(dbPath, &contactDB) == SQLITE_OK)
{
NSString *updateSQL = #"UPDATE account SET name = ? ";
const char *update_stmt = [updateSQL UTF8String];
int e = sqlite3_prepare_v2(contactDB, update_stmt, -1, &statement, nil);
if(e != SQLITE_OK) {
NSLog(#"Problem with updateEntryWithUniqueID");
NSLog(#"Error Code: %d, message '%s'", e, sqlite3_errmsg(contactDB));
return;
}else{
NSLog(#"Updated");
[G setNickname:thisNickname];
}
int result=sqlite3_bind_text(statement, 1, [thisNickname UTF8String], -1, SQLITE_TRANSIENT);
NSLog(#"bind result= %i", result);
if(sqlite3_step(statement) != SQLITE_DONE) {
NSLog(#"Problems updating");
}
/* Finished */
sqlite3_finalize(statement);
}else
{
NSLog(#"Can not open DB.");
}
}else{
NSLog(#"path not exsist"); }
Base of my understanding, it seems like the way the NSArray is structured when they load the data could be different since you are using NSIndexPath.
I do recommend the following approach, using a Singleton Object.
Singleton Object works like a container that is shared among a fews view controller(s) or classes.
Back to your issue, you can store the object (singleEvent) in the Singleton for the other uitableview to pick it up.
This is assuming that your singleEvent object have the key fields such as Id.
Hope this helps.

SQLite insert statement does not insert the new data

SQLite insert statement does not insert the new data
i am using this code
- (void) addN: (number *) theN
{
const char *sql = "insert into table (valueN) Values(?)";
sqlite3_stmt *addStmt = nil;
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
NSString * docsDir = [paths objectAtIndex:0];
NSString * thePath = [docsDir stringByAppendingPathComponent: #"theDB.sqlite"];
sqlite3_open([thePath UTF8String], &database);
sqlite3_prepare_v2(database, sql, -1, &addStmt, NULL);
sqlite3_bind_int(addStmt, 1, theN.theNumber);
sqlite3_step(addStmt);
sqlite3_finalize(addStmt);
sqlite3_close(database);
}
and the method to insert the record is
- (IBAction) saveN: (id) sender
{
numbers *theNumbers = [[numbers alloc] init];
number *theNumber = [[number alloc] init];
theNumber.theNumber = newN;
[theNumbers addN:theNumber];
[theNumbers release];
[theNumber release];
}
the code is executed but no record is inserted in the DB.
can someone please point me where is the mistake - cause there obviously is a mistake :)
You're not checking the return values of any of the sqlite3 calls. There's a good chance one of them is failing.
this code seems to work:
- (void) addN: (number *) theN
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *thePath = [[[NSBundle mainBundle] resourcePath]
stringByAppendingPathComponent:#"theDB.sqlite"];
BOOL success = [fileManager fileExistsAtPath:thePath];
if (!success)
{
NSLog(#"Failed to find database file '%#'.", thePath);
}
if (!(sqlite3_open([thePath UTF8String], &database) == SQLITE_OK))
{
NSLog(#"An error opening database, normally handle error here.");
}
const char *sql = "insert into table (valueN) Values(?)";
sqlite3_stmt *addStmt = nil;
if (sqlite3_prepare_v2(database, sql, -1, &addStmt, NULL) != SQLITE_OK)
{
NSLog(#"Error, failed to prepare statement, normally handle error here.");
}
sqlite3_bind_int(addStmt, 1, theN.theNumber);
sqlite3_step(addStmt);
if(sqlite3_finalize(addStmt) != SQLITE_OK)
{
NSLog(#"Failed to finalize data statement, normally error handling here.");
}
if (sqlite3_close(database) != SQLITE_OK)
{
NSLog(#"Failed to close database, normally error handling here.");
}
}

NSImage to blob and blob to NSImage (SQLite, NSData)

First of all, I know that I should not use an SQLite database to store image, but I only store favicon of website which are really small.
My problem is that I try to insert those favicon into the database (seems to work), I convert the favicon to NSData with the -tiffrepresentation method of NSimage and then insert it to my database into a blob column:
NSImage *favico = [webview mainFrameIcon];
[appDelegate insertBookmark:[titleField stringValue] url:[urlfield stringValue] data:[favico TIFFRepresentation]]
The SQLite method look like this:
-(void)insertBookmark:(NSString *)title url:(NSString *)url data:(NSData *)data
{
NSData *imagedata = [[NSData alloc]initWithData:data];
sqlite3 *database;
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK) {
NSString *query = [NSString stringWithFormat:#"INSERT INTO Bookmarks (title, url, image) VALUES ('%#', '%#', '%#')",title, url, data];
const char *querychar = [query UTF8String];
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(database, querychar, -1, &statement, NULL) == SQLITE_OK)
{
int row = 3;
sqlite3_bind_blob(statement, row, [imagedata bytes], [imagedata length], NULL);
sqlite3_step(statement);
sqlite3_finalize(statement);
}
else
{
NSLog(#"Error");
}
[databasePath retain];
[databaseName retain];
}
sqlite3_close(database);
[imagedata release];
}
When I look into the database I have value in the image column between <data> (normal ? )
Now when I extract the blob from the database and try to put it in my object I got null in the NSimage:
-(void) readDatabase {
// Setup the database object
sqlite3 *database;
bookmarks = [[NSMutableArray alloc] init];
// Open the database from the users filessytem
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK) {
// Setup the SQL Statement and compile it for faster access
const char *sqlStatement = "SELECT * FROM Bookmarks ORDER BY title ASC";
sqlite3_stmt *compiledStatement;
if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK) {
// Loop through the results and add them to the feeds array
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
// Read the data from the result row
NSString *aTitle = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 1)];
NSString *aUrl = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 2)];
NSData *data = [[NSData alloc] initWithBytes:sqlite3_column_blob(compiledStatement, 3) length:sqlite3_column_bytes(compiledStatement, 3)];
NSImage *image = [[NSImage alloc]initWithData:data];
bookmarkObject *bookmark = [[bookmarkObject alloc] initWithName:aTitle url:aUrl favico:image];
[bookmarks addObject:bookmark];
[bookmark release];
[data release];
[image release];
}
}
// Release the compiled statement from memory
sqlite3_finalize(compiledStatement);
sqlite3_close(database);
[databasePath retain];
[databaseName retain];
}
}
Thanks in advance
The < and > around your actual data come from NSData. By using %# in your format string and providing data, NSString sends description to data. [NSData description] wraps the content between < and >.
Another issue with your code is, that you seem to mix parameter-less & parameter prepared statements:
Either use stringWithFormat: to create a complete query statement
or use a query like INSERT INTO Bookmarks (title ...) VALUES (? ...) combined with sqlite3_bind_blob
The parameter syntax SQLite supports can be found here:
http://www.sqlite.org/c3ref/bind_blob.html