I am developing an iOS app using sqlite. This is a very data-centric app so I have made a function called prepareQuery. I won't ever be accessing the database at multiple times, so cmd is global and is working great.
-(BOOL) prepareQuery:(NSString *) query
{
const char *dbPath = [databasePath UTF8String];
if (sqlite3_open(dbPath, &db) == SQLITE_OK)
{
const char *query_stmt = [query UTF8String];
if (sqlite3_prepare_v2(db, query_stmt, -1, &cmd, NULL) == SQLITE_OK)
return YES;
}
return NO;
}
Now I have the following (edited for readability)
[self prepareQuery:#"SELECT STATEMENT FOR TABLE 1;"];
NSLog(#"Result: %i", sqlite3_step(cmd));
//Work with the 1st query
sqlite3_finalize(cmd);
[self prepareQuery:#"SELECT STATEMENT FOR TABLE 2;"]];
NSLog(#"Result: %i", sqlite3_step(cmd));
//Work with the 2nd query
sqlite3_finalize(cmd);
if ([self prepareQuery:#"INSERT STATEMENT FOR TABLE 1;"])
NSLog(#"Result: %i", sqlite3_step(cmd));
else
NSLog(#"error preparing statement");
I get the following output:
Result: 100
Result: 100
Result: 5
This is not the first procedure I've had where I've ran multiple selects then done an insert. Any idea why the last result is SQLITE_BUSY? I've tried closing the database, and setting cmd to nil.
I found the issue (I knew that would happen as soon as I hit post...)
The code I posted is called from a different procedure. All 15 statements in there were finalized except one. I finalized that one and it worked. I don't understand why I could run 7 selects, 1 update, 7 more selects without finalizing #4 but it gave an error in the 2nd procedure...
Related
When I am trying to insert data into my table, sometimes I get this error, and my app crashes!
Crash logs :
Observation(4001,0x10dd67000) malloc: *** error for object 0x7fff3a917100: Non-aligned pointer being freed (2)
*** set a breakpoint in malloc_error_break to debug
2016-11-03 11:12:03.063 Observation[4001:46477] Insertion failed !
Printing description of dbpath:
(const char *) dbpath = 0x00007fff3b8a5690 "/Users/macbt/Library/Developer/CoreSimulator/Devices/0EEC62AE-6DF0-4FC4-9D30-1EB90CB695A5/data/Containers/Data/Application/A709E729-3162-4CC8-B9FF-2F22A32FC6BD/Documents/ObservationDB.db"
Printing description of insertSQL:
insert into table_hazard (id, name, modifiedDate) values ("1","Hazard", "03/11/2016 11:12:03 AM")
Printing description of insert_stmt:
(const char *) insert_stmt = 0x00007fff3b9291a1 "insert into table_hazard (id, name, modifiedDate) values (\"1\",\"Hazard\", \"03/11/2016 11:12:03 AM\")"
Try to set a breakpoint on malloc_error_break.
Set your variables to nil after you release them.
Go carefully over all the calls to sqlite3_prepare_v2 instructions and make sure that the matching sqlite3_finalize is called for each one of them.
Add #synchroinized block to make it thread safe
The solution of this type of problems is , you may be missing below statement.
sqlite3_finalize(statement);
sqlite3_close(database);
after every
sqlite3_open()
sqlite3_prepare_v2()
we should always finalize the statement and close the database before return statement. Don't leave database open.
without finalizing statement and without closing Database if you try to again open it sqlite3_open()
or
sqlite3_prepare_v2()
this will result in EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) regarding database .
For example:
-(BOOL) insertDropdownValues:(NSString *)tableName
andId:(NSInteger) dID
name:(NSString*) name
modifiedDate:( NSString*) modifiedDate {
const char *dbpath = [databasePath UTF8String];
if (sqlite3_open(dbpath, &database) == SQLITE_OK)
{
NSString *insertSQL = [NSString stringWithFormat:#"insert into %# (%#, %#, %#) values (\"%ld\",\"%#\", \"%#\")",tableName,ID,NAME,MODIFIED_DATE, dID,name,modifiedDate];
const char *insert_stmt = [insertSQL UTF8String];
sqlite3_prepare_v2(database, insert_stmt,-1, &statement, NULL);
if (sqlite3_step(statement) == SQLITE_DONE)
{
NSLog(#"Inserted SuccessFull");
sqlite3_finalize(statement);
sqlite3_close(database);
return YES;
}
else {
NSLog(#"Insertion failed !");
sqlite3_finalize(statement);
sqlite3_close(database);
return NO;
}
}
sqlite3_reset(statement);
return NO;
}
I believe the problem might be due to accessing database in different threads at the same time. You can use thread lock to solve this problem. You can write the code for database operations inside #synchronized block for solving this problem. It can be implemented as follows.
#synchronized (self) {
// do the operation
}
Please let me know if it either works or doesn't work. Feel free to suggest edits.
Go for Fmdb for sqlite implementation https://github.com/ccgus/fmdb it's very easy to understand
i got 2 main question.
First one; is there any database browser for sqlite that i'm using in my iOS application?
The second question - big one - is a bit complicated.
-(BOOL)createDB{
NSString *docsDir;
NSArray *dirPaths;
// Get the documents directory
dirPaths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = dirPaths[0];
// Build the path to the database file
databasePath = [[NSString alloc] initWithString:
[docsDir stringByAppendingPathComponent: #"insider.db"]];
BOOL isSuccess = YES;
NSFileManager *filemgr = [NSFileManager defaultManager];
if ([filemgr fileExistsAtPath: databasePath ] == NO)
{
const char *dbpath = [databasePath UTF8String];
if (sqlite3_open(dbpath, &database) == SQLITE_OK)
{
char *errMsg;
const char *sql_stmt =
"create table if not exists userInfo (device_token text primary key, device_type text, carrier text, app_version text,os_version text, first_name text, last_name text,last_viewed_item text, last_added_item text, last_item_price_tag text, name_entered integer, login_screen integer, item_detailed_screen integer )";
if (sqlite3_exec(database, sql_stmt, NULL, NULL, &errMsg)
!= SQLITE_OK)
{
isSuccess = NO;
NSLog(#"Failed to create table");
}
sqlite3_close(database);
return isSuccess;
}
else {
isSuccess = NO;
NSLog(#"Failed to open/create database");
}
}
return isSuccess;
}
this is my createDB method that i call it my applicationDIdBecome active,
and i have several methods to get the items that will be saved to my database one of them ;
-(BOOL)getUserFirstName:(NSString *)firstName {
NSLog(#"User's first name is this %#",firstName);
const char *dbpath = [databasePath UTF8String];
if (sqlite3_open(dbpath, &database) == SQLITE_OK)
{
NSString *insertSQL = [NSString stringWithFormat:#"insert into userInfo (first_name) values (\"%#\")", firstName];
const char *insert_stmt = [insertSQL UTF8String];
sqlite3_prepare_v2(database, insert_stmt,-1, &statement, NULL);
if (sqlite3_step(statement) == SQLITE_DONE)
{
return YES;
}
else {
return NO;
}
sqlite3_reset(statement);
}
return NO;
}
i have a lot of these kind of methods ( for each columns in my create table statement)
What i want is this;
I have a backend that use mysql as database.
I need to make the last insertion row and make it JSON format so I can manipulate it and write it to my backend server.
How can i do this?
Thanks in advance.
Cheres.
Regarding SQLite tool, the database format is binary compatible across platforms. So you can just run the app in the simulator, browse to the folder with the simulator files (which varies depending upon Xcode version: it's ~/Library/Application Support/iPhone Simulator in Xcode versions prior to 6, it's ~/Library/Developer/CoreSimulator/Devices in Xcode 6) and open up the database. If your Library folder is hidden, you can unhide it from the Terminal command line, chflags nohidden ~/Library.
You can use the Mac OS X sqlite3 command line app. Many people use the free Firefox SQLite Manager add-on. I personally use Base, a relatively inexpensive SQLite tool. Use whatever you want.
Regarding retrieving results from SQLite and creating JSON from that, the easiest approach is to build a NSDictionary with the values, which you can then pass to NSJSONSerialization method dataWithJSONObject, which can build the JSON body of the request.
So, presumably you'll have some model structure that captures the content in your thirteen columns of this local table. So, insert that data in the local table, and then write a different routine that takes that model data and creates your request to POST this to the server.
You need to determine what your web service API will look like as well as how you want to POST it (e.g. NSURLConnection, NSURLSession (for iOS 7 and later), AFNetworking, etc.), but this seems well beyond the scope of this question.
A couple of unrelated observations:
If you use sqlite3_exec, supplying a pointer to a char * for the error message, remember that you're responsible for releasing that. Also, you might as well log that error message so you know why it failed:
if (sqlite3_exec(database, sql_stmt, NULL, NULL, &errMsg) != SQLITE_OK) {
isSuccess = NO;
NSLog(#"Failed to create table: %s", errMsg);
sqlite3_free(errMsg);
}
See the sqlite3_exec documentation for more information.
I would advise against using stringWithFormat to build your INSERT statements. Always use ? placeholders in your SQL, and then manually bind values.
Also, getUserFirstName is returning immediately if the statement was successful. So, it's not freeing the memory associated with the sqlite3_prepare_v2 (which you should do via sqlite3_finalize, not sqlite3_reset). It's also not closing the database.
-(BOOL)insertUserFirstName:(NSString *)firstName
{
BOOL success = YES;
const char *dbpath = [databasePath UTF8String];
if (sqlite3_open(dbpath, &database) == SQLITE_OK) {
const char *insert_stmt = "insert into userInfo (first_name) values (?)";
if (sqlite3_prepare_v2(database, insert_stmt, -1, &statement, NULL) != SQLITE_OK) {
NSLog(#"prepare failure: %s", sqlite3_errmsg(database));
sqlite3_close(database);
return NO;
}
if (sqlite3_bind_text(statement, 1, [firstName UTF8String], -1, NULL) != SQLITE_OK) {
success = NO;
NSLog(#"bind 1 failure: %s", sqlite3_errmsg(database));
} else if (sqlite3_step(statement) != SQLITE_DONE) {
success = NO;
NSLog(#"step failure: %s", sqlite3_errmsg(database));
}
sqlite3_finalize(statement);
statement = NULL;
sqlite3_close(database);
database = NULL;
}
return success;
}
Personally, I'd even advise against opening and closing the database with every SQLite call. But if you do, make sure you balance your open statements and close statements like above. You might want to also test to make sure that firstName is not nil (and if it is, call sqlite3_bind_null instead of sqlite3_bind_text).
It doesn't quite make sense to me that you're inserting only the user name. You'd presumably want to insert all of the column values at once.
When writing SQLite code, you might want to consider using FMDB, a thin Objective-C wrapper around SQLite C API, which greatly simplifies your life. When you tackle step 3, performing the sqlite3_bind_XXX calls for each of your thirteen columns, I think you'll really start to appreciate the power of something like FMDB.
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.
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.
I am trying to build a converter-like app between different measurement units. At the moment, I am using SQLite and I've got a table that includes different units alongside corresponding rates.
Inside my app I've got the following functions to retrieve rate values depending on selected units:
+(float)rateFrom:(NSString *)from to:(NSString *)to{
if (sqlite3_open([sqlPath UTF8String], &database) == SQLITE_OK) {
const char *sql = [[NSString stringWithFormat:#"SELECT %# FROM Units WHERE code = '%#'", to, from] UTF8String];
sqlite3_stmt *selectstmt;
if(sqlite3_prepare_v2(database, sql, -1, &selectstmt, NULL) == SQLITE_OK) {
return (float)sqlite3_column_double(selectstmt, 0);
}
}
else{
sqlite3_close(database);
}
return -1;
}
The problem is that the return rate is 0.00
I've tried the constructed query directly on my Terminal window and returns correct values
I've tried NSNumber and initWithFloat: with no success too.
Could anyone explain me what am I doing wrong here?
Thanks in advance.
You're never actually executing the query. sqlite3_prepare_v2 "compiles" the query, but doesn't actually run it. You're probably going to want to call sqlite3_step to actually execute the query. You're also not calling sqlite3_finalize on your compiled statement, so you're also leaking memory with this code. This might help:
+ (float)rateFrom:(NSString *)from to:(NSString *)to
{
float retVal = -1;
if (sqlite3_open([sqlPath UTF8String], &database) == SQLITE_OK) {
const char *sql = [[NSString stringWithFormat:#"SELECT %# FROM Units WHERE code = '%#'", to, from] UTF8String];
sqlite3_stmt *selectstmt;
if(sqlite3_prepare_v2(database, sql, -1, &selectstmt, NULL) == SQLITE_OK &&
sqlite3_step(selectstmt) == SQLITE_ROW) {
retVal = (float)sqlite3_column_double(selectstmt, 0);
}
if (selectstmt) {
sqlite3_finalize(selectstmt);
}
sqlite3_close(database);
database = NULL;
}
return retVal;
}
Also, while were at it, if the strings in to and from come from user input, you probably don't want to use them literally in queries, as this is vulnerable to SQL injection attacks.
You could also consider using FMDB which is a nice Objective-C wrapper for the SQLite API.
I am using the following code to read the rows from the Sqlite3 database.
if(sqlite3_open([[AppHelper getDatabasePath] UTF8String], &database) == SQLITE_OK)
{
const char *sqlStatement = "select * from customers";
sqlite3_stmt *compiledStatement;
if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK)
{
while(sqlite3_step(compiledStatement) == SQLITE_ROW)
{
NSString *firstName = [NSString stringWithUTF8String:(char *) sqlite3_column_text(compiledStatement, 1)];
NSLog(#"%#",firstName);
}
}
sqlite3_finalize(compiledStatement);
}
sqlite3_close(database);
The code does not go into the if(sqlite3_prepare_v2 ..... condition.
Can anyone tell me what I am doing wrong? I have the .sql file placed in my Documents Directory and it contains one record.
UPDATE 1:
I am running the app in the simulator and I placed my myDatabase.sql file inside the Supporting Files folder.
UPDATE 2:
The error is "No such table customers" but I can see that I have customers table in the database. I used FireFox Sqlite Manager to see the customers table.
The solution was to close/reset the simulator since the simulator was hanging on to the empty database. Once the simulator content was reset everyone started working again!
Oh, Yeah, when you change sqlite file, you always need to remove app and install again. Or if you want upgrade for new table, either you "Alter Table" old file or move all data to new file.