Related
It's my first time trying to develop an SQLite database for IOS, or any IOS app for that matter. I'm trying to follow a tutorial I found online and adapt it for my own use. The database was created without any issues but my Insert statement never seems to return my error message.
Nothing appears in the console as nothing drastic actually goes wrong with the program. If you need any more information I'll try my best to find it and update the question with it.
Here is my code:
// Method to store a GPS location
-(void)insertGPS:(GPS*)GPS
{
// Create insert statement for the person
NSString *insertStatement = [NSString stringWithFormat:#"INSERT INTO GPSJob (jobNo, sourceMonitor, positionNumber, latitude, longitude) VALUES (\"%#\", \"%#\", \"%#\",\"%#\",\"%#\")", GPS.jobNumber, GPS.sourceMonitor, GPS.positionNumber, GPS.latitude, GPS.longitude ];
// Define an error
char *error;
// Attempt to execute the insert statement
if ( sqlite3_exec(databaseHandle, [insertStatement UTF8String], NULL, NULL, &error) == SQLITE_OK)
{
NSLog(#"GPS inserted into database with values: %#, %#, %#, %#, %#.", GPS.jobNumber, GPS.sourceMonitor, GPS.positionNumber, GPS.latitude, GPS.longitude);
}
// If the insert statement is not okay
else {
NSLog(#"Error: %s", sqlite3_errmsg(databaseHandle));
}
}
Here is the tutorial I'm following: http://www.apptite.be/tutorial_ios_sqlite.php
The updated error message said this :
2015-08-12 15:28:25.299 NoiseApp[7602:207] Error: out of memory
-----------------------------------Solution----------------------------------
For anyone who wants the solution to this problem I simply amended the function to read as follows :
// Method to store a GPS location
-(void)insertGPS:(GPS*)GPS
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *databasePath = [documentsDirectory stringByAppendingPathComponent:#"GPS.db"];
if(sqlite3_open([databasePath UTF8String], &databaseHandle) ==SQLITE_OK){
// Create insert statement for the person
NSString *insertStatement = [NSString stringWithFormat:#"INSERT INTO GPSJob (jobNo, sourceMonitor, positionNumber, latitude, longitude) VALUES (\"%#\", \"%#\", \"%#\",\"%#\",\"%#\")", GPS.jobNumber, GPS.sourceMonitor, GPS.positionNumber, GPS.latitude, GPS.longitude ];
// Define an error
char *error;
// Attempt to execute the insert statement
if ( sqlite3_exec(databaseHandle, [insertStatement UTF8String], NULL, NULL, &error) == SQLITE_OK)
{
NSLog(#"GPS inserted into database with values: %#, %#, %#, %#, %#.", GPS.jobNumber, GPS.sourceMonitor, GPS.positionNumber, GPS.latitude, GPS.longitude);
}
// If the insert statement is not okay
else {
NSLog(#"Error: %s", sqlite3_errmsg(databaseHandle));
}
}
}
databaseHandle is not defined in your function. You should get your reference to the sqlite3_open somewhere.
Use sqlite3_open to open your database, if you didn't do this already.
Example of opening. (note if you want to write to this database you should copy it from the resourcePath to somewhere writeable, and open this version)
databaseName = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"DB.sql"];
if(sqlite3_open([databaseName UTF8String],&databaseHandle) == SQLITE_OK)
{
}
UPDATED QUESTION, check the end of the post
I am making an iPhone app and I want when the user first lunches the app, for the app to create a new database and populate it with the data of a plist file.
Here is my view did load code:
//get database path
NSString *symptomDatabasePath = [self symptomsDatabasePath];
//check if database exists and initialize if it doesn't
if(![[NSFileManager defaultManager] fileExistsAtPath:symptomDatabasePath])
{
[self createAndPopulateDatabase];
}
My symptomDatabasePath function:
//get the path of the symptoms database
- (NSString *) symptomsDatabasePath
{
NSArray *pathsArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [pathsArray objectAtIndex:0];
return [documentsDirectory stringByAppendingPathComponent:#"symptoms.sqlite"];
}
And finally my createANdPopulateDatabase function:
- (void) createAndPopulateDatabase
{
//get query to insert symptoms into database
NSArray *pathsArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [pathsArray objectAtIndex:0];
NSString *databaseDataFile = [documentsDirectory stringByAppendingPathComponent:#"symptomsDatabaseList.plist"];
NSString *symptomData = [NSString stringWithContentsOfFile:databaseDataFile encoding:NSUTF8StringEncoding error:NULL];
sqlite3 *database;
if (sqlite3_open([[self symptomsDatabasePath] UTF8String], &database)!=SQLITE_OK)
{
sqlite3_close(database);
NSAssert(0, #"Failed to open database");
}
NSString *createTableQuery = #"CREATE TABLE IF NOT EXISTS tbl_symptoms (symptomCode varchar(15) NOT NULL PRIMARY KEY,title varchar(255) NOT NULL,shortTitle varchar(255) NOT NULL,inclusions varchar(255) NOT NULL,exclusions varchar(255) NOT NULL,symptomCategory varchar(255) NOT NULL);";
char *errorMsg;
//create new table
if(sqlite3_exec(database, [createTableQuery UTF8String], NULL, NULL, &errorMsg)!=SQLITE_OK)
{
sqlite3_close(database);
NSAssert(0, #"Error creating table: %s", errorMsg);
}
NSString *insertQueryString = #"INSERT INTO tbl_symptoms (symptomCode, title, shortTitle, inclusions, exclusions, symptomCategory) VALUES ";
//array containing the seperate symptom data
NSArray *symptomArray = [symptomData componentsSeparatedByString:#"),"];
sqlite3_stmt *statement;
//nsmutable array with all queries
NSMutableArray *queryArray = [[NSMutableArray alloc] init];
//loop through the aray with symptoms build queries and store them in an array
for(int i=0; i<[symptomArray count]; i++)
{
//create query string with insert command
NSMutableString *insertString = [[NSMutableString alloc] initWithString:insertQueryString];
//append the specific symptoms data
[insertString appendString:[symptomArray objectAtIndex:i]];
[insertString appendString:#");"];
[queryArray insertObject:insertString atIndex:i];
}
//insert data into database
for(int i=0; i<[queryArray count]; i++)
{
const char *insertChar = [[queryArray objectAtIndex:i] UTF8String];
if(sqlite3_prepare_v2(database, insertChar, -1, &statement, nil)!=SQLITE_OK)
{
sqlite3_close(database);
NSAssert(0, #"Error filling table: %s", errorMsg);
}
if (sqlite3_step(statement)!=SQLITE_DONE)
{
NSAssert(0, #"Error inserting data into database: %s", errorMsg);
}
//finalize changes
}
sqlite3_finalize(statement);
sqlite3_close(database);
}
My code is a bit messy because i have been trying various solutions. Anyway I have the data I want to input written like this in my phlist file:
('Z27', 'Fear of a social problem', 'Fear of a social problem', 'concern about/fear of having a social problem', 'if the patient has a social problem, code the problem ', 'Social problems’),
('Z28', 'Limited function/disability (Z)', 'Limited function/disability (Z)', '', '', 'Social problems’),
('Z29', 'Social problem NOS', 'Social problem NOS', 'environmental problems; malingering', '', 'Social problems’
there are 320 such entries in my file, these are the last 3 because they are the ones I am having trouble with.
The app copies all the other 317 entries, but not these 3. And I know it is missing a ) at the end, but that's because my code appends it on every loop.
I get a SIGABRT error and the following error code:
"Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Error filling table: (null)'"
Can anyone help me? I probably have done something wrong with sqlite3, though I don't understand why it only doesn't copy the last 3 entries.
Also earlier it wouldn't copy only the last entry, but after I copy pasted it around to see if it was a problem with the entry itself, it started not copying the last 3 :S.
Any help will be greatly appreciated
UPDATE
So it turns out I was getting the errors cause I was using NSAssert. Now I am using NSLogs to log my errors, and the app launches correctly, but I still get the error when copying the final 3 entries.
According to my logs moth the sqlite3_prepare_v2 and the sqlite3_step functions fail, but ONLY for the final 3 entries :S
The problem is that you have smart quotes. For example, I see you have
..., 'Social problems’), ...
Note, you're starting that with a standard apostrophe, but finishing it with a closing single quote. If you look very carefully, they look different.
Replace those smart quotes with standard apostrophe and that should fix the immediate problem.
I'd suggest you log the SQL and carefully examine it. Also, when the sqlite3_prepare_v2 fails, you are not logging the appropriate error message. You can do something like:
if (sqlite3_prepare_v2(database, insertChar, -1, &statement, nil)!=SQLITE_OK) {
NSLog(#"prepare failure: %s", sqlite3_errmsg(database));
sqlite3_close(database);
NSAssert(0, #"Error filling table");
}
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];
I working on an app that takes input from a text field and puts it into a string. I have a table with a field in it that I want to check the value of the string from the input against the value in the field in the database. I'm new to iOS and fairly new to SQLite.
Code:
-(IBAction)setInput:(id)sender
{
NSString *strStoreNumber;
NSString *strRegNumber;
strStoreNumber = StoreNumber.text;
strRegNumber = RegNumber.text;
lblStoreNumber.text = strStoreNumber;
lblRegNumber.text = strRegNumber;
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* documentsDirectory = [paths lastObject];
// NSString* databasePath = [documentsDirectory stringByAppendingPathComponent:#"tblStore.sqlite"];
NSString* databasePath = [[NSBundle mainBundle] pathForResource:#"tblStore" ofType:#"sqlite"];
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK)
{
NSLog(#"Opened sqlite database at %#", databasePath);
//...stuff
}
else
{
NSLog(#"Failed to open database at %# with error %s", databasePath, sqlite3_errmsg(database));
sqlite3_close (database);
}
NSString *querystring;
// create your statement
querystring = [NSString stringWithFormat:#"SELECT strStore FROM tblStore WHERE strStore = %#;", strStoreNumber];
const char *sql = [querystring UTF8String];
NSString *szStore = nil;
NSString *szReg = nil;
if (sqlite3_prepare_v2(database, sql, -1, &databasePath, NULL)!=SQLITE_OK) //queryString = Statement
{
NSLog(#"sql problem occured with: %s", sql);
NSLog(#"%s", sqlite3_errmsg(database));
}
else
{
// you could handle multiple rows here
while (sqlite3_step(databasePath) == SQLITE_ROW) // queryString = statement
{
szStore = [NSString stringWithUTF8String:(char*)sqlite3_column_text(databasePath, 0)];
szReg = [NSString stringWithUTF8String:(char*)sqlite3_column_text(databasePath, 1)];
} // while
}
sqlite3_finalize(databasePath);
// Do something with data...
}
It gets to the line "NSLog(#"Opened sqlite database at %#", databasePath);", so it appears as though it has access to the database. However, when I run the app, I get the "NSLog(#"sql problem occured with: %s", sql);" error, which I can see in the console. Additionally, in the console, it says "No such table: tblStore".
I created the table using the Firefox add-on SQLite Manager. I added the sqlite3 library to the project. I dragged and dropped the database table I created in SQLite manager into my project, above my two AppDelegate files and my two ViewController files.
Any help or input would be greatly appreciated. Thanks!
EDIT: I have properly added the file to the project, and it appears as though the table is found now. Now I have some strange warnings, though:
"Incompatible pointer types passing 'const char *' to parameter of type 'sqlite3_stmt *' (aka 'struct sqlite3_stmt *')"
This warning appears on the following lines of code:
if (sqlite3_prepare_v2(database, sql, -1, &databasePath, NULL)!=SQLITE_OK)
while (sqlite3_step(sql) == SQLITE_ROW)
szStore = [NSString stringWithUTF8String:(char*)sqlite3_column_text(sql, 0)];
szReg = [NSString stringWithUTF8String:(char*)sqlite3_column_text(sql, 1)];
sqlite3_finalize(sql);
It's got something to do with "sql", but I'm unsure of what. Any suggestions?
Your code seems ok - did you copy the db to the ressource folder of your project?
EDIT
Make sure you access your db file with something like that:
- (void) initializeDB {
// Get the database from the application bundle
NSString* path = [[NSBundle mainBundle] pathForResource:#"tblStore" ofType:#"sqlite"];
if (sqlite3_open([path UTF8String], &database) == SQLITE_OK)
{
NSLog(#"Opening Database");
}
else
{
// Call close to properly clean up
sqlite3_close(database);
NSAssert1(0, #"Error: failed to open database: '%s'.",
sqlite3_errmsg(database));
}
}
The database file you add to the project will be embedded in the main NSBundle (see [NSBundle mainBundle]).
In order to do what you want, you need to copy the database from the main bundle to the documents folder before trying to access it. Otherwise, as you are experiencing, you will not be able to find the SQLite DB on the document's folder.
You can copy your database, click finder and write this address(/Users/administrator/Library/Application Support/iPhone Simulator/6.1/Applications/) in finder click ok.
You will get documentary path.
Open your project document file and paste your database....
I am currently trying to simply insert a new row into my SQLite3 database on my iPad. I have done it multiple times before in other apps and just copied the code. The copied SELECT queries work fine, but if I try to INSERT, it fails at == SQLITE_DONE
This is how I try to insert into the database:
NSString *databaseName = #"Waypoints.sql";
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
NSString *databasePath = [documentsDir stringByAppendingPathComponent:databaseName];
sqlite3 *database;
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK)
{
NSString *statement = [NSString stringWithFormat:
#"INSERT INTO Waypoints (id, name, alt, ias, temp, tas, wd, ws, gs, mt, mh, dist, time, fuel, fuelrate) VALUES (%d,'New...','','','','','','','','','','','','','');", [self numberOfWaypoints]];
NSLog(#"Statement: %#", statement);
const char *sqlStatement = [statement cStringUsingEncoding:NSASCIIStringEncoding];
sqlite3_stmt *compiledStatement;
if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK)
{
if(sqlite3_step(compiledStatement) == SQLITE_DONE)
NSLog(#"DATABASE: Adding: Success");
else
NSLog(#"DATABASE: Adding: Failed");
}
else
NSLog(#"Error. Could not add Waypoint.");
sqlite3_finalize(compiledStatement);
}
sqlite3_close(database);
If I run this code on the press of a button, it outputs DATABASE: Adding: Failed in the console.
The NSLogged statement looks like this:
INSERT INTO Waypoints (id, name, alt, ias, temp, tas, wd, ws, gs, mt, mh, dist, time, fuel, fuelrate) VALUES (2,'New...','','','','','','','','','','','','','');
Which works perfectly fine if I paste it in the terminal (connected to same database file).
This brings me to the conclusion: what could be causing this problem?
I thought of maybe write permissions to the file. Could be it, but it's not in the bundle but already copied to the documents folder on the device.
Please help to how I can get this to work?
Try using NSUTF8StringEncoding for your cStringUsingEncoding or printing
NSLog(#"%#", [NSString stringWithUTF8String:(char*)sqlite3_errmsg(database)]);
Glad to see you could find your error with this.
By following the tip from #IgnacioInglese by printing out the error with NSLog(#"%#", [NSString stringWithUTF8String:(char*)sqlite3_errmsg(database)]); I found that the database is locked.
I found that I made a rookie-error by return a value in a method before closing the database. Fixing that solved my problem.