Using an SQLCipher database in a persistent store - objective-c

I've gotten to the point where I can create an encrypted copy of my database with SQLCipher, now I'm trying to integrate it into my project. I've tried using the following code in my app delegate to unencrypt the database...
NSString *databasePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]
stringByAppendingPathComponent: #"encrypted.db"];
if (sqlite3_open([databasePath UTF8String], &db) == SQLITE_OK) {
const char* key = [#"BIGSecret" UTF8String];
sqlite3_key(db, key, strlen(key));
if (sqlite3_exec(db, (const char*) "SELECT count(*) FROM sqlite_master;", NULL, NULL, NULL) == SQLITE_OK) {
// password is correct, or, database has been initialized
NSLog(#"correct password");
} else {
// incorrect password!
NSLog(#"incorrect password");
}
Then later at the persistent store, I use the following code.
if (__persistentStoreCoordinator != nil) {
return __persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"encrypted.db"];
NSError *error = nil;
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
The first time I load the program after creating the database, I'll get a "correct password" log, but anytime after that I get an "incorrect password", but the database is still usable, which leads me to believe that the database is being overwritten or something.

CoreData doesn't work directly with SQLCipher, as it's using SQLite from the device directly. You could potentially take a look at the Encrypted Core Data project, (https://github.com/project-imas/encrypted-core-data) which uses SQLCipher and a custom NSIncrementalStore to provide similar features.

Related

How to re-use Core Data in Extension code in Objective-C? The managedObjectContext can't be created by UIApplicationDelegate as we use to do

The problem I meet is we can't get the managedObjectContext by this way:
[((MDAppDelegate*)appController) mainQueueContext] ;
Because the error message is:
'sharedApplication' is unavailable: not available on iOS (App
Extension) - Use view controller based solutions where appropriate
instead.
My QUESTION is:
Is there any existing example to help us connect to Core Data through Extension (Today/Watch)?
P.S. I have read following questions, none of them help. I just need an example:
App and Extension - Use Core data == error : sharedApplication()' is unavailable
WatchKit : 'sharedApplication' is unavailable: not available on iOS (App Extension) - Use view controller based solutions where appropriate instead
Use AppDelegate in today extension
We found and fix the problem now. The problem is caused by my misunderstanding of Core Data. We used to re-use tutorial's source code. When the system is work, we have no time to get deep understand of it.
The problem is we can't use Container App's managed object context. To fix it we use following code:
replace
[((MDAppDelegate*)appController) mainQueueContext] ;
to
[self mainQueueContext] ;
Then, add following...
- (NSManagedObjectContext *)mainQueueContext {
if (_mainQueueContext != nil) {
return _mainQueueContext;
}
_mainQueueContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_mainQueueContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
[_mainQueueContext setPersistentStoreCoordinator:self.persistentStoreCoordinator];
return _mainQueueContext; }
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL;
NSString *containerPath = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.<your project group ID>"].path;
NSString *sqlitePath = [NSString stringWithFormat:#"file://%#/%#", containerPath, #"<Your database file>.sqlite"];
storeURL = [NSURL URLWithString:sqlitePath];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSDictionary *options = #{
NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES
};
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:options
error:&error]) {
abort();
}
NSDictionary *fileAttributes = [NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSFileProtectionKey];
if (![[NSFileManager defaultManager] setAttributes:fileAttributes ofItemAtPath:storeURL.path error:&error]) {
// Handle error
}
return _persistentStoreCoordinator;
}
- (NSManagedObjectModel *)managedObjectModel
{
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
NSURL *modelURL;
NSString *containerPath = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.<your project group ID>"].path;
NSString *modelPath = [NSString stringWithFormat:#"file://%#/%#", containerPath, #"<Your database file>.momd"];
modelURL = [NSURL URLWithString:modelPath];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return _managedObjectModel;
}
Another Important issue is the Container App is still using the Core Data store located in application's Documents directory. The extension can't access it. So, we migrate the store to group shared folder using below lines:
NSString *directoryShared = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.<your project group ID>"].path;
NSString *storePathShared = [NSString stringWithFormat:#"file://%#/%#", directoryShared, #"<Your database file>.sqlite"];
NSURL *storeUrlShared = [NSURL URLWithString:storePathShared];
[_persistentStoreCoordinator migratePersistentStore:store
toURL:storeUrlShared
options:options
withType:NSSQLiteStoreType
error:&error];
if (error != nil) {
NSLog(#"Error when migration to groupd url %#, %#", error, [error userInfo]);
}
We just leave the original database alone, let the container app keep using it. there are some better way to achieve it. like:
Migrating NSPersistentStore from application sandbox to shared group container

sqlite_prepare_v2 does not return SQLITE_OK

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.

objective c sqlite no select works

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.

How to test whether row is added into database by unit testing?

I am passing the xml to my addRowToTheDatabase function . In this function i simply parse the xml file and and retrieved elements to the xml file. Now I am testing this application. How can I test whether the row added in the database or not in from unit test case ?
// I manipulated one of my queries real quick.. I'm assuming you're already connecting to a DB in SQLite, so much of this is un-needed. But this is the basic structure of a query.. as long as the DB resides in your application itself.
-(void) basicQuery
{
#try {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *theDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"databasename.sqlite"];
BOOL success = [fileManager fileExistsAtPath:theDBPath];
if (!success) {
NSLog(#"Failed to find database file '%#'.", theDBPath);
}
if (!(sqlite3_open([theDBPath UTF8String], &database) == SQLITE_OK)) {
NSLog(#"An error opening database, normally handle error here.");
}
const char *sql = "SELECT * FROM TABLE"; // your basic query
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(database, sql, -1, &statement, NULL) != SQLITE_OK){
NSLog(#"Error, failed to prepare statement, normally handle error here.");
}
while (sqlite3_step(statement) == SQLITE_ROW) {
NSString *value = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 0)];
NSLog(#"value = %#",value);
}
if(sqlite3_finalize(statement) != SQLITE_OK){
NSLog(#"Failed to finalize data statement, normally error handling here.");
}
if (sqlite3_close(database) != SQLITE_OK) {
NSLog(#"Failed to close database, normally error handling here.");
}
}
#catch (NSException *e) {
NSLog(#"An exception occurred: %#", [e reason]);
return nil;
}
}

xCode sqlite query

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));