This must be a stupid problem but i cant get my query to work. Its as if my database is empty (I double checked, its not). Its a simple SELECT * FROM table query. This is how i try it:
+(MyDatabase *)database{
if (database == nil) {
database = [[MyDatabase alloc] init];
}
return database;
}
- (id)init
{
self = [super init];
if (self) {
NSString *sqliteDb = [[NSBundle mainBundle] pathForResource:#"personsDB" ofType:#"sqlite3"];
if (sqlite3_open([sqliteDb UTF8String], &database) != SQLITE_OK) {
NSLog(#"Fail to open Database.");
}
}
return self;
}
and
-(NSArray *)getAllRows{
NSMutableArray *retArray = [[NSMutableArray alloc] init];
NSString *query = #"SELECT * FROM persons";
sqlite3_stmt *statement;
NSLog(#"checking SQLITE_OK?");
if (sqlite3_prepare_v2(database, [query UTF8String], -1, &statement, nil) == SQLITE_OK) {
NSLog(#"SQLITE_OK");
while (sqlite3_step(statement) == SQLITE_ROW) {
int personID = sqlite3_column_int(statement, 0);
char *personChars = (char *) sqlite3_column_text(statement, 1);
int gender = sqlite3_column_int(statement, 2);
int related = sqlite3_column_int(statement, 3);
NSString *person = [[NSString alloc] initWithUTF8String:personChars];
MyDBInfo *info = [[MyDBInfo alloc] initWithpersonID:personID person:person gender:gender related:related ];
[retArray addObject:info];
}
sqlite3_finalize(statement);
}
return retArray;
}
I think this is all the interesting stuff.
In my Log I get checking SQLITE_OK?, but no SQLITE_OK. I'm not getting Fail to open Database. so I'm assuming that its all good there.
My Database is full and there is a table called persons. I'm very new to sqlite in iOS apps.
Thank you.
Are you sure the db is included in your bundle? The default behavior of sqlite3_open is SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, so it will be created on your device/simulator if it wasn't there already, so you won't see Fail to open Database. Use sqlite3_open_v2 if you don't want SQLITE_OPEN_CREATE. Anyway, I sometimes have added files to my xcode project and they're not automatically included in my bundle. Select the target in the top of the project navigator, click on the target, select the target in the main panel, select "Build Phases", and see if your db is included in the "Copy Bundle Resources".
Probably unrelated, but I always copy the database from the bundle to documents folder (if it's not there already).
If I don't receive the expected SQLITE_OK, I always look at my return code or log my error message and error codes (and this would probably report that the table in question was not found, which would have let you identify the problem).
Thus,
NSLog(#"%s db err '%s' (%1d)", __FUNCTION__, sqlite3_errmsg(contactDB), sqlite3_errcode(contactDB));
Related
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.
I am having SQLite database where I have one table called tblUser,where I am having variables say Id,name,image,gender.
In my app,I am giving functionality to add,delete update the User entry (Common functionality). Say I have 2 record by default on the page with 2 users and that both are deleted. Now the 3rd record inserted will be provided Primary key as 3.
Now if I edit that particular record then it is not updated with new data rather the primary key of the same gets replaced with 1 when I call following function :
+ (void) getInitialDataToDisplay:(NSString *)dbPath
{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
//sqlite3_open([dbPath UTF8String], &database);
if (sqlite3_open([dbPath UTF8String], &database) == SQLITE_OK)
{
const char *sql = "select UserId,UserAge,UserName,UserGender,UserImage from [tblUser]";
sqlite3_stmt *selectstmt;
if(sqlite3_prepare_v2(database, sql, -1, &selectstmt, NULL) == SQLITE_OK)
{
while(sqlite3_step(selectstmt) == SQLITE_ROW)
{
NSInteger primaryKey = sqlite3_column_int(selectstmt,0);
UserDetail *DataObj = [[UserDetail alloc] initWithPrimaryKey:primaryKey];
DataObj.UserAge = sqlite3_column_int(selectstmt,1);
DataObj.UserName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 2)];
DataObj.UserGender = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 3)];
DataObj.UserImage = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 4)];
DataObj.isDirty = NO;
[appDelegate.DataArray addObject:DataObj];
}
}
else
{
NSAssert1(0,#"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database));
}
}
else
sqlite3_close(database); //Even though the open call failed, close the database connection to release all the memory.
}
Note that I am calling this function when I come back to the page where listing of the user is shown after clicking save button of the page where all details need to be filled. Any hint will be much helpful. Thank you.
Assuming that UserId is an INTEGER PRIMARY KEY:
SQLite will create new IDs as one larger than the previously largest value,
but if the table is empty, it will (re)start at 1.
I can't make a Select on a table in a sqlite database.
I have the following code to copy the .sqlite-file to the user's directory:
// copy the database to the user's directory
- (void)checkAndCreateDB {
// Check if the SQL database has already been saved to the users phone, if not then copy it over
BOOL success;
// Create a FileManager object, we will use this to check the status
// of the database and to copy it over if required
NSFileManager *fileManager = [NSFileManager defaultManager];
// Check if the database has already been created in the users filesystem
success = [fileManager fileExistsAtPath:databasePath];
// If the database already exists then return without doing anything
if(success) return;
// If not then proceed to copy the database from the application to the users filesystem
// Get the path to the database in the application package
NSString *databasePathFromApp = [[NSBundle mainBundle] pathForResource:databaseName ofType:nil];
// Copy the database from the package to the users filesystem
[fileManager copyItemAtPath:databasePathFromApp toPath:databasePath error:nil];
[fileManager release];
}
And the select here:
sqlite3 *database;
categories = [[NSMutableArray alloc] init];
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK) {
const char *sqlStatement = "select * from category";
sqlite3_stmt *compiledStatement;
NSLog(#"get");
if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK) {
NSLog(#"test");
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
NSString *cId = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 1)];
NSString *cName = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 2)];
Category *category = [[Category alloc] initWithId:cId name:cName];
[categories addObject:category];
[categories release];
}
}
// Release the compiled statement from memory
sqlite3_finalize(compiledStatement);
}
sqlite3_close(database);
the log shows only: "get". "test" isn't there.
I hope someone can help me.
I believe your database copy code isn't working as you are passing nil to the ofType parameter of [NSBundle pathForResource]. Try passing the correct file extension and add code to detect the success or failure of the copy operation (and make your checkAndCreateDB method return BOOL) and take it from there.
Your database select code looks OK to me, so I'm guess you have an empty database as explained in #Micheal's answer.
sqlite3_open will create a new (empty) database if the one specified doesn't exist. To make sure that this isn't what's happening, try using sqlite3_open_v2 instead and use SQLITE_OPEN_READWRITE as the flags argument. That way you'll get an error if you attempt to open a database that doesn't exist.
There is an example project for using SQLite here you can refer to: https://github.com/AaronBratcher/ABSQLite
It has classes for accessing SQLite in a more traditional database way that I feel makes things easier.
I have problems with SQLite function sqlite3_prepare_v2, it always returns 1 error code. I'm using SQLite wrapper SQLitemanagerforIOS4. Previously the same error happened without using the wrapper, I switched to it because, despite the statement was encoded in UTF8, the error still happened. I checked the database path with the debugger, and it's correct, so I'm lost...By the way, database is correctly opened and closed.
Here it is the pice of code :
- (NSArray *)getRowsForQuery:(NSString *)sql {
NSMutableArray *resultsArray = [[NSMutableArray alloc] initWithCapacity:1];
if (db == nil) {
[self openDatabase];
}
sqlite3_stmt *statement;
const char *query = [sql UTF8String];
int prepareStatus = sqlite3_prepare_v2(db, query, -1, &statement, NULL);
while (sqlite3_step(statement) == SQLITE_ROW) {
Many thanks for your help.
Here they are the parameters passed to the wrapper object:
- (void)viewDidLoad
{
[super viewDidLoad];
dbManager = [[SQLiteManager alloc] initWithDatabaseNamed:#"XLO.sqlite"];
SArray *provinciaArray = [dbManager getRowsForQuery:[NSString stringWithFormat:#"SELECT provincia FROM provincias;"]];
Thanks!
Peter, here it is:
- (NSError *) openDatabase {
NSError *error = nil;
NSString *databasePath = [self getDatabasePath];
const char *dbpath = [databasePath UTF8String];
#ifdef DEBUG
NSLog(#"SQL result: <%s>", dbpath );
#endif
int result = sqlite3_open(dbpath, &db);
if (result != SQLITE_OK) {
const char *errorMsg = sqlite3_errmsg(db);
NSString *errorStr = [NSString stringWithFormat:#"The database could not be opened: %#",[NSString stringWithCString:errorMsg encoding:NSUTF8StringEncoding]];
error = [self createDBErrorWithDescription:errorStr andCode:kDBFailAtOpen];
}
return error;
}
Many thanks to all!
Because it's saying the database table does not exist, you may not be opening the database # the path that you think you are. Remember that sqlite is passive - it will create a database on first write.
Run the app in the simulator and print out the path. Then go to that path in terminal and use the sqlite cmdline to confirm the db is there and it has the tables.
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.