sqlite passing comma separated string as sqlite3_result_text - objective-c

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.

Related

sqlite fetch statement

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

Replacing character within cstring - getting bad access

Is it possible to replace a character from a c string after converting it from NSString via the UTF8string method?
For example take the code below. It is to format a string with particular rule.
- (NSString *)formatString:(NSString *)input {
if (input.length==0) {
return #"";
}
//code to determine rule
....
....
// substitute output format with input characters
if (rule) {
input = [input substringFromIndex:prefix.length];
char *string = (char *)[rule UTF8String];
int repCount = 0;
for (int i=0; i<rule.length; i++) {
if (string[i] == '#') {
if (repCount < input.length)
string[i] = [input characterAtIndex:repCount++];//bad access
else
string[i] = ' ';
}
}
NSMutableString *output = [NSMutableString stringWithCString:string encoding:NSUTF8StringEncoding];
...
... //do something with the output
return output;
} else {
return input;
}
}
Initially string[0] has '#' and it should get replaced with the character in the input. This is not happening.
In a word, NO. That buffer doesn't belong to you so leave it alone.
A couple of issues:
You are casting UTF8String, which returns a const char *, to char *. UTF8String is, by definition, returning a read-only string and you should use it as such. (You really should use casts sparingly, if at all. Certainly never use casts to override const qualifiers for variables.)
If you want to perform this C-string manipulation, you have to copy the string to your own buffer. For example, use getCString or getCharacters methods (but only after you've created a buffer to receive them, and remember to add a character for the NULL terminator).
By the way, you're also returning characterAtIndex, which is a unichar (which can be larger than 8-bits), and using it in your char * buffer (8-bits per character). I'd be wary about mixing and matching those without being very careful. It is best to pick one and stick with it (and unichar offers a little more tolerance for those non-8-bit characters).
Perhaps you check for this earlier, but you're setting string to be those characters after the prefix, and then proceed to check the next rule.length number of characters. But, as far as I can tell, you have no assurances that string actually has that many characters left in it. You should test for that, or else that will also cause problems.
Personally, I'd retire this whole C-string algorithm and employ the appropriate NSString and/or NSMutableString methods to do whatever replacement you wanted, e.g. stringByReplacingCharactersInRange, stringByReplacingOccurrencesOfString, or the equivalent NSMutableString methods, replaceCharactersInRange or replaceOccurrencesOfString.

Using a function argument inside a function in Objective-C

I am new to Objective-C development and I have already run into the first problem. I am trying to write a function that takes a string as argument and compares that string with other strings. The pseudo code for the function would be the following:
String string2
String string3
function compare_string (String string) {
if (string == "a specific string" &&
string == string2 &&
string == string3) {
// do something
}
}
Until now I only have the following:
NSString *string2 = #"Second string";
NSString *string3 = #"Third string";
(void) compare_string: (NSString *) string {
but now I am already stuck because I don't know how to call the input string string inside the function. Should I simply do something like
if (string == string2) { ... }
or is there another way to use function arguments in Objective-C?
This will be like this:
-(void)compareString:(NSString *string){
if ( [string isEqualToString:#"a specific string"] && [string isEqualToString:string2] && [string isEqualToString:string3] ){
// do something
}
}
NOTE: str1==str2 will compare the memory address, and isEqualToString: compares the contents of the strings.
Check out the docs for NSString there are a bunch of methods that do this for you already - so you don't need to make your own string comparison code
An example would look like
if ([string compare:string2] == NSOrderedSame) { ... }
This is handy if you wish to do complex comparisons like case insensitive etc
for simple equality you could just use
if ([string isEqualToString:string2]) { ... }
Edit
I mis read the question - here is an answer that actually relates to what you asked
You need to use the isEqual: methods defined on object for checking equally. == will only check the pointer values.
You could write your method like this
- (void)compareString:(NSString *)string;
{
if ([#[string, string, string] isEqual:#[ #"a specific string", string1, string2]]) {
// do stuff
}
}
Then later calling it would look like this
[self compareString:#"A string to compare"];
NB
Wrapping it up in arrays reads slightly nicer than comparing them all individually

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

Parsing arithmetic expression for long numbers that need formatting

I am trying to make a simple calculator app. Currently, the app works perfectly. One problem: It's smart enough to change results into formatted numbers (800000 = 800,000), but not full expressions (200*600/21000 = 200*600/21,000).
I would like to be able to have a method that I could feed a string and get back a string of properly formatted numbers with operations still inside the string.
Example:
I feed the method 30000/80^2. Method gives back 30,000/80^2.
EDIT: People seem to be misunderstanding the question (Or it's possible I am misunderstanding the answers!) I want to be able to separate the numbers - 60000/200000 would separate into 60000 & 200000. I can do it from there.
Well, what's the problem? You obviously can parse the whole expression (you say calculator works), you can format single numbers (you say you can format results).
The only thing you need is to parse the expression, format all the numbers and recompose the expression...
EDIT: There is a simpler solution. For formatting, you don't need to parse the expression into a tree. You just have to find the numbers.
I suggest to create character set of all operators
NSCharacterSet* operators = [NSCharacterSet characterSetWithCharactersInString:#"+*-/^()"];
NSCharacterSet* whitespaces = [NSCharacterSet whitespaceCharacterSet];
Then split the expression using this set:
NSString* expression = [...];
NSMutableString* formattedExpression = [NSMutableString string];
NSRange numberRange = NSMakeRange(0, 0);
for (NSUInteger i = 0; i < expression.length; i++) {
unichar character = [expression characterAtIndex:i];
if ([whitespaces characterIsMember:character] || [operators characterIsMember:character]) {
if (numberRange.length > 0) {
NSString* number = [expression substringWithRange:numberRange];
NSString* formattedNumber = [self formatNumber:number];
[formattedExpression appendString:number];
numberRange.length = 0;
}
}
else if (numberRange.length == 0) {
numberRange.location = i;
numberRange.length = 1;
}
else {
numberRange.length++;
}
if ([operators characterIsMember:character]) {
[formattedExpression appendFormat:#"%C", character];
}
}
if (numberRange.length > 0) {
NSString* number = [expression substringWithRange:numberRange];
NSString* formattedNumber = [self formatNumber:number];
[formattedExpression appendString:number];
}
Note that this should work even for numbers prefixed by a sign. I am ignoring all whitespaces because if you want to have a pretty expression, you probably want to handle whitespaces differently (e.g. no space after (, space before +/-, space after - only if it's not a number sign...). In general, for handling spaces, parsing the expression into a tree would simplify matters. Also note that infix expressions are not unambiguous - that means that you should sometimes add parenthesis. However, that can't be done without parsing into a tree.
Look up NSNumberFormatter. Not only will that handle formatting of numbers, it will do so based on the user's locale.