SQLite - best practice for opening database - ios

I was in the situation where I've declared sqlite3 variable globally to store database conection and opening database every time without closing it. This lead to memory leaks on my app. I was able to fix this by closing database before open it.
sqlite3 *dbObj;
-(BOOL)openDB
{
//opening database
if (sqlite3_open(dbpath, &dbObj) == SQLITE_OK)
{
NSLog(#"Database opened successfully .....");
return YES;
}
else
{
return NO;
}
}
-(NSArray)getAllTablesInDatabse:(NSString*)database
{
sqlite3_close(dbObj);
[self openDB];
}
This is first way I fixed it.
There's another way I was able to do the same,to check dbObj is nil/NULL in openDB function instead of closing and opening each time. The second way :
-(BOOL)openDB
{
if(dbObj == nil || dbObj == NULL || dbObj = 0)
{
//opening database
if (sqlite3_open(dbpath, &dbObj) == SQLITE_OK)
{
NSLog(#"Database opened successfully .....");
return YES;
}
else
{
return NO;
}
}
else
{
return YES;
}
}
-(NSArray)getAllTablesInDatabse:(NSString*)database
{
[self openDB];
}
Which is best way from performance and other perspective ?

1) It's a good practice to close database after completion of desired task. For example you are creating one table then do like,
NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
self.databasePath = [[NSString alloc]initWithString:[docsDir stringByAppendingPathComponent:#"contacts.db"]];
sqlite3_stmt *statement;
if (sqlite3_open([self.databasePath UTF8String], &_contactDB) == SQLITE_OK) {
NSLog(#"database opened successfully");
NSString *query = #"create table if not exists Employee (id integer primary key, name text, surname text, roll text, salary integer)";
const char *queryStatement2 = [query UTF8String];
char *errorMessage;
if (sqlite3_exec(self.contactDB, queryStatement2, NULL, NULL, &errorMessage) == SQLITE_OK) {
if (!errorMessage) {
NSLog(#"table created sucessfully!");
}
else{
NSLog(#"error : %s",errorMessage);
}
}
}
sqlite3_close(self.contactDB);
So, You should close your database every time after use.
2) It is good to check before opening database that if it is open or not compare to close database every time before open.
3) If you close database every time then it will definitely open every time so there is no need to put any condition.

Related

Memory allocation keeps increasing while querying sqlite database

+ (BOOL) isExistingTicket:(NSString *)TicketID{
int numrows=0;
sqlite3 *database;
NSString *dbPath = [documentsDirectory stringByAppendingPathComponent:dbName];
if(sqlite3_open([dbPath UTF8String], &database) == SQLITE_OK)
{
NSString *sqlString = [NSString stringWithFormat:#"select count(*) from tickets WHERE ticket_id = '%#' LIMIT 1",TicketID];
sqlite3_stmt *selectStatement;
int returnValue = sqlite3_prepare_v2(database, [sqlString UTF8String], -1, &selectStatement, NULL);
if (returnValue == SQLITE_OK)
{
if(sqlite3_step(selectStatement) == SQLITE_ROW)
{
numrows= sqlite3_column_int(selectStatement, 0);
}
}
sqlite3_finalize(selectStatement);
}
else
sqlite3_close(database);
if (numrows > 0) {
return YES;
}else{
return NO;
}
}
Data set of around 200 comparing is using this query in my app. App memory usage keeps increasing when this function is called several times. It consumes more than 25Mb and does not decrease after that. Why is this happening? Can you suggest any optimizations?
This code closes the database only when the sqlite3_open call failed.
Move the sqlite3_close call into the first branch of the if.

implementing WAL in iOS?

The code I placed below works. So my scenario is I have the code below in a class. I use this class simply for merging tables and updating one table. When I call the object that this code lives in the app delegate for example, it works great! But when I call the same object after clicking a button in a tableview controller, I get a database lock error. So here's what I am wondering. After reading sqlite documentation WAL: http://www.sqlite.org/wal.html I am thinking that I cannot read and update concurrently to the sqlite db, right? I am unsure of a solution around this, so what would be a good suggestion around my problem. Remember, keep in mind this code works just fine in the app delegate, it does not work in my tableview controller when I call it using action upon clicking the button. Note: I SELECT data from the SQLITE database to display table cell names etc. Thanks ahead of time!
//Allocates a filemanager object. Ideally, this object is used for searching through the applications context
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL dbErr = NO;
//Boolean variable to tell if the database exists
BOOL error,mainDbError;
//Looks through all the databases. If there is a database that does not exist, the following error message will appear to the user. If all the databases exist on the system, the database opens respectively
error = [self checkAndOpenSyncDB];
mainDbError = [self checkAndOpenMainDB];
if (!error&&mainDbError) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Oops!"
message:#"We didn't mean for this to happen. Looks like there was a problem loading the sync database. Contact technical support for further assistance."
delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show];
}
else{
//This is I think to check when the last time the sync occured on the system.. not quite sure though1
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSLog(#"Erorr syncing the database: Code: %d, message: '%s'", error,sqlite3_errmsg(syncOpenHandle));
char *errorMessage;
int errorNum = 0;
//Atataching the sync db to the master db
NSString *attachSQL = [NSString stringWithFormat:#"ATTACH DATABASE \'%#\' AS sync_db", self->pathForSync];
NSLog(#"PATH FOR SYNC !!!!!!! %#", pathForSync);
NSLog(#"Here's the arratch string: %#", attachSQL);
//
if (sqlite3_exec(syncOpenHandle, [attachSQL UTF8String], NULL, NULL, &errorMessage) == SQLITE_OK) {
NSString *masterQuery = [NSString stringWithFormat:#"SELECT name FROM sqlite_master WHERE type='table';"];
const char *masterStmt = [masterQuery UTF8String];
sqlite3_stmt *statement;
BOOL loopErr;
loopErr = sqlite3_prepare_v2(syncOpenHandle, masterStmt, -1, &statement, NULL);
if (sqlite3_prepare_v2(syncOpenHandle, masterStmt, -1, &statement, NULL)==SQLITE_OK) {
while (sqlite3_step(statement) == SQLITE_ROW) {
NSString * currentTable = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 0)];
NSLog(#"Here's the current table: %#",currentTable);
//This is where the magic happens. If there are any keys matching the database, it will update them. If there are no current keys in the database, the query will insert them.
if ([currentTable isEqualToString:#"USER_DATA"] == NO && [currentTable isEqualToString:#"USER_ACTIVITY"]== NO && [currentTable isEqualToString:#"USER_ITINERARY"] == NO) {
NSString *tblUpdate = [NSString stringWithFormat:#"INSERT or REPLACE INTO main.%# SELECT * FROM sync_db.%#;",currentTable, currentTable];
const char *updateStmt = [tblUpdate UTF8String];
//sqlite3_busy_timeout (mainOpenHandle, 60000);
bool update;
update = sqlite3_exec(syncOpenHandle, updateStmt, NULL, NULL, &errorMessage)== SQLITE_OK;
NSLog(#"Error sync ... '%s'", sqlite3_errmsg(syncOpenHandle));
if (sqlite3_exec(syncOpenHandle, updateStmt, NULL, NULL, &errorMessage)== SQLITE_OK) {
NSLog(#"Error sync ... '%s'", sqlite3_errmsg(syncOpenHandle));
if (errorNum == 1) {
//A database reset is needded
//self->isResetDataBase = YES;
}
dbErr = YES;
}
}
}
NSLog(#"Error sync ... '%s'", sqlite3_errmsg(syncOpenHandle));
}
NSLog(#"Here's the error num %d", errorNum);
NSLog(#"Erorr syncing the database: Code: %d, message: '%s'", error,sqlite3_errmsg(syncOpenHandle));
NSLog(#"Error sync ... '%s'", sqlite3_errmsg(syncOpenHandle));
sqlite3_finalize(statement);
//Detaching the database from the mainDB
NSString *detachSQL = [NSString stringWithFormat:#"DETACH DATABASE sync_db"]; // reference sync db
if ((errorNum = sqlite3_exec(syncOpenHandle, [detachSQL UTF8String], NULL, NULL, &errorMessage))!= SQLITE_OK) {
NSLog(#"Detatched syncDb Failed. ErrorMessage = %s ", errorMessage);
}
}
}
NSLog(#"Error sync ... '%s'", sqlite3_errmsg(syncOpenHandle));
//Closing the database when finished.
if (syncOpenHandle!=nil) {
sqlite3_close(syncOpenHandle);
NSError *err;
int success = [fileManager fileExistsAtPath:pathForSync];
if (success) {
[[NSFileManager defaultManager]removeItemAtPath:pathForSync error:&err];
}
}

Sqlite - while (sqlite3_step(statement) == SQLITE_ROW) is not executing

I am trying to get the highest value in the Points column in my DB, however the sqlite_step statement never get executed.
This is the method with this in it.
+(NSMutableArray*)getMax {
[self databaseInit];
highScore *newMax = [[highScore alloc]init];
NSMutableArray *maxPointsArray = [[NSMutableArray alloc]init];
if (sqlite3_open(dbpath,&peopleDB)==SQLITE_OK)
{
NSString *selectMaxSQL = [NSString stringWithFormat:#"SELECT MAX POINTS FROM PEOPLE"];
if(sqlite3_prepare_v2(peopleDB, [selectMaxSQL UTF8String],-1,&statement, NULL)==SQLITE_OK){
while (sqlite3_step(statement) == SQLITE_ROW){
newMax.max = sqlite3_column_int(statement, 0);
}
[maxPointsArray addObject:newMax];
}
}
sqlite3_finalize(statement);
sqlite3_close(peopleDB);
return maxPointsArray;
}
If it makes a difference the databaseInit method is -
+(void)databaseInit {
//get documents directory
dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = dirPaths [0];
//create path to database file
databasePath = [[NSString alloc]initWithString:[docsDir stringByAppendingPathComponent:#"people.db"]];
dbpath = [databasePath UTF8String];
}
Any help with this would be hugely appreciated, let me know if any more code is needed.
It is not executed because your query is wrong.
Assuming that points is a field of your people table, the query should be:
SELECT MAX(points) FROM people
You have the immediate answer, but the long term solution is to log sqlite3_errmsg if any SQLite functions fail. The way your code is written, you have no way of knowing which of the calls failed and why.
Thus, you may want:
+(NSMutableArray*)getMax {
int rc;
[self databaseInit];
highScore *newMax = [[highScore alloc]init];
NSMutableArray *maxPointsArray = [[NSMutableArray alloc]init];
if ((rc = sqlite3_open(dbpath, &peopleDB)) == SQLITE_OK) {
const char *selectMaxSQL = "SELECT MAX(POINTS) FROM PEOPLE";
if(sqlite3_prepare_v2(peopleDB, selectMaxSQL, -1, &statement, NULL)==SQLITE_OK){
if ((rc = sqlite3_step(statement)) == SQLITE_ROW){
newMax.max = sqlite3_column_int(statement, 0);
[maxPointsArray addObject:newMax];
} else {
if (rc == SQLITE_DONE) {
NSLog(#"No record returned");
} else {
NSLog(#"Step failed %s (%d)", sqlite3_errmsg(peopleDB), rc);
}
}
sqlite3_finalize(statement);
} else {
NSLog(#"Prepare failed %s (%d)", sqlite3_errmsg(peopleDB), rc);
}
} else {
NSLog(#"Open failed %d", rc);
}
sqlite3_close(peopleDB);
return maxPointsArray;
}
If you had done this with your original SQL, it would have reported precisely where in the SQL it encountered the error. Furthermore, if you have any problems in the future, this sort of defensive programming will help diagnose problems more quickly.
It's also worth noting that it's generally recommended not to open and close the database with every database call, but rather to open the database only once. It's more efficient and people have reported problems when constantly opening and closing databases.

error: database is locked in sqlite

//create database here
-(void) createDataBase
{
docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0];
fullPath = [docPath stringByAppendingPathComponent:#"Recording.sqlite"];
sqlite3_open([fullPath UTF8String], &dbForEmf);
}
// create table
-(void) createTable
{
if (sqlite3_open([fullPath UTF8String], &dbForE) == SQLITE_OK)
{
const char* createQuery = "CREATE TABLE IF NOT EXISTS EEE(E_ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, E_NAME TEXT,E_READING TEXT,E_DATE TEXT,E_SIZE TEXT,E_LABLE TEXT)";
int errorCode = sqlite3_prepare(dbForE, createQuery, -1, &prepareStmt, NULL);
if (errorCode == 0) {
sqlite3_step(prepareStmt);
}
else
{
}
sqlite3_finalize(prepareStmt);
sqlite3_close(dbForE);
}
else
{
NSLog(#"data base not Open");
}
}
// insert data here
-(void) insertData
{
if (sqlite3_open([fullPath UTF8String], &dbForE)==SQLITE_OK)
{
NSString *insertSql = [NSString stringWithFormat:#"INSERT INTO EEE(E_NAME,E_READING,E_DATE,E_SIZE,E_LABLE) VALUES('%#','%#','%#','%#','%#')",_referenceObject.lastFilePath,_referenceObject.fileString,_referenceObject.dateString,_referenceObject.fileSizeString,_referenceObject.titleOfButtonString];
const char *insert_stmt=[insertSql UTF8String];
sqlite3_prepare_v2(dbForE,insert_stmt,-1,&prepareStmt,NULL);
sqlite3_reset(prepareStmt);
if (sqlite3_step(prepareStmt)==SQLITE_DONE)
{
NSLog(#"inserted the values in table");
}
else
{
NSLog(#" not inserted the values in table");
NSLog(#"error: %s", sqlite3_errmsg(dbForE));
}
sqlite3_finalize(prepareStmt);
sqlite3_close(dbForE);
}
}
hai data is insert on first viewController when ever i goto second viewController and come back to first viewController the data is not inserted error displays database is locked...
check if you have leave the database open in second ViewController or anywhere else.
in createDataBase you have opened your database and it was not closed and you again tried to open this hence your database get locked first you should close database in createdatabase function.

Checking if a table exists and if it does not exist, create it... iOS/SQLite

I currently have a table that exists which I'm able to pull data from. What I would like to do is check if the table already exists in the bundle, and if it does not, I want to create the table and save it to the bundle (meaning the path would be in the main bundle). I want the database to be checked and created at the top of the setInput method. I've been scouring SO for something similar to this, but I haven't come up with anything yet. Any help is very appreciated. Here is my code:
-(IBAction)setInput:(id)sender
{
NSString *strStoreNumber;
NSString *strRegNumber;
strStoreNumber = StoreNumber.text;
strRegNumber = RegNumber.text;
lblStoreNumber.text = strStoreNumber;
lblRegNumber.text = strRegNumber;
NSString* databasePath = [[NSBundle mainBundle] pathForResource:#"tblStore" ofType:#"sqlite"];
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK)
{
NSLog(#"Opened sqlite database at %#", databasePath);
sqlite3_exec(database, "CREATE TABLE IF NOT EXISTS tblStore (ID INTEGER PRIMARY KEY AUTOINCREMENT, Message TEXT)", NULL, NULL, NULL);
//...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, strReg FROM tblStore WHERE strStore = %# AND strReg = %#;", strStoreNumber, strRegNumber];
const char *sql = [querystring UTF8String];
NSString *szStore = nil;
NSString *szReg = nil;
sqlite3_stmt *statement = nil;
if (sqlite3_prepare_v2(database, sql, -1, &statement, 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(statement) == SQLITE_ROW)
{
szStore = [NSString stringWithUTF8String:(char*)sqlite3_column_text(statement, 0)];
szReg = [NSString stringWithUTF8String:(char*)sqlite3_column_text(statement, 1)];
}
}
sqlite3_finalize(statement);
lblStoreNumber.text = szStore;
lblRegNumber.text = szReg;
//
}
I'm still quite new to iOS and SQLite, so if I did not provide an accurate enough description of what I'm trying to do, let me know and I'll try to be more specific. Thanks!
A quick search for "iphone sql create table if not exists" gave this as the top result.
This part of the SQL is probably what you are looking for:
CREATE TABLE IF NOT EXISTS tableName( ... )
It creates a table if it does not already exist.
Create table using sqlite swift
func createTable(_ tableName:String) {
sqlStatement = "CREATE TABLE \(tableName)(Id TEXT,Details BLOB,Type TEXT)"
if sqlite3_prepare_v2(database, sqlStatement,-1, &compiledStatement, nil) == SQLITE_OK {
if sqlite3_step(compiledStatement) == SQLITE_DONE {
print("table created")
}
else {
print("table could not be created")
}
}
else {
print("Create table statement could not be prepared")
}
sqlite3_finalize(compiledStatement)
}

Resources