iOS SQLite: Insert statement fails, can't see why - ios

So I can create the database with no issue using the following statement for the first table:
const char *sqlStatement = "CREATE TABLE IF NOT EXISTS LIBRARY_SEARCH (SEARCH_ID INTEGER PRIMARY KEY AUTOINCREMENT, SEARCH_TERM TEXT)";
But when I go to insert a row it fails. This is how I insert the row.
NSString *insertStatement = [NSString stringWithFormat:#"INSERT INTO LIBRARY_SEARCH(SEARCH_TERM) VALUES (\"%#\")", myQuery] ;
Where myQuery is an NSString that holds the value of the searchBar.text in the app. It's a property and is of course synthesized.
So I then call this:
if ( sqlite3_exec(searchSessionDB, [insertStatement UTF8String], NULL, NULL, &insert_error) == SQLITE_OK)
Which fails. Can anyone see why?

Got it. Made a small typo in my open database statement.
For anyone who looks at this in the future, this is how the opening statement now looks (and it works fine!):
if(sqlite3_open(dbpath, &searchSessionDB) = SQLITE_OK){
Thanks to Robert Ryan for suggesting this NSLog:
NSLog(#"%s", sqlite3_errmsg(searchSessionDB));
And thanks Elpsk and H2CO3 for the wrapper/app bundle information.

I suggest you to use FMDB for query your database.
It's a wrapper to sqlite, easy to use and easy and all errors are explained better.

I'm sure you're trying to update an SQLite database located in your app bundle. You can't: you can't write to your app's own bundle. Copy the database file into the Documents or Library directory off of your app sandbox.
Other: if you're looking for an OO SQLite-wrapper, check out mine.

Related

How to use two sqlite database files in single iOS project with simultaneously fetch data from both Database

My requirement is to add two SQLite database files in my project. I know that is possible. But how can I access two database files simultaneously?
One more thing currently I am using FMDB to manipulate database operation.
Note : Both of my DB file sizes are near about 1GB each. so also want to know that if I use 2 DB file so is there any memory issue will occur in my application ?
Thanks.
Generally, there is rare requirement of 2 Database files in a single project specially in iOS where we have used only one DB.
But as per your requirement, we can use multiple Database files in a single project with multiple Database instances.
To Create 2 instance use below code for SQLite Native:
//Create Object of DataBase
static sqlite3 *databaseOne = nil;
static sqlite3 *databaseTwo = nil;
//Open Both Databases while we needed in our project
//Open First Database
sqlite3_open([path UTF8String], &databaseOne);
//Opne Second Database
sqlite3_open([path UTF8String], &databaseTwo);
//prepare for statement execution
sqlite3_prepare_v2(databaseOne, [sql UTF8String], -1, &stmt, NULL)
sqlite3_prepare_v2(databaseTwo, [sql UTF8String], -1, &stmt, NULL)
But make sure you will not mix these 2 objects while executing.
Note: Close both connections while your DB task is done.
To Create 2 instance with FMDB use below code:
//First DB
FMDatabase *dbOne = [FMDatabase databaseWithPath:<your first DB file Path>];
[dbOne open];
//Do Your DB task
[dbOne close];
//Second DB
FMDatabase *dbTwo = [FMDatabase databaseWithPath:<your second DB file Path>];
[dbTwo open];
//Do Your Second DB task
[dbTwo close];
Hope this will help you.

How to optimize SQLcipher performance?

I used SQLCipher to encrypt sqlite database in my app.everything is ok but my app runs slow during fetching database.i changed PRAGMA kdf_iter to 4000 and it's still slow.before encryption i don't have any problem.
-(NSError *) openDatabase {
NSError *error = nil;
NSString *databasePath = [self getDatabasePath];
const char *dbpath = [databasePath UTF8String];
int result = sqlite3_open_v2 (dbpath, &db , SQLITE_OPEN_READWRITE , NULL);
if (result == SQLITE_OK) {
sqlite3_exec(db, [#"PRAGMA kdf_iter = '4000';" UTF8String], NULL, NULL, NULL);
sqlite3_exec(db, [#"PRAGMA key = 'password'" UTF8String], NULL, NULL, NULL);
NSLog(#"Password is correct , Database is Activated");
sqlite3_exec(db, [#"PRAGMA cipher = 'aes-256-cfb';" UTF8String], NULL, NULL, NULL);
}
else {
NSLog(#"Incorrect password!");
}
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;
}
Finally i could optimize my SQLCipher performance with Nick Parker useful guidance.
As he said:
There are a few very important guidelines for optimal SQLCipher performance:
Do not repeatedly open and close connections, as key derivation is very expensive, by design. Frequent opening / closing of the database connection (e.g. for every query) is a very common cause of performance issues that can usually be easily resolved using a singleton database connection.
Use transactions to wrap insert / update / delete operations. Unless executed in a transaction scope, every operation will occur within it's own transaction which slows things down by several orders of magnitude
Ensure your data is normalized (i.e., using good practices for separation of data into multiple tables to eliminate redundancy). Unnecessary duplication of data leads to database bloat, which means more pages for SQLCipher to operate on
Ensure that any columns that are used for searches or join conditions are indexed. If you don't, SQLCipher will need to execute full database scans across large numbers of pages
Vacuum periodically to ensure databases are compact if you do large deletes, updates etc.
To diagnose performance problems with specific query statements, it may be helpful to run an explain query plan command against specific queries.
If you are uncertain of what queries are performing poorly, SQLCipher includes a pragma called cipher_profile that allows for profiling queries and their respective execution time in milliseconds.
This is the Reference Link
Many thanks to Nick Parker.
Also this blog was very useful for me.
Why are you using CFB mode (aes-256-cfb) instead of CBC?
I don't believe hardware encryption supports AES CFB so it probably will use software encryption which can easily be 500+ times slower.
From the docs:
SQLCipher uses aes-256-cbc as the default cipher and mode of operation. It is possible to change this, though not generally recommended

sqlite3_prepare_v2 returns OK on iPhone and iPad mini devices, but with error on normal iPad device

I'm facing a problem in this code: it's working ok on iPhone and iPad Mini, but it's giving the error message on the big iPad. Is there any reason?
Code:
- (sqlite3_stmt *)executeQuery:(char *)aQuery {
NSString *dbPath = [[NSUserDefaults standardUserDefaults] objectForKey:kDBPath];
sqlite3_stmt *statement = NULL;
sqlite3 *database;
if (sqlite3_open((char *)[dbPath UTF8String], &database) == SQLITE_OK) {
if (sqlite3_prepare_v2(database, aQuery, -1, &statement, NULL) != SQLITE_OK) {
//Here is the point: on iPad Mini and iPhone it works fine, but on the normal iPad it can't execute this query
NSLog(#"Error on SQL - (sqlite3_stmt *)executeWithReturn:(char *)aQuery");
statement = NULL;
}
} else {
NSLog(#"Error on Open database");
}
return statement;
}
Prints from LLDB:
(lldb) print (BOOL)[[NSFileManager defaultManager] isWritableFileAtPath:dbPath]
(BOOL) $0 = YES
(lldb) print (char *)aQuery
(char *) $1 = 0x1f0a1000 "select CodAplicacao, CodLocalidade, Nome, Desc, DataCriacao, Editando from Aplicacao_en"
Any ideas?
Regards!
EDIT:
It's getting the error "database disk image is malformed". I couldn't find in the internet how to fix it.
In your revised question, you inform us that sqlite3_errmsg is reporting "database disk image is malformed". This is a sign that your database has been corrupted. This can happen if your app crashed while doing some database operation at some previous date.
You can confirm this by running the following SQL:
PRAGMA integrity_check;
If you want to go through that process, you can copy the database from your device back to your Mac (using Xcode's Organizer and go to the "Devices" section) and then use your Mac SQLite database tool of choice (worst case, the command line sqlite3 program) and then run the previous PRAGMA command. You can even perform this programmatically (it returns a result set like a SELECT statement).
Thing is, we're fairly confident that it's going to report problems. The easy fix is to rebuild the database (e.g. delete the database and if you originally copied it from the bundle, do that again, if you programmatically created it, repeat that process).
If you're determined to try to recover user data from the database, you can contemplate following the process outlined in Sergei Dorogin's technical blog - SQLiteException "database disk image is malformed". He wrote that for a different platform, but the basic process is probably applicable here. Thing is, that unless you absolutely need to recover user data, that process is probably unnecessary.
So, in short, your database on that device is corrupted and you need to rebuild it. Just delete the app (in case your database is in the Documents folder, you need to delete app to empty out that folder) and reinstall the app and you should be good.
Change your log statement from:
NSLog(#"Error on SQL - (sqlite3_stmt *)executeWithReturn:(char *)aQuery");
to
NSLog(#"Error on SQL - %s: %s", __FUNCTION__, sqlite3_errmsg(database));
That should give you an informative error message that tells you what's wrong.
Generally, these sorts of problems result from version of the database on that particular device. It is extremely unlikely to be related to the device itself.
Perhaps your app created a blank database at some point and thus the table is not found. If you use Xcode's Organizer, go to the "Devices" section, and you can copy the app folders to your Mac and then examine the database on your Mac and see if you confirm that the database is as you expected it to be.

Using Sqlite3 VACUUM command on Core Data SQLite Persistent store

In our app, we are implementing sharing of partial Core Data SQLite database through network/email. In order to keep the file size small, I have implemented the below method to shrink the Core Data database.
- (void) shrinkDB
{
sqlite3 * database;
NSString * string = [shareStoreURL path];
const char * filename = [string cStringUsingEncoding:[NSString defaultCStringEncoding]];
char *errMsg;
if (sqlite3_open(filename, &database) == SQLITE_OK)
{
NSLog(#"Shrinking...");
if (sqlite3_exec(database, "VACUUM;", NULL, NULL, &errMsg) != SQLITE_OK)
{
NSLog(#"Failed execute VACUUM");
}
sqlite3_close(database);
}
}
QUESTION: The above code does shrink the database. But Apple says the implementation details of Core Data are subject to change any time. Do you think I would be safe using this method for foreseeable future? Or is there any other better solution?
The proper way to do this is by giving the NSSQLiteManualVacuumOption to the persistent store coordinator.
Snippet from documentation:
NSSQLiteManualVacuumOption
Option key to rebuild the store file,
forcing a database wide defragmentation when the store is added to the
coordinator. This invokes SQLite's VACUUM command. It is ignored by
stores other than the SQLite store. Available in OS X v10.6 and later.
Declared in NSPersistentStoreCoordinator.h.
See this: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/CoreDataFramework/Classes/NSPersistentStoreCoordinator_Class/NSPersistentStoreCoordinator.html
How Apple structures persistent data in an SQLite database is an implementation detail which is subject to change. However, the method by which SQLite manages deleted records is independent of Apple's implementation.
That being said, the process of vacuuming a SQLite database results in rebuilding the entire database, which may have negative effects if the sqlite file is in use by a CoreData NSPersistentStoreCoordinator.
In your case, it sounds like you want to vacuum after saving changes but before sending it via email. Using the NSSQLiteManualVacuumOption option appears to only vacuum the DB when the SQLite file is initially opened.
I'd either run the above code after the file is no longer associated with a NSPersistentStoreCoordinator or use the NSSQLiteManualVacuumOption then re-open and close the file before sending it via email.
Another option is to use an external SQLite tool, such as Base on OS X, to manually vacuum files. I've also used the Firefox SQLite manager extension in the past.

Using SQLite with iOS... very beginner program

I am attempting to write an application in which I have two text fields that I will be using for input. The input taken from the text fields are being initialized as a string. So what I want to accomplish is:
Create an SQLite database (file) that will be saved within the Xcode project (I'm not sure where it can be saved to, but I would need to be able to read it and write to it).
I then want to place the strings (from the text field inputs) into the SQLite table, which will only have two fields (for simplicity). I'm using a button to accept the input from the text fields, place the input into strings, and then put the value of the strings into a label(s).
I ultimately will want to place the strings from the text fields into the table, and then read the table to "grab" the strings and display them into the labels. All of this would be done by the click of a button.
I realize this is very specific, but I have been having a difficult time finding anything simple to accomplish this. I am very new to iOS development and I appreciate if anyone could be as specific and detailed as possible, or at least point me to some resources that would teach me how to accomplish this.
I am using Xcode v4.3.2 and I do not use Storyboard or ARC. I tried to be as detailed as possible, but as you can, my goal is really simple.
I'm tapped out and I appreciate all the help I can get.
Thanks!
-Matt
creating a database file: simply drop your data.sqlite file in your xcode project, as you drop any other class or image resource. Please note, that you will have to copy the file to writeable directory after app installation. take a look at createeditablecopyofdatabaseifneeded.
open your database with sqlite3_open with respect to your new database location.
query the database as you like with sqlite3_prepare_v2
a simple query snippet looks like:
NSString *querystring;
// create your statement
querystring= [NSString stringWithFormat:#"SELECT text1, text2 FROM datatable;"];
const char *sql = [querystring UTF8String];
NSString *text1 = nil;
NSString *text2 = nil;
if (sqlite3_prepare_v2(db, sql, -1, &statement, NULL)!=SQLITE_OK){
NSLog(#"sql problem occured with: %s", sql);
NSLog(#"%s", sqlite3_errmsg(db));
}
else
{
// you could handle multiple rows here
while (sqlite3_step(statement) == SQLITE_ROW) {
text1 = [NSString stringWithUTF8String:(char*)sqlite3_column_text(statement, 0)];
text2 = [NSString stringWithUTF8String:(char*)sqlite3_column_text(statement, 1)];
} // while
}
sqlite3_finalize(statement);
// go on with putting data where you want
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. The sample project uses SQL to create the tables and shows how you can change the schema over different versions of your app.

Resources