I am developing an iOS application in objective-c and i am using sqllite3 as local DB to hold some data.
There are multiple tables that I need to use but it not happening. If I try to create multiple tables in same DB {db path as DB1} then the second table is not created.
But if I try to create different tables in different db path, then it functions normally. ex : db path as DB1 for table T1 , DB2 for table T2 and so on ....
I am not able to understand Why is this happening!
Here is the code :
Statement to create table :
if(![[NSFileManager defaultManager] fileExistsAtPath:[self getEventDbFilePath]]) //if the file does not exist
{
[self createTable:[self getEventDbFilePath]];
}
db path function :
-(NSString *) getUserInfoDbFilePath
{
NSString * docsPath= NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSLog(#"docpath=%#",docsPath);
return [docsPath stringByAppendingPathComponent:#"userinfo.db"];
}
create table
-(int) createUserInfoTable:(NSString*) filePath
{
sqlite3* db = NULL;
int rc=0;
rc = sqlite3_open_v2([filePath cStringUsingEncoding:NSUTF8StringEncoding], &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
if (SQLITE_OK != rc)
{
sqlite3_close(db);
NSLog(#"Failed to open db connection");
}
else
{
char * query ="CREATE TABLE IF NOT EXISTS userinfo (id INTEGER PRIMARY KEY AUTOINCREMENT, selfuserid TEXT, profilepic TEXT, username TEXT, gender TEXT, profilelink TEXT )";
char * errMsg;
rc = sqlite3_exec(db, query,NULL,NULL,&errMsg);
if(SQLITE_OK != rc)
{
NSLog(#"Failed to create table rc:%d, msg=%s",rc,errMsg);
}
sqlite3_close(db);
}
return rc;
}
If I create a new table with similar function as above and different table name, with same db path, the second table will not be created.
Now for second table, if I provide a new path, the table gets created in thew db path.
Any suggestions would be great.
Related
I have created a table(REPORTDATA) in existing database. I am trying to insert the values in to table. But it is not inserted. I am using the following code.
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = dirPaths[0];
databasePath = [docsDir stringByAppendingPathComponent:#"Album.db"];
const char *dbpath = [databasePath UTF8String];
NSString *insertSQL;
if (sqlite3_open(dbpath, & albumDB) == SQLITE_OK)
{
int rowCount = [self GetArticlesCount];
rowCount += 1;
NSString *tempcount = [NSString stringWithFormat:#"%d", rowCount];
insertSQL = [NSString stringWithFormat: #"INSERT INTO REPORTDATA (Num, Json) VALUES ('%#','%#')", tempcount, tempcount];
char *errmsg=nil;
if(sqlite3_exec(albumDB, [insertSQL UTF8String], NULL, NULL, &errmsg)==SQLITE_OK)
{
}
else
{
NSLog(#"Error Message is =%s",errmsg);
}
}
sqlite3_close(albumDB);
Get number of rows in a table:
- (int) GetArticlesCount
{
int count = 0;
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = dirPaths[0];
databasePath = [docsDir stringByAppendingPathComponent:#"Album.db"];
if (sqlite3_open([self.databasePath UTF8String], &albumDB) == SQLITE_OK)
{
const char* sqlStatement = "SELECT COUNT(*) FROM REPORTDATA";
sqlite3_stmt *statement;
if( sqlite3_prepare_v2(albumDB, sqlStatement, -1, &statement, NULL) == SQLITE_OK )
{
//Loop through all the returned rows (should be just one)
while( sqlite3_step(statement) == SQLITE_ROW )
{
count = sqlite3_column_int(statement, 0);
}
}
else
{
NSLog( #"Failed from sqlite3_prepare_v2. Error is: %s", sqlite3_errmsg(albumDB) );
}
sqlite3_finalize(statement);
sqlite3_close(albumDB);
}
return count;
}
I am getting
Error Message is =(null).
I'd suggest examining the actual return value of sqlite3_exec:
int rc;
char *errmsg = NULL;
if ((rc = sqlite3_exec(albumDB, [insertSQL UTF8String], NULL, NULL, &errmsg)) == SQLITE_OK) {
NSLog(#"Insert succeeded");
} else {
NSLog(#"Insert failed: %s (%ld)", errmsg, (long)rc);
if (errmsg) sqlite3_free(errmsg);
}
You report that it returned 21, which is SQLITE_MISUSE. This is typical if you called the API functions in the wrong order (e.g. performing some SQL after the database was closed).
The GetArticlesCount method is reopening a database (which is already open), replacing the albumDB variable with a new sqlite3 * pointer. Then, GetArticlesCount is closing the database, and when you return to the first method, the albumDB pointer is now referencing a closed database handle. Thus subsequent SQL calls will generate SQLITE_MISUSE.
To avoid this problem, I would advise against having each function that performs SQL from opening and closing the database. Open the database once and then have all subsequent SQLite calls use that one sqlite3 * pointer.
Try to find error by using below code.
const char *sql = "INSERT INTO REPORTDATA (Num, Json) VALUES VALUES (?,?)"
if (sqlite3_prepare_v2(albumDB, sql, -1, &statement, NULL) != SQLITE_OK)
{
NSLog(#"Prepare failure: %s", sqlite3_errmsg(albumDB));
}
if (sqlite3_bind_text(statement, 1, [commentString UTF8String], -1, NULL) != SQLITE_OK)
{
NSLog(#"Bind 1 failure: %s", sqlite3_errmsg(albumDB));
}
if (sqlite3_step(statement) != SQLITE_DONE) {
NSLog(#"Step failure: %s", sqlite3_errmsg(albumDB));
}
sqlite3_finalize(statement);
As others suggested, I also recommend you to examine the actual error message.
9 of 10, I believe it is because the database file Album.db is not in the documents directory.
Try adding a breakpoint and check the databasePath value, open that directory and confirm the file is there.
If the file has 0 bytes of size, make sure to remove it and add the correct file to your Bundle Resources in:
Project -> Targets -> right target -> Build Phases -> Copy Bundle Resources
EDIT: In your case, you closed the database in GetArticlesCount and tried to use the database pointer after closing it. So I believe Rob's answer is the right solution.
I'm building an iOS app using storyboards.I have integrated sqlite database in my app.
I'm unable to insert data into the table,i'm getting this error:
Failed to open db connection
I have created twoo more table with the same code that is working fine but this sports is my third table in which i'm getting this error.
here is my code
//SQLlite database code used to get file path
-(NSString *) getSportsFilePath {
NSString * docsPath= NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES)[0];
return [docsPath stringByAppendingPathComponent:#"sportsdb.db"];
}
-(int) createTable:(NSString*) filePath {
sqlite3* db = NULL;
int rc=0;
rc = sqlite3_open_v2([filePath cStringUsingEncoding:NSUTF8StringEncoding], &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
if (SQLITE_OK != rc) {
sqlite3_close(db);
}
else {
char * query ="CREATE TABLE IF NOT EXISTS sportsselection (id INTEGER PRIMARY KEY AUTOINCREMENT, sportslist TEXT)";
char * errMsg;
rc = sqlite3_exec(db, query,NULL,NULL,&errMsg);
if (SQLITE_OK != rc) {
NSLog(#"Failed to create table rc:%d, msg=%s",rc,errMsg);
}
sqlite3_close(db);
}
return rc;
}
//SQLlite database code is used to insert data into the table
-(int) insert:(NSString *)filePath withName:(NSString *)sportslist {
sqlite3* db = NULL;
int rc=0;
rc = sqlite3_open_v2([filePath cStringUsingEncoding:NSUTF8StringEncoding], &db, SQLITE_OPEN_READWRITE , NULL);
if (SQLITE_OK != rc) {
sqlite3_close(db);
NSLog(#"Failed to open db connection");
}
else {
NSString * query = [NSString stringWithFormat:#"INSERT INTO sportsselection (sportslist) VALUES (\"%#\")",sportslist];
char * errMsg;
rc = sqlite3_exec(db, [query UTF8String] ,NULL,NULL,&errMsg);
if (SQLITE_OK != rc) {
NSLog(#"Failed to insert record rc:%d, msg=%s",rc,errMsg);
}
sqlite3_close(db);
}
return rc;
}
- (void)viewDidLoad {
if ( _sports) {
for (int j=0;j< _sports.count;j++) {
int rc= [self insert:[self getSportsFilePath] withName: _sports[j]];
if (rc != SQLITE_OK) {
NSLog(#"Failed to insert record");
}
else
NSLog(#"Record is added");
}
}
}
You should log the return code that sqlite3_open_v2 returned to determine the cause of the error.
One way you could get the error you describe would be if you failed to call createTable before you called insert. Usually you would check for the existence of the file, and call createTable if it's not found. You don't appear to call createTable anywhere.
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.
In Xcode I am trying to get FMDB to use SQLCipher to encrypt a database. In my project I already have a compiling version of SQLCipher which I already have proved is working via sqlite3 calls. I have a unit test that creates the database and 1 table then inserts a row. Everything works using FMDB except the database is still unencrypted.
-(id)initWithDatabaseFilename:(NSString*)filename{
NSString *databasePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]
stringByAppendingPathComponent: filename];
self.databasePath = databasePath;
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:databasePath]) {
database = [FMDatabase databaseWithPath:databasePath];
[database setLogsErrors:YES];
[database setTraceExecution:NO];
BOOL keyCheck = [database setKey:#"B!GSecret"];
NSLog(#"Database is encrypted: %d",keyCheck);
NSLog(#"database created");
} else {
NSLog(#"Didnt need to create database");
}
[self createTable];
return self;
}
-(void)createTable{
BOOL tableExists = [self.database tableExists:kTASKTableName];
if(!tableExists){
[database open];
[database executeUpdate:#"CREATE TABLE TEST(TEST_PK integer primary key autoincrement, TITLE text, OTHERTITLE text, TESTVAL text, COMPLETED integer, CREATION_DATE double)"];
[database close];
}
}
-(BOOL) addTasks:(NSArray*) tasks{
BOOL insertSuccess = NO;
if([self.databasePath isEqualToString:#""]){
NSLog(#"Database has not yet been initialized");
}
[database open];
for(TESTOBJ *to in tasks){
[database executeUpdate:#"insert into TEST(TITLE, OTHERTITLE, TESTVAL) VALUES (?,?,?)",
to.title,to.otherTitle,to.testVal,nil];
}
[database close];
return insertSuccess;
}
Sorted the issue by adding
[database setKey:#"B!GSecret"];
after each database open statement.
Since this is where google usually take people, if anyone is having problems encrypting a alreadt existing unencrypted database, I was able to figure out, after a painful research, how to do it and created a tutorial on:
http://www.guilmo.com/fmdb-with-sqlcipher-tutorial/
But the most important parts are, Opening your existing DB and attaching a new encrypted one. Then setting the key in your FMDB connections.
SQLCipher - Encrypting the database
// Import sqlite3.h in your AppDelegate
#import <sqlite3.h>
// Set the new encrypted database path to be in the Documents Folder
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDir = [documentPaths objectAtIndex:0];
NSString *ecDB = [documentDir stringByAppendingPathComponent:#"encrypted.sqlite"];
// SQL Query. NOTE THAT DATABASE IS THE FULL PATH NOT ONLY THE NAME
const char* sqlQ = [[NSString stringWithFormat:#"ATTACH DATABASE '%#' AS encrypted KEY 'secretKey';",ecDB] UTF8String];
sqlite3 *unencrypted_DB;
if (sqlite3_open([self.databasePath UTF8String], &unencrypted_DB) == SQLITE_OK) {
// Attach empty encrypted database to unencrypted database
sqlite3_exec(unencrypted_DB, sqlQ, NULL, NULL, NULL);
// export database
sqlite3_exec(unencrypted_DB, "SELECT sqlcipher_export('encrypted');", NULL, NULL, NULL);
// Detach encrypted database
sqlite3_exec(unencrypted_DB, "DETACH DATABASE encrypted;", NULL, NULL, NULL);
sqlite3_close(unencrypted_DB);
}
else {
sqlite3_close(unencrypted_DB);
NSAssert1(NO, #"Failed to open database with message '%s'.", sqlite3_errmsg(unencrypted_DB));
}
self.databasePath = [documentDir stringByAppendingPathComponent:#"encrypted.sqlite"];
Note that we set 2 parameters in SQL Query, the DATABASE and the KEY. The DATABASE should be the full path to the encrypted database you want to create, in this case, string ecDB, and the KEY parameter is the key that’s going to be use to ENCRYPT your database, so choose a strong one
Now on your FMDB functions, call [db setKey:#"strongKey"] after every time you open the db.
// FMDatabase Example
FMDatabase *db = [FMDatabase databaseWithPath:[self getDatabasePath]];
[db open];
[db setKey:#"secretKey"];
// FMDatabaseQueue Exmple
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:[self getDatabasePath]];
[queue inDatabase:^(FMDatabase *db) {
[db setKey:#"secretKey"];
...
}];
Let me know if you have any questions!
In addition to the excellent answer from #gmogames, see below a Swift example showing how to create from scratch an encrypted SQLCipher database, without having to convert an existing unencrypted database.
The trick is to create the file using appropriate sqlite3_open (or sqlite3_open_v2) command, to set the key using sqlite3_key and to populate the database before closing it (adding a table for example will be enough). If no table or content is created before closing the database, the file will be empty (size 0) and unencrypted.
You could then open the newly encrypted database with FMDB and set the key using customerDB.setKey(secretKey).
A common mistake is to try to create an encrypted database from scratch with FMDB and setting its key using customerDB.setKey(secretKey). Such procedure is suitable to open an existing encrypted SQLCipher database, but not to create one. A SQLCipher encrypted database can only be created using sqlite3 commands as below, and then opened with FMDB.
var rc: Int32
var db: OpaquePointer? = nil
var databasePath: String = "" // Initialize to your database path
var secretKey: String = "Thisisasecretkey"
rc = sqlite3_open_v2((databasePath as NSString).utf8String, &db, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX, nil)
if (rc != SQLITE_OK) {
let errmsg = String(cString: sqlite3_errmsg(db))
NSLog("Error opening database: \(errmsg)")
return
}
rc = sqlite3_key(db, secretKey, Int32(secretKey.utf8.count))
if (rc != SQLITE_OK) {
let errmsg = String(cString: sqlite3_errmsg(db))
NSLog("Error setting key: \(errmsg)")
}
// create a table to populate database and ensure it is saved in encrypted form
let sql = "CREATE TABLE IF NOT EXISTS TEST_TABLE (ID INTEGER PRIMARY KEY AUTOINCREMENT, INFO_TEXT TEXT default '');"
if (sqlite3_exec(db, sql, nil, nil, nil) != SQLITE_OK) {
print("error")
}
sqlite3_close(db)
print("encrypted db created")
// Now, we open the newly created encrypted database
customerDB = FMDatabase(path: databasePath)
if customerDB.open() {
// We set secret key
customerDB.setKey(secretKey)
// We perform needed operations on the database using FMDB requests
// ...
}
I createed a table in user.db, this file is in my xcode project folder.
create table user (uid INTEGER PRIMARY KEY ASC AUTOINCREMENT, username, mobile);
Next, in the app delegate .m file, I insert two rows, then I query select * from user and print it.
However, use statement and sqlite3_exec, the insertions are not successful, and there are no errors at all. user.db has 3 rows I inserted from sqlite command line. When I run this app, it prints 5 rows of data, 2 rows are inserted by me, but when I query in sqlite command line, there are still 3 rows, the two newer rows are not committed?
I also run sqlite3_get_autocommit, and find out autocommit=1, so what have I missed to cause this to happen?
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
[self.window makeKeyAndVisible];
NSString* file = [[NSBundle mainBundle] pathForResource:#"user" ofType:#"db"];
sqlite3* database = NULL;
if(sqlite3_open([file UTF8String], &database) == SQLITE_OK)
{
NSLog(#"database open successfully.");
// stmt
// int autocommit = sqlite3_get_autocommit(database);
sqlite3_stmt* stmt;
static char * sql = "insert into user (username, mobile) values('xxxx', '5555555');commit;";
int success = sqlite3_prepare_v2(database, sql, -1, &stmt, NULL);
if(success != SQLITE_OK)
{
NSLog(#"create stmt failed");
}
success = sqlite3_step(stmt);
if(success != SQLITE_OK)
{
NSLog(#"insert failed");
}
sqlite3_finalize(stmt);
sqlite3_exec(database, "begin;insert into user (username, mobile) values('yyyy', '666666');commit;", NULL, NULL, 0);
// insert
char *zErrMsg = 0;
int rc = sqlite3_exec(database, "select * from user", MyCallback, 0, &zErrMsg);
if(rc != SQLITE_OK)
{
fprintf(stderr, "SQL error: %s\n", zErrMsg);
}
}
sqlite3_close(database);
return YES;
}
You can't update databases stored in the app resources. Try copying the database to your app's Document or Library folder and then updating the database there.