Reputation: 95
I'm currently getting an error when trying to insert data into my database.
#import "ReminderDB.h"
@implementation ReminderDB
@synthesize db = _db;
-(id)init{
_db = [self openDB];
[self createTable:@"Reminders" withField1:@"Title" withField2:@"Who" withField3:@"Quantity"];
return self;
}
-(NSString *) filepath{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
return [[paths objectAtIndex:0] stringByAppendingPathComponent:@"reminders.sqlite"];
}
- (sqlite3*)openDB {
if (_db == NULL) {
int rc;
if ((rc = sqlite3_open([[self filepath] UTF8String], &(_db))) != SQLITE_OK) {
NSLog(@"%s error (%1d)", __FUNCTION__, rc);
_db = NULL;
} else {
NSLog(@"db opened");
}
}
return _db;
}
-(void)createTable: (NSString *) tableName
withField1: (NSString *) field1
withField2: (NSString *) field2
withField3: (NSString *) field3
{
char *err;
NSString *sql = [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS '%@' ('%@' TEXT PRIMARY KEY, '%@' TEXT, '%@' TEXT);", tableName, field1, field2, field3];
if(sqlite3_exec(_db, [sql UTF8String], NULL, NULL, &err) != SQLITE_OK){
sqlite3_close(_db);
NSAssert(0, @"Could not create table");
}
else{
NSLog(@"Table Created");
}
}
-(void)addReminder:(NSString*)title
who:(NSString*)who
quantity:(NSString*)quantity{
NSString *sql = [NSString stringWithFormat:@"INSERT INTO Reminders ('Title', 'Who', 'Quantity') VALUES ('%@', '%@', '%@')", title, who, quantity];
char *err;
int rc;
if((rc = sqlite3_exec(_db, [sql UTF8String], NULL, NULL, &err)) != SQLITE_OK){
NSLog(@"%s error (%1d)", __FUNCTION__, rc);
sqlite3_close(_db);
NSAssert(0, @"Could not update table");
}
else{
NSLog(@"Table Update Successful");
}
}
@end
This code successfully opens the database and creates the table. However when I call addReminder:who:quantity
the table will not update and the error I am getting is an SQL Error or Missing Database
error. Which doesn't make sense to me because I know the table is created and exists.
EDIT
I have updated my addReminder:who:quantity:
to use binds instead of exec's. I have also taken out the close calls. I am now getting an error when calling prepare.
-(void)addReminder:(NSString*)title
who:(NSString*)who
quantity:(NSString*)quantity{
const char *sql = "INSERT INTO Reminders ('Title', 'Who', 'Quantity') VALUES (?, ?, ?)";
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(_db, sql, -1, &statement, NULL) == SQLITE_OK) {
// Bind the parameters (note that these use a 1-based index, not 0).
sqlite3_bind_text(statement, 1, [title UTF8String], -1, SQLITE_TRANSIENT);
sqlite3_bind_text(statement, 2, [who UTF8String], -1, SQLITE_TRANSIENT);
sqlite3_bind_text(statement, 3, [quantity UTF8String], -1, SQLITE_TRANSIENT);
NSLog(@"Binding successful");
}
int returnCode = sqlite3_step(statement);
if (returnCode != SQLITE_DONE) {
// error handling...
NSLog(@"An error occoured (%d)", returnCode);
}
else{
NSLog(@"Table Updated");
}
}
I know the problem is in the prepare call because I am not getting "Binding successful" in my log.
Upvotes: 0
Views: 1138
Reputation: 437381
A couple of thoughts:
I don't see anything in the provided code snippet that would produce a warning that says "SQL Error or Missing Database". Have you identified where that's being produced?
If you close the database anywhere, don't forget to set _db
to NULL
. You might have a close method that looks like:
- (int)closeDB {
if (_db) {
int rc = sqlite3_close(_db);
_db = NULL;
return rc;
}
return SQLITE_OK;
}
Having said that, you generally don't open and close databases while an app is running, so this might not be critical. But if you are closing it anywhere, make sure to NULL
the pointer, too. Otherwise your openDB
method won't work properly if you try to reopen the database.
In your addReminder
method, make sure to log sqlite3_errmsg
:
-(void)addReminder:(NSString*)title
who:(NSString*)who
quantity:(NSString*)quantity {
const char *sql = "INSERT INTO Reminders ('Title', 'Who', 'Quantity') VALUES (?, ?, ?)";
sqlite3_stmt *statement;
int returnCode;
if ((returnCode = sqlite3_prepare_v2(_db, sql, -1, &statement, NULL)) == SQLITE_OK) {
// Bind the parameters (note that these use a 1-based index, not 0).
sqlite3_bind_text(statement, 1, [title UTF8String], -1, SQLITE_TRANSIENT);
sqlite3_bind_text(statement, 2, [who UTF8String], -1, SQLITE_TRANSIENT);
sqlite3_bind_text(statement, 3, [quantity UTF8String], -1, SQLITE_TRANSIENT);
NSLog(@"Binding successful");
} else {
NSLog(@"Prepare failed: %s (%ld)", sqlite3_errmsg(_db), (long) returnCode);
return;
}
returnCode = sqlite3_step(statement);
if (returnCode != SQLITE_DONE) {
// error handling...
NSLog(@"An error occurred: %s (%ld)", sqlite3_errmsg(_db), (long)returnCode);
}
else{
NSLog(@"Table Updated");
}
sqlite3_finalize(statement);
}
Note, don't forget to call sqlite3_finalize
whenever you call sqlite3_prepare_v2
or else you'll leak memory.
As an aside, your CREATE TABLE
call is creating Quantity
as a TEXT
column. You might want to create that as INTEGER
if it's an integer value, or REAL
if it's a floating point value.
When you bind values for the Quantity
value, you might want to use the type-appropriate bind call, e.g.
sqlite3_bind_int(statement, 3, [quantity intValue]);
Clearly, though, your createTable
method seems to be hard coded to create a table with three TEXT
columns, so you might want to refactor that, too (though SQLite ignores the column definition when inserting values, so this isn't absolutely critical).
But, by storing numeric data types, it means that when you sort by Quantity
column or do arithmetic calculations, SQLite will handle this properly. Actually, SQLite is pretty flexible in terms of converting string values to numeric values in many cases, so this isn't critical, but I think it's still appropriate to store numeric values as numbers, rather than strings.
Note, the above point is completely unrelated to your problem at hand, but it does reflect best practice.
You say that your sqlite3_prepare_v2
statement is producing an error that says:
table Reminders has no column named Who
Your create statement only creates the table if it doesn't exist. So if you accidentally created it earlier using different columns, then "Who" might not be found, because it won't recreate a table that already exists.
Open the database from the simulator/device (not from the project folder, if you have one there) using your Mac OS X database tool of choice (or the sqlite3
command line program, if you don't have a nice database tool) and take a look at the table and make sure what the column names are. When dealing with the simulator, the precise location of this folder depends upon what version of Xcode you're using. In Xcode 5, it's in ~/Library/Application Support/iPhone Simulator
and then navigate to the appropriate iOS version and application, and look in the appropriate Documents
subfolder. In Xcode 6 it's located in ~/Library/Developer/CoreSimulator/Devices
(and you have to figure out which cryptic folder the app is in, often simplified if you sort this folder by "date"). And, if you're not seeing ~/Library
, make sure to unhide the Library
folder, easily shown by using the command chflags nohidden ~/Library
from the Terminal
command line.
Or just delete the app from the simulator/device and start over, letting it recreate the database again, so you know you're not dealing with an old, out-of-date database.
While I encourage you to finish this up using the SQLite C API (it's a good learning experience), I must say that you might want to investigate FMDB, which is an Objective-C wrapper around the SQLite C API. I wouldn't do that right now (you have too many moving parts and I don't want to confuse the situation further), but after you address your immediate challenges, it's worth considering.
Upvotes: 1