I am working on a project that is built on raw sqlite, and I have changed it to FMDB.
All the queries are parametrized.
Here is one such an example:
NSString* sqlQuery = [NSString stringWithFormat:#"SELECT COUNT(*) FROM Contacts WHERE FirstName='%#' AND LastName='%#'", fName,lName];
and I pass it to my helper class:
NSInteger count = [[[DB sharedManager] executeSQL:sqlQuery] integerValue];
Helper class:
- (NSString*)executeSQL:(NSString *)sql
{
__block NSString *resultString = #"";
[_secureQueue inDatabase:^(FMDatabase *db) {
FMResultSet *results = [db executeQuery:sql];
while([results next]) {
resultString= [results stringForColumnIndex:0];
}
}];
return resultString;
}
I can make a workaround something like:
sql = #"SELECT COUNT(*) FROM Contacts WHERE FirstName=? AND LastName=?"
[db executeQuery:sql,firstParam, secondParam]
But I do not want to change the helper method, I need to pass the changed/updated sql query to my helper method.
How can I change this:
NSString* sqlQuery = [NSString stringWithFormat:#"SELECT COUNT(*) FROM Contacts WHERE FirstName='%#' AND LastName='%#'", fName,lName];
to something like:
NSString* sqlQuery = [NSString stringWithFormat:#"SELECT COUNT(*) FROM Contacts WHERE FirstName=? AND LastName=?", fName,lName];
If you want to avoid SQL Injection problems, then you must never build a query using stringWithFormat. You must properly bind variables into the query. Period. End of discussion.
So you have no choice but to change your helper. Have it take two parameters instead of one. The first being the query with the proper use of ? and the second being an array of values that get bound into the query but the helper method.
Related
I'm trying to get allways the same order for my fetch data, because all the time I request, he get another sorted array so I'm trying to order by ID but I couldn't.
My situation:
I have 2 models, FORM and FORMCOMPONENT
1 FORM have many FORMCOMPONENT
when I do this:
Form *form = ...;
[form formComponents]; //I get all the components but each time I run I get with a differente order
How I suppose order this if I don't have any field for ORDER? On android I did this with the ID.. that's why I don't have any field with this proposal.
I tryed to order in a array with sortWithComparator but I can't do this because I'm not allowed to get the number ID (only the number, not the entire string he give me with [obj objectID] ). This is funny because when I use sqlite3 for see the database only have the number there.
Another way I thought about is get the FORMCOMPONENTs direct with a new request, not by FORM one ([form formComponents]), like this:
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"FormComponent" inManagedObjectContext:[appDelegate managedObjectContext]];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
//SOME PREDICATE WITH THE FORM
[request setPredicate:predicate];
NSError *error = nil;
NSMutableArray *arrayComps = (NSMutableArray*) [[appDelegate managedObjectContext] executeFetchRequest:request error:&error];
so, anyone have an idea? Thanks.
--- EDIT ---
PS: I don't want to avoid the problem creating another field for order. If I don't have any other solution so I will do this.
You can sort by objectID by using a sort descriptor with self as the sort key. The underlying SQL will have ORDER BY t0.Z_PK, Z_PK being the internal ID of the record (as found as the last component of the object's NSManagedObjectID).
Core data is no database.
Internally Core Data uses PK, of course. But Core Data is no database, it is a system to model a graph. Consequently no "ID information" is published. Looking for an ID is anti-conceptional.
If your data does not have a "natural order", there should be nothing wrong in getting a random order. If it has a natural order, simply use sort descriptors.
If you need an order "creation date" simply add a property creationDate and set it in -awakeFromInsert. (BTW: AFAIK it is an urban legend, that PK always raises. A PK can be something else than autoincrement.)
Core Data doesn't expose any kind of SQL-style numeric ID that you could use for sorting. If you don't want to add an attribute to sort on, the easiest thing might be to configure this relationship as being ordered. Then you get an NSOrderedSet for the relationship, and you can keep individual instances in whatever order you want. Make the relationship ordered in the Core Data model editor, and use mutableOrderedSetValueForKey: when you want to add a new instance to the relationship.
The recommended way it to add a field for the ID/sort order you want to give to your components.
Alternatively you could make the relationship ordered by checking the checkbox for that in the CoreData editor. This has some more overhead, but is the only option if components need to appear in multiple lists.
Meddling with SQLite directly isn't a smart approach.
Thanks for the help. I found what I'm looking for (with this I can order for example)
I need to get PK direct from sql, if anyone needs do this, here is the code:
Just don't forget what our friend Tom said: "That's not how Core Data works"
-(NSMutableArray*) readDatabase {
NSLog(#"READ");
sqlite3 *database;
NSMutableArray *components = [[NSMutableArray alloc] init];
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
NSString *databasePath = [documentsDir stringByAppendingPathComponent:#"Model.sqlite"];
// Open the database from the users filessytem
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK) {
NSLog(#"SQLITE_OK");
// Setup the SQL Statement and compile it for faster access
const char *sqlStatement = "select * from zformcomponent";
sqlite3_stmt *compiledStatement;
if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK) {
// Loop through the results and add them to the feeds array
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
// Read the data from the result row
int aPK = sqlite3_column_int(compiledStatement, 0);
//NSString *aDescription = [NSString stringWithUTF8String: (char *)sqlite3_column_text(compiledStatement, 2)];
//NSString *aImageUrl = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 3)];
NSLog(#"PK: %d",aPK);
//Animal *animal = [[Animal alloc] initWithName:aName description:aDescription url:aImageUrl];
//[animals addObject:animal];
}
}
sqlite3_finalize(compiledStatement);
}
sqlite3_close(database);
return components;
}
This is an odd problem. I've found a workaround that works, but I'd like to understand why, because I suspect it will come up again later in my code. Here's the method that's giving my the problem:
FMDatabase *cardDatabase // initialized elsewhere
NSString *cleanChar; // this is the sanitized query string
NSString *cardQueryString;
int cardID;
switch ([self studyLanguage])
{
case CSStudyLanguageMandarinTraditional:
cardQueryString = #"SELECT rowid, * FROM trad_char WHERE search = ";
break;
// [...other cases...]
}
cardQueryString = [cardQueryString stringByAppendingString:cleanChar];
[cardDatabase open];
FMResultSet* cardSet = [cardDatabase executeQuery:cardQueryString];
BOOL foundCard = NO;
while ([cardSet next])
{
foundCard = YES;
cardID = [cardSet intForColumn:#"rowid"];
}
[cardDatabase close];
This code gives a database error:
DB Error 1: no such column: 意
where "意" is the character sequence I'm passing in cleanChar, which is apparently being treated as a column rather than a value somehow. However...if I add the search character in the query step, like so:
cardQueryString = #"SELECT rowid, * FROM trad_char WHERE search = ?";
FMResultSet* cardSet = [cardDatabase executeQuery:cardQueryString, cleanChar];
everything works just fine.
So I've actually solved the problem...but I want to know why, because it affects the way I'd like to do some more complicated queries with multiple joins down the road, where it'll be harder to just plug in variables at the end.
So: Is this a bug in FMDatabase? Do I just not understand how to use FMDatabase? Or is there some other problem with the code?
Your query is failing because you haven't wrapped your search string in single quotes (used to designate literals). So you need to replace this line
cardQueryString = [cardQueryString stringByAppendingString:cleanChar];
with this:
cardQueryString = [NSString stringWithFormat:#"%#'%#'", cardQueryString cleanChar];
However, that's bad practice, to compose the query by using NSString methods to combine the query string and query argument. It's problematic for several reasons, such as if the argument contains reserved characters or words, or escape sequences that open you up to SQL injection issues.
You should instead provide argument via the binding mechanisms of SQLite, which are wrapped up nicely in the FMDB methods. So, your "workaround" is actually the best method:
cardQueryString = #"SELECT rowid, * FROM trad_char WHERE search = ?";
FMResultSet* cardSet = [cardDatabase executeQuery:cardQueryString, cleanChar];
or send one or more arguments in an array:
cardQueryString = #"SELECT rowid, * FROM trad_char WHERE search = ?";
FMResultSet* cardSet = [cardDatabase executeQuery:cardQueryString arguments:#[cleanChar]];
Tom
BareFeetWare
I run an applescript from an objective-c application using NSAppleScript the executeAndReturnError method. This returns an NSAppleEventDescriptor object containing the result of the script. My script returns an applescript record. How do interpret the record in objective-c? For instance if the returned script record is { name:"Jakob", phone:"12345678" } how do I get the string "Jakob" in the name property?
Here's a method to convert the record to a dictionary. Note that I didn't try this but it should work.
Also, your "name" key is not a good key to use in applescript because "name" has other meanings to applescript. I've often had problems using that in a record. I would suggest changing it something like "theName" or using it in bars |name|.
-(NSDictionary*)recordToDictionary:(NSAppleEventDescriptor*)theDescriptor {
NSUInteger j,count;
id thisDescriptor = [theDescriptor descriptorAtIndex:1];
count = [thisDescriptor numberOfItems];
NSMutableDictionary* thisDictionary = [NSMutableDictionary dictionaryWithCapacity:count/2];
for (j=0; j<count; j=j+2) {
NSString* theKey = [[thisDescriptor descriptorAtIndex:(j+1)] stringValue];
NSString* theVal = [[thisDescriptor descriptorAtIndex:(j+2)] stringValue];
[thisDictionary addEntriesFromDictionary:[NSDictionary dictionaryWithObject:theVal forKey:theKey]];
}
return (NSDictionary*)[[thisDictionary retain] autorelease];
}
Of course after the record is in dictionary format you can just use the "valueForKey:" instance method on the dictionary to get "Jacob".
I have an array of products classes. Each product class has multiple properties such as name, functions, ingredients, etc.
I developed a simple search algorithm using NSPredicate that allows me to search for a specific string in the array of products.
In a nutshell, the predicate looks like: "string in product.name OR string in product.ingredients OR string in product.functions...".
I can get the result array correctly, i.e. list of products that contains the search string in one of its properties.
BUT, my question is, how to sort this result by RELEVANCE? i.e. if a search string hits multiple times in a product class, naturally I want to put this product at the top of the search result array?
Thanks!
I have coded an example that might answer your question. I am not saying this is the best implementation, but it works.
NSArray *strings = #[#"A",#"B",#"C",#"A",#"D",#"E",#"B",#"B",#"B"];
NSMutableDictionary *sortedDictionary = [NSMutableDictionary dictionary];
for (NSString *string in strings) {
NSNumber *count = [sortedDictionary objectForKey:string];
if (count) {
count = #(count.integerValue + 1);
} else {
count = #(1);
}
[sortedDictionary setObject:count forKey:string];
}
NSArray *result = [sortedDictionary keysSortedByValueWithOptions:NSNumericSearch usingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj2 compare:obj1];
}];
NSLog(#"%#", result); // (B,A,D,E,C)
I'm trying to use a datareader in SQLite, but am unable to find anything in the docs I have ("Using SQLite" by Kreibich).
Can someone tell me if it's supported and where I can find some examples?
Yes, you just need to get yourself System.Data.SQLite.
It comes in two variants, one that has SQLite built-in, and another which requires that you also ship a separate native sqlite DLL.
the sqlite api has a concept which is logically equivalent to the .net reader. which means, you issue a query and then iterate read data as needed. that keeps memory low as your not pulling the complete result set into memory.
first of all, take a look at other wrappers like fmdb.
here's the equivalent using the c api inside of iPhone. You prepare the statement by passing the sql query (sqlite parses under the cover), then you call step which is the equivalent to the .net reader read method. The you read columns just like the .net data reader. Note that this example prepares and finalizes (cleans up). A more efficient approach is to save the prepared statement and then call reset to avoid having sqlite parse the query over and over.
// prep statement
sqlite3_stmt *statement;
NSString *querySQL = #"SELECT id, name FROM contacts";
NSLog(#"query: %#", querySQL);
const char *query_stmt = [querySQL UTF8String];
// preparing a query compiles the query so it can be re-used.
sqlite3_prepare_v2(_contactDb, query_stmt, -1, &statement, NULL);
// process result
while (sqlite3_step(statement) == SQLITE_ROW)
{
int idx = 0;
Contact *contact = [[Contact alloc] init];
NSNumber *idField = [NSNumber numberWithLongLong:sqlite3_column_int64(statement, idx++)];
[contact setId:idField];
NSString *nameField = [[NSString alloc] initWithUTF8String:(const char *) sqlite3_column_text(statement, idx)];
[contact setName:nameField];
NSLog(#"id: %#", [contact id]);
NSLog(#"name: %#", [contact name]);
[nameField release];
[contactsList addObject:contact];
[contact release];
}
sqlite3_finalize(statement);