When retrieving objects from an NSMutableArray in cocoa-touch is the below code ok? Should I be allocating([alloc]) new Page objects each time or is just pointing to it alright? Do I need to do anything to the Page *pageObj after, such as set it to nil?
const char *sql = "insert into Page(Book_ID, Page_Num, Page_Text) Values(?, ?, ?)";
for (i = 0; i < ([[self pagesArray] count] - 1); i++) {
if(addStmt == nil) {
if(sqlite3_prepare_v2(database, sql, -1, &addStmt, NULL) != SQLITE_OK) {
NSAssert1(0, #"Error while creating add statement. '%s'", sqlite3_errmsg(database));
}
}
Page *pageObj = [[self pagesArray] objectAtIndex:i];
if(pageObj.isNew) {
sqlite3_bind_int(addStmt, 1, self.book_ID);
sqlite3_bind_int(addStmt, 2, pageObj.page_Number);
sqlite3_bind_text(addStmt, 3, [[pageObj page_Text] UTF8String], -1, SQLITE_TRANSIENT);
if(SQLITE_DONE != sqlite3_step(addStmt)) {
NSAssert1(0, #"Error while inserting data. '%s'", sqlite3_errmsg(database));
}
NSLog(#"Inserted Page: %i into DB. Page text: %#", pageObj.page_Number, pageObj.page_Text);
}
//Reset the add statement.
sqlite3_reset(addStmt);
}
Thanks. I also understand this should probably be in a transaction but I didn't quite get that working just yet.
The way you're declaring a pointer is correct. You don't need alloc, since that creates a new object, when you want to refer to an existing object in the array. You would want to retain it if you were going to keep the reference outside of that method, but since you're only using it temporarily it's fine not to.
The actual pointer variable will be destroyed and recreated every trip to the loop, so there's no need to set it to nil. Even if you declared the variable outside the loop, simply assigning it to a new object is fine. The only time you'd set it to nil is when you're releasing the object stored in the pointer (or the object may be released elsewhere). If you didn't set it to nil in that case, the pointer would refer to an invalid memory location after the object is dealloced, usually causing a crash.
One bug I can see though, you're skipping the last element in your array in your for loop by subtracting 1 from the count.
Aside from the previously mentioned count error, it looks good.
As far as transactions go, I highly recommend wrapping this write loop in one. It will greatly increase your write performance and I found that it helps with memory usage as well. I use the following class method to begin a transaction:
+ (BOOL)beginTransactionWithDatabase:(sqlite3 *)database;
{
const char *sql1 = "BEGIN EXCLUSIVE TRANSACTION";
sqlite3_stmt *begin_statement;
if (sqlite3_prepare_v2(database, sql1, -1, &begin_statement, NULL) != SQLITE_OK)
{
return NO;
}
if (sqlite3_step(begin_statement) != SQLITE_DONE)
{
return NO;
}
sqlite3_finalize(begin_statement);
return YES;
}
and this one to end a transaction:
+ (BOOL)endTransactionWithDatabase:(sqlite3 *)database;
{
const char *sql2 = "COMMIT TRANSACTION";
sqlite3_stmt *commit_statement;
if (sqlite3_prepare_v2(database, sql2, -1, &commit_statement, NULL) != SQLITE_OK)
{
return NO;
}
if (sqlite3_step(commit_statement) != SQLITE_DONE)
{
return NO;
}
sqlite3_finalize(commit_statement);
return YES;
}
I should probably store the SQL statements for later reuse, but these transaction statements are called much less frequently than my other queries.
Of course not. You have allocated it before and are just referencing the same object. No need to reallocate. Also you don't need to set it to nil.
Related
I am working on a Keyboard extension on iOS using Objective C where I am using SQLite. I need to understand a few concepts about SQLite which I didn't get by googling. Let me divide the question in parts.
PART: 1
I have come across a PRAGMA in SQLite called PRAGMA cache_size = pages;
The default size here is 2000 pages. Comparing with the default, according to my understanding,
cache_size > 2000 means more memory usage, more speed (than default).
cache_size < 2000 means less memory usage, less speed (than default).
Am I correct here?
PART: 2
I am trying to change the cache_size in the following way,
if (sqlite3_exec(sqlite3Database, [#"PRAGMA CACHE_SIZE=50;" UTF8String], NULL, NULL, NULL) == SQLITE_OK) {
NSLog(#"Successfully changed cache size");
}
else
NSLog(#"Error: failed to set cache size with message %s.", sqlite3_errmsg(sqlite3Database));
I am using this after opening the database. The following code shows it,
-(void)runQuery:(const char *)query isQueryExecutable:(BOOL)queryExecutable{
// Create a sqlite object.
sqlite3 *sqlite3Database;
// Set the database file path.
NSString *databasePath = [self getDbFilePath];
// Initialize the results array.
if (self.arrResults != nil) {
[self.arrResults removeAllObjects];
self.arrResults = nil;
}
self.arrResults = [[NSMutableArray alloc] init];
// Open the database.
BOOL openDatabaseResult = sqlite3_open([databasePath UTF8String], &sqlite3Database);
if(openDatabaseResult == SQLITE_OK) {
if (sqlite3_exec(sqlite3Database, [#"PRAGMA CACHE_SIZE=50;" UTF8String], NULL, NULL, NULL) == SQLITE_OK) {
NSLog(#"Successfully changed cache size");
}
else
NSLog(#"Error: failed to set cache size with message %s.", sqlite3_errmsg(sqlite3Database));
// Declare a sqlite3_stmt object in which will be stored the query after having been compiled into a SQLite statement.
sqlite3_stmt *compiledStatement;
// Load all data from database to memory.
BOOL prepareStatementResult = sqlite3_prepare_v2(sqlite3Database, query, -1, &compiledStatement, NULL);
if(prepareStatementResult == SQLITE_OK) {
// Check if the query is non-executable.
if (!queryExecutable){
// In this case data must be loaded from the database.
// Declare an array to keep the data for each fetched row.
NSMutableArray *arrDataRow;
// Loop through the results and add them to the results array row by row.
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
// Initialize the mutable array that will contain the data of a fetched row.
arrDataRow = [[NSMutableArray alloc] init];
// Get the total number of columns.
int totalColumns = sqlite3_column_count(compiledStatement);
// Go through all columns and fetch each column data.
for (int i=0; i<totalColumns; i++){
// Convert the column data to text (characters).
char *dbDataAsChars = (char *)sqlite3_column_text(compiledStatement, i);
// If there are contents in the currenct column (field) then add them to the current row array.
if (dbDataAsChars != NULL) {
// Convert the characters to string.
[arrDataRow addObject:[NSString stringWithUTF8String:dbDataAsChars]];
}
}
// Store each fetched data row in the results array, but first check if there is actually data.
if (arrDataRow.count > 0) {
[self.arrResults addObject:arrDataRow];
}
}
}
else {
// This is the case of an executable query (insert, update, ...).
// Execute the query.
int executeQueryResults = sqlite3_step(compiledStatement);
if (executeQueryResults == SQLITE_DONE) {
// Keep the affected rows.
self.affectedRows = sqlite3_changes(sqlite3Database);
// Keep the last inserted row ID.
self.lastInsertedRowID = sqlite3_last_insert_rowid(sqlite3Database);
}
else {
// If could not execute the query show the error message on the debugger.
NSLog(#"DB Error: %s", sqlite3_errmsg(sqlite3Database));
}
}
}
else {
// In the database cannot be opened then show the error message on the debugger.
NSLog(#"db error: %s", sqlite3_errmsg(sqlite3Database));
}
// Release the compiled statement from memory.
sqlite3_finalize(compiledStatement);
}
// Close the database.
sqlite3_close(sqlite3Database);
}
But, when I call the method, sqlite3_exec(sqlite3Database, [#"PRAGMA CACHE_SIZE=50;" UTF8String], NULL, NULL, NULL), it always gives SQLITE_OK no matter what I do.
For example, if I do sqlite3_exec(sqlite3Database, [#"abcd bla bla" UTF8String], NULL, NULL, NULL), it returns SQLITE_OK!!
Why is that so?
PART: 3
I want to increase the speed of execution of my queries, but at the same time don't want to use IMDB as the size of the database is huge.
So is PRAGMA page_size = bytes; make any relevance in this case? If yes, then how to do it in Objective C?
Any help is appreciated. Thanks and regards.
So basically I have an app that will provide tasks based on selected project. Both projects and tasks are stored in a SQLite database.
To get the current project id I compare the selected project (_selectedProject) to my database, to get the ID. This is done in my getSelectedProjectId method. However, when running this method in the getTasks method, the Where-statement wont work at all. If I don't run the getSelectedProjectId method first, it works just fine. Am I forgetting to release something? Or is it something else? Any ideas?
I'm pretty new to both SQLite and Objective C, so this may not be a complex issue. I have made sure the getSelectedProjectId method returns the correct project ID. I have also made sure the query that is run in the getTasks method is correct, and when running it through my terminal it returns a number of rows. In the app it returns nothing, provided I'm running the getSelectedProjectId somewhere in that method first.
This is the method that fetches the tasks:
- (void)getTasks
{
[self openDB];
sqlite3_stmt *statement;
int projectId = [self getSelectedProjectId];
NSString *query = [NSString stringWithFormat:#"SELECT * FROM tasks WHERE project_id=%i", projectId];
const char *query_statement = [query UTF8String];
sqlite3_prepare_v2(_contactDB, query_statement, -1, &statement, NULL);
while (sqlite3_step(statement) == SQLITE_ROW)
{
// I add the task title to my array of tasks here.
}
sqlite3_finalize(statement);
sqlite3_close(_contactDB);
}
And this is the method that gets the correct project id from the database:
- (int)getSelectedProjectId
{
sqlite3_stmt *statement;
NSString *query = [[NSString alloc]
initWithFormat:#"SELECT id FROM projects WHERE title=\"%#\" LIMIT 0,1",
_selectedProject];
int rowId = 0;
const char *query_statement = [query UTF8String];
[self openDB];
sqlite3_prepare_v2(_contactDB, query_statement, -1, &statement, NULL);
if (sqlite3_step(statement) == SQLITE_ROW)
{
rowId = sqlite3_column_int(statement, 0);
}
sqlite3_finalize(statement);
sqlite3_close(_contactDB);
return rowId;
}
The problem occured because I closed the DB connection in my getSelectedProjectId-method. I'm now leaving my DB open instead, works like a charm.
I am currently developing on Objective-C using sqlite3. The following code seems like a bad memory access.
-(sqlite3_stmt *)PrepareStmt:(NSString *)query
{
//...
const char *query_stmt = [query UTF8String];
sqlite3_stmt *stmt = nil;
int retval = 0;
if ((retval = sqlite3_prepare_v2(db, query_stmt, -1, &stmt, nil)) == SQLITE_OK)
{
return stmt;
}
else
{
//Error handling...
}
}
- (void)SomeFunc
{
NSString *query = #""; //Assume valid SQL statement
sqlite3_stmt *stmt = [self PrepareStmt:query];
//Use stmt, like step, etc.
sqlite3_finalize(stmt);
}
The sqlite3_stmt in PrepareStmt is set to nil and it will be an out parameter from sqlite3_prepare_v2(). The memory should be allocated in that function. Therefore, it should be released by calling sqlite3_finalize().
My question here is that if we return sqlite3_stmt from PrepareStmt(), it should be still valid right? The local pointer in PrepareStmt() is already popped off the stack, but the memory allocated by sqlite3_prepare_v2() should still be valid.
Is this thinking valid? Or do I need to pass in an address of an pointer to PrepareStmt()?
Thank you!
Yes it's valid in this case. But note that sqlite3_finalize() isn't just about releasing memory (i.e. dealloc). It also sends a message to the DB to tell it to drop it's precompiled SQL statement, etc...
Here is my second stupid Noob problem. I am trying to do a simple Delete and I keep blowing up on the prepare step. I already have other Deletes, Inserts, Updates and Selects working. I am sure it is something simple. I appreciate your help.
+ (void)flushTodaysWorkouts {
sqlite3_stmt *statement = nil;
//open the database
if (sqlite3_open([[BIUtility getDBPath] UTF8String], &database) != SQLITE_OK) {
sqlite3_close(database);
NSAssert(0, #"Failed to opendatabase");
}
NSArray *woList = [self todaysScheduledWorkouts];
for (Workout *wo in woList) {
NSInteger woID = wo.woInstanceID;
if(statement == nil) {
const char *sql = "DELETE FROM IWORKOUT WHERE WOINSTANCEID = ?";
if(sqlite3_prepare_v2(database, sql, -1, &statement, NULL) != SQLITE_OK)
NSAssert1(0, #"Error while creating delete statement. '%s'", sqlite3_errmsg(database));
}
//When binding parameters, index starts from 1 and not zero.
sqlite3_bind_int(statement, 1, woID);
if (SQLITE_DONE != sqlite3_step(statement))
NSAssert1(0, #"Error while deleting. '%s'", sqlite3_errmsg(database));
sqlite3_finalize(statement);
}
if(database) sqlite3_close(database);
}
I solved this, but I don't know why. It did not want to let me do the deletes inside of the for loop. I put the delete in it's own method and called it from a for loop and it worked great. Weird.
I have just come across this error message, and it turned out to be two threads executing queries simultaneously. Just rearranging the code was enough to change the timing so that it would go away.
Adding locks to prevent multiple accesses to the database resolved the problem
I've just encountered a similar one.
It turned out I forgot to reset a prepared and bound statement for DELETE, which would run into trouble the second time it got invoked.
For the case in this question, perhaps the sqlite3_finalize should be replaced with a splite3_reset to reuse in a loop block. And the statement should be finalized only after the loop before invoking sqlite3_close.
I have a trouble in my database - somehow at some point it appears that a database is being closed even before it was opened.
If a database was not opened I am using the following statement: if (!database) then break;
when *database is being set to nil (database = nil) when it was not opened.
Am I doing it in a right way? Or there is some other error in my code?
Here is my code:
-(BOOL) loadDB: (NSString*) dbPath {
//Database was opened before
if (database) {
sqlite3_close(database);
database = nil;
}
//Opening database
if (sqlite3_open([dbPath UTF8String], &database) != SQLITE_OK)
{
database = nil;
return FALSE;
}
return TRUE;
}
The code is being called for multiple times and at some time it throws an exception. Why may this happen?
When I am using debugger to see where a problem occured, it shows me: sqlite3_close(database);
Thank you in advance.
Try setting the database pointer to NULL instead of nil.
-(BOOL) loadDB: (NSString*) dbPath {
BOOL retVal = TRUE
//Database was opened before
if (database) {
sqlite3_close(database);
database = NULL; // <-- NULL not nil
}
//Opening database
if (sqlite3_open([dbPath UTF8String], &database) != SQLITE_OK) {
database = NULL; // <-- NULL not nil
retVal = FALSE;
}
return retVal;
}
In Objective C nil is a nil pointer on an object. But database is a pointer to a struct, so use NULL instead.
Your close brace is too early (but I don't think that's the problem because it wouldn't compile.
As a style note, please only return ONE time from a function (and make that at the bottom). Create a BOOL, initialize it to TRUE and change it's value to FALSE when necessary.