sqlite fetch statement - objective-c

Good day,
I need to fetch rows from my sqlite table, but I need to pass multiple parameters. This is my statement that does not work.
SELECT * FROM messages WHERE currentuser=\"%#\" AND (belongstouser=\"%#\" OR mymsgforuser=\"%#\") ORDER BY ID ASC
I need it to first check for the currentuser match, then out of those matches to check for either the belongstouser or mymsgforuser matches. Is it possible to nest a sqlite statement in this fashion? I tried removing the parenthesis and that didn't work either. I also searched the sqlite documentation and could not find a solution.

I can see wrong SQL syntax. string constants must be quoted with single quotes (') instead of (")
And as rmaddy said, you'd better avoid stringWithFormat. Use prepare statement technique.
- (BOOL)_prepareStatement:(sqlite3_stmt **)statement withSQL:(const char *)sql {
sqlite3_stmt *s = *statement;
//caDatabase is declared as sqlite3 *caDatabase object
if (nil == s && sqlite3_prepare_v2(caDatabase, sql, -1, &s, NULL)!= SQLITE_OK)
{
[self _showError];
*statement = nil;
return NO;
}
*statement = s;
return YES;
}
- (caObjectId)existObject:(caObjectId)objId withType:(caCacheObjectType)objType libraryID:(int)aLibraryID
{
#synchronized (self)
{
const char *caSQLexistObj = "SELECT id FROM objects WHERE objId = ? AND objType = ? AND libraryID = ?";
if(![self _prepareStatement:&ca_existObjectStatement withSQL:caSQLexistObj]) {
//produce some error message
return;
}
sqlite3_bind_int(ca_existObjectStatement, 1, objId);
sqlite3_bind_int(ca_existObjectStatement, 2, objType);
sqlite3_bind_int(ca_existObjectStatement, 3, aLibraryID);
NSInteger result = sqlite3_step(ca_existObjectStatement);
if (result != SQLITE_ROW)
{
sqlite3_reset(ca_existObjectStatement);
return caObjectIdNone;
}
caObjectId cacheId = sqlite3_column_int(ca_existObjectStatement, 0);
sqlite3_reset(ca_existObjectStatement);
return cacheId;
}
}

Related

sqlite passing comma separated string as sqlite3_result_text

I have a custom function that needs to return a comma-separated list of IDs that I then need to use in a statement. However, I can't seem to get it to work as sqlite3_result_text seems to be unhappy about the single quotes I've used for individual strings. Here's what I have:
void sqliteExtractNames(sqlite3_context *context, int argc, sqlite3_value **argv) {
assert(argc == 1);
if (sqlite3_value_type(argv[0]) == SQLITE_TEXT) {
unsigned const char *stringC = sqlite3_value_text(argv[0]);
NSString *stringOrig = [[NSString alloc] initWithUTF8String:(char *) stringC];
// This returns something like: 'Name 1', 'Name 2'
NSString *nameString = [self getCommaSeparatedNames: stringOrig];
if ([nameString length] > 0) {
sqlite3_result_text(context, [nameString UTF8String], -1, SQLITE_TRANSIENT);
} else {
sqlite3_result_null(context);
}
} else {
sqlite3_result_null(context);
}
}
And then later I'm doing:
SELECT count(*) FROM mytable WHERE name IN (sqliteExtractNames(somecolumn))
However this does not seem to work. If I change the getCommaSeparatedNames method to instead return a single word without single quotes, it works. The moment I use more than one word separated by a comma, it stops working. How can I pass a text result back which can be used in this statement?
Your function sqliteExtractNames returns a single string. So your query is the equivalent of:
SELECT ... WHERE name IN ('aaa,bb,ccc')
which check the name against a single string value.
If you have SQLite 3.9.0 or later, you could implement your function as a table-valued function, which is rather complex.

SQLite PRAGMA cache_size iOS

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.

Use of undeclared identifier - identifier is in "if statement"

I have an alert Use of undeclared identifier 'sql3'.
If I delete the if statement, there 's no problem, but it seems it's not recognized when sql3 is inside the if statement.
Is there a way to fix it?
if ([typedepartie isEqual: #"ami"]) {
const char *sql3 =
[[NSString stringWithFormat:#"SELECT id
FROM tabledesquestions
WHERE pack = ? ORDER BY RANDOM() LIMIT 4"]
cStringUsingEncoding:NSUTF8StringEncoding];
}
listequestionmulti = [[NSMutableArray alloc]init];
sqlite3_stmt *sql1Statement;
if(sqlite3_prepare(database1, sql3, -1, &sql1Statement, NULL) != SQLITE_OK) {
NSLog(#"Problem with prepare statement: %s", sqlite3_errmsg(database1));
}
The identifier sql3 is defined inside curly braces. Therefore it is purely local to those curly braces. Therefore when you get past that to the next part of your code, it no longer exists.
if ([typedepartie isEqual: #"ami"]) {
const char *sql3 = // sql3 is born here...;
} // and dies here
What you want is more like this:
char *sql3 = // default value;
if ([typedepartie isEqual: #"ami"]) {
sql3 = // other value;
}

why does my sqlite sql return no results?

The follow is my db function:
+(NSArray*)searchWithKey:(NSString*)_key{
NSMutableArray* tmpArray = [NSMutableArray array];
static Statement* stmt = nil;
char* sql = "select * from Bookmarks where BMUrl like '%?%'";
if (stmt == nil) {
stmt = [DBConnection statementWithQuery:sql];
[stmt retain];
}
[stmt bindString:_key forIndex:1];
while ([stmt step] == SQLITE_ROW) {
BookMark* tmpBM = [[BookMark alloc] initWithStatement:stmt];
NSLog(#"tmpBM = %#",tmpBM);
[tmpArray addObject:tmpBM];
[tmpBM release];
}
[stmt reset];
return tmpArray;}
The keyword of sql is "like" which I use.But there are no results that the sqlite return.Anyone could tell me why?
I change the sql into "select * from Bookmarks where BMUrl like '%h%'",there are some results which are returned.So , I guess the mistake is the function "bindString:forIndex",the code is
- (void)bindString:(NSString*)value forIndex:(int)index{
sqlite3_bind_text(stmt, index, [value UTF8String], -1, SQLITE_TRANSIENT);}
which is the correct sqlite3 api that i will use? thank u!
Bindings aren't interpolated like that. If you put a quotation mark in a string, as in '%?%', it will be interpreted as a literal question mark.
You should instead modify your input _key:
Escape any instances of % and _ with a \
Add %s at the beginning and end
This prepares it to be used with a LIKE operator.
You also need to modify your SQL so that the ? represents a standalone parameter: ... where BMUrl like ?.
Here's an example for how to escape special characters and add %s at the beginning and end of _key:
NSString *escapedKey = [_key stringByReplacingOccurencesOfString:#"%"
withString:#"\\%"];
escapedKey = [escapedKey stringByReplacingOccurencesOfString:#"_"
withString:#"\\_"];
NSString *keyForLike = [NSString stringWithFormat:#"%%%#%%", escapedKey];
[stmt bindString:keyForLike forIndex:1];

Retrieving an integer value from a sqlite3 db (problem in obj-c)

In a sqlite3 database, I've a table "data" with two fields: type and path. The field type is defined as INTEGER. In this field I insert a NSUInteger value (which will be for example 0 or 1). The problem is that, when I retrieve it, I obtain a "strange" value. I don't know where I'm wronging.
if (init_statement == nil) {
const char *sql = "SELECT type,path FROM data WHERE id=?";
if (sqlite3_prepare_v2(database, sql, -1, &init_statement, NULL) != SQLITE_OK) {
NSAssert1(0, #"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database));
}
}
sqlite3_bind_int(init_statement, 1, primaryKey);
if (sqlite3_step(init_statement) == SQLITE_ROW) {
int type = (int)sqlite3_column_text(init_statement, 0);
char *relPath = (char *)sqlite3_column_text(init_statement, 1);
// other stuff
}
// Reset the statement for future reuse.
sqlite3_reset(init_statement);
SQLite allows only 64 bit signed integers. You are assigning it an unsigned integer. Change it to NSInteger instead.