FMDB sqlite's sql query not working - ios

I am creating a temp table using the following SQL query. But its not showing any results (result set is empty) when I execute it programmatically, and shows records when I execute it manually on the database.
NSString *query=[NSString stringWithFormat:#"create temp table search2 as select Observationsid from Observations where admin_id=%d AND teacher_id=%d AND observation_type=%d AND isvalid='yes' AND date BETWEEN '12-20-2013' AND '12-23-2013'",appDelegate.admin_id,appDelegate.teacher_id,appDelegate.observationType];
FMResultSet *results=[appDelegate.database executeQuery:query];
Nslogged query:
Printing description of results->_query:
create temp table search2 as select Observationsid from Observations where admin_id=2 AND teacher_id=1 AND observation_type=2 AND isvalid='yes' AND date BETWEEN '12-20-2013' AND '12-23-2013'
and records are there in database.

This SQL statement shouldn't return results, so you should use executeUpdate method rather than a executeQuery method. You should not see any results from this. If you want to see the results, do a separate executeQuery statement returning the results from search2. (I assume you're building the search2 table for a reason.)
NSString *query=[NSString stringWithFormat:#"create temp table search2 as select Observationsid from Observations where admin_id=%d AND teacher_id=%d AND observation_type=%d AND isvalid='yes' AND date BETWEEN '12-20-2013' AND '12-23-2013'",appDelegate.admin_id,appDelegate.teacher_id,appDelegate.observationType];
if (![appDelegate.database executeUpdate:query])
NSLog(#"create error: %#", [appDelegate.database lastErrorMessage]);
query = #"SELECT * FROM search2";
FMResultSet *results = [appDelegate.database executeQuery:query];
if (!results)
NSLog(#"create error: %#", [appDelegate.database lastErrorMessage]);
By the way, while you can get away with stringWithFormat for this particular SQL, that's not a good idea. You generally want to use the ? placeholders, e.g.
NSString *query = #"create temp table search2 as select Observationsid from Observations where admin_id=? AND teacher_id=? AND observation_type=? AND isvalid='yes' AND date BETWEEN '12-20-2013' AND '12-23-2013'";
if (![appDelegate.database executeUpdate:query, #(appDelegate.admin_id), #(appDelegate.teacher_id), #(appDelegate.observationType)])
NSLog(#"create error: %#", [appDelegate.database lastErrorMessage]);
Unrelated, but I notice that you're using MM-DD-YYYY format text string for your dates. That's not going to work. SQLite doesn't natively "understand" dates. Notably, if you've stored your dates in MM-DD-YYYY format, a text string of 12-21-2052 is BETWEEN '12-20-2013' AND '12-23-2013' (because text strings are compared in alphabetically order), which is probably not what you intended. See Date And Time Functions.
But, fortunately, FMDB does the NSDate conversion for you, storing it as numeric value. So, for example, let's assume your table was defined as follows:
CREATE TABLE IF NOT EXISTS Observations
(Observationsid INTEGER PRIMARY KEY AUTOINCREMENT,
admin_id INTEGER,
teacher_id INTEGER,
observation_type INTEGER,
isvalid TEXT,
date REAL);
You really want to use YYYY-MM-DD or, better, rely upon FMDatabase to convert your NSDate values for you. Remember, SQLite does not have proper date formats, so choose an appropriate format (e.g. YYYY-MM-DD format) or let FMDatabase handle this for you, using NSDate objects.
If you want to facilitate the conversion of date strings to dates, you can use a NSDateFormatter, e.g.:
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = #"MM-dd-yyyy";
NSDate *someDate = [formatter dateFromString:#"12-22-2013"];
if (![appDelegate.database executeUpdate:#"INSERT INTO Observations (admin_id, teacher_id, observation_type, isvalid, date) VALUES (?, ?, ?, ?, ?)", #(appDelegate.admin_id), #(appDelegate.teacher_id), #(appDelegate.observationType), #"yes", someDate])
NSLog(#"insert error: %#", [appDelegate.database lastErrorMessage]);
Or, if I wanted to select the dates between these two dates:
NSDate *startDate = [formatter dateFromString:#"12-20-2013"];
NSDate *endDate = [formatter dateFromString:#"12-23-2013"];
NSString *query = #"create temp table search2 as select Observationsid, date from Observations where admin_id=? AND teacher_id=? AND observation_type=? AND isvalid='yes' AND date BETWEEN ? AND ?";
if (![appDelegate.database executeUpdate:query, #(appDelegate.admin_id), #(appDelegate.teacher_id), #(appDelegate.observationType), startDate, endDate])
NSLog(#"create error: %#", [appDelegate.database lastErrorMessage]);
query = #"SELECT * FROM search2";
FMResultSet *results = [appDelegate.database executeQuery:query];
if (!results)
NSLog(#"select error: %#", [appDelegate.database lastErrorMessage]);
while ([results next])
NSLog(#"%d %#", [results intForColumnIndex:0], [results dateForColumnIndex:1]);
If you want to avoid this NSDate logic, you could alternatively consciously store dates in a text format in YYYY-MM-DD format. That works, too. Again, see that Date and Time Functions page for a list of valid text formats for dates.

Related

Cannot send user's input containing an apostrophe to sqlite3-database (Obj-C)

I've been looking at some similar questions on here but can't seem to find any working solutions.
My app lets the user write whatever they want in a UITextField and send it to a sqlite3-database. However, if it contains an apostrophe, it won't send it.
I know putting two apostrophes in the text field works but that isn't user-friendly at all... Any ideas?
Thanks!
- (IBAction)saveInfo:(id)sender {
// Prepare the query string.
// If the recordIDToEdit property has value other than -1, then create an update query. Otherwise create an insert query.
NSString *query;
if (self.recordIDToEdit == -1) {
query = [NSString stringWithFormat:#"insert into peopleInfo values(null, '%#', %#)", self.txtFirstname.text, self.txtDate.text];
}
else{
query = [NSString stringWithFormat:#"update peopleInfo set firstname='%#', date=%# where peopleInfoID=%d", self.txtFirstname.text, self.txtDate.text, self.recordIDToEdit];
}
Never do it this way! Always remember little Bobby Tables:
Exploits of a Mom
Use prepared statements to insert data into a database, never concatenate / build your statements on your own; take a look here:
Objective-C: Fast SQLite Inserts with Prepared Statements for Escaped Apostrophes and SQL Injection Protection
On your own risk, you could use stringByReplacingOccurrencesOfString:withString: to escape each single quote with two single quotes:
NSString *firstName = self.txtFirstname.text;
firstName = [firstName stringByReplacingOccurrencesOfString:#"'" withString:#"''"];
if (self.recordIDToEdit == -1) {
query = [NSString stringWithFormat:#"insert into peopleInfo values(null, '%#', %#)", firstName, self.txtDate.text];
}
else{
query = [NSString stringWithFormat:#"update peopleInfo set firstname='%#', date=%# where peopleInfoID=%d", firstName, self.txtDate.text, self.recordIDToEdit];
}
You should always do some kind of "string escaping" when concatenating a query. As for the safety, look up for SQL injection attacks.

Finding objects between two dates

Okay so I have a parse database with two keys (startedAt) and (endedAt) basically I would like to return all the objects between these two given times. However, I store the times as strings, not dates. I would really like to not have to change them to dates as that would force me to rework code in a lot of other parts of my program. So is it possible to do the following
NSDate *leftDate = leftClassRoomAfterDatePicker.date;
NSDate *arrivedDate = arrivedBeforeDatePicker.date;
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"MM-dd hh:mm"];
NSString *leftDateString = [dateFormat stringFromDate:leftDate];
NSString *arrivedDateString = [dateFormat stringFromDate:arrivedDate];
NSLog(leftDateString);
NSLog(arrivedDateString);
PFQuery *query = [PFQuery queryWithClassName:#"PassData"];
[query whereKey:#"startTime" greaterThan:leftDate];
[query whereKey:#"timeArrived" lessThan:arrivedDate];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
NSLog(#"Successfully retrieved %d objects.", objects.count);
}];
When I run this code I get the following error
The problem is that the strings are not returning the correct value. In my parse database I have a value of startTime: Dec 08, 18:28
and endTime: Dec 08, 18:28
But it still does not work (no objects are returned). I think this is because the greaterthan/lessthan functions will not work on strings like this.
You've converted leftDate and arrivedDate to leftDateString and arrivedDateString, but you're still using leftDate and arrivedDate in your query. I think you meant to write:
PFQuery *query = [PFQuery queryWithClassName:#"PassData"];
[query whereKey:#"startTime" greaterThan:leftDateString];
[query whereKey:#"timeArrived" lessThan:arrivedDateString];
in which case you'd no longer get the error since you'd be comparing string to string.
Although I generally recommend that you store and sort your dates with NSDate objects, in this case where your format is in the same descending order of importance as a typical NSDate sort of month, day, hour, then minute, i.e. "MM-dd hh:mm", as long as year or seconds don't matter to you and as long as the queried time format matches the database time format, this query should work since greaterThan and lessThan will compare the string objects alphabetically/numerically.
Error: startTime contains string values but got a date (Code: 102,
Version: 1.2.21)
The error clearly indicates that you are comparing two different objects one is string and second one is date. So there are two things you can either convert any one into date or string. So to implement the same in a easy way, you can write one category with function which will convert either into date or string and use that method to perform the comparison.
I guess for that to work you have to have Date fields in database, then you pass NSDate to whereKey: on iOS.

Bind parameter in FROM clause in SQLite

Is it possible to bind a parameter in the FROM clause of a query to SQLite? How?
If not, why? Any alternatives?
Here is what I want to do in my iOS application:
- (BOOL)existsColumn:(NSString *)column inTable:(NSString *)table ofDatabase:(FMDatabase *)database {
NSString *query = #"SELECT ? FROM ? LIMIT 0";
NSArray *queryParameters = #[column, table];
return (BOOL)[database executeQuery:query withArgumentsInArray:queryParameters];
}
Here is the error message I get:
near "?": syntax error
I understand that there are other ways to check if a column exists. I am specifically curious why I can't bind a parameter in the FROM clause (only binding a parameter in the SELECT clause above works so it must be an issue with the FROM clause).
The ? should only be used for binding actual values into the query such in the VALUES section of an INSERT or values in a WHERE clause.
For dynamically building the column and table names you should simply use a string format to build the query string before sending it to FMDB.
- (BOOL)existsColumn:(NSString *)column inTable:(NSString *)table ofDatabase:(FMDatabase *)database {
NSString *query = [NSString stringWithFormat:#"SELECT %# FROM %# LIMIT 0", column, table];
return (BOOL)[database executeQuery:query];
}
Please note that you should not use the string format in place of using ? and proper value binding for actual values (which you don't have in this case).

Query filtering based on dates using FMDB?

How do I query games that are after a "visible" date?
The dateformat is set:
FMDatabase *db = self.database;
if([self.database open]){
[db beginTransaction];
NSDateFormatter *formatter = [NSDateFormatter new];
[formatter setDateFormat:#"yyyy-dd-MM'T'HH:mm:ss'Z'"];
[db setDateFormat:formatter];
...
}
The table is created the following way:
[db executeUpdate:#"CREATE TABLE IF NOT EXISTS game (gameId Varchar(36) NOT NULL PRIMARY KEY,name TEXT,visibleFrom TEXT)"];
The query is executed the following way:
FMResultSet *results = [db executeQuery:#"SELECT * FROM game WHERE game.visibleFrom >= ?,[NSDate date]];
This returns nothing. When I use select * from game then i get all the games. But I need the filtered result. Thanks in advance.
Take a look at the SQLite syntax for querying dates: sqlite select with condition on date
You might need something like '#" ... where date > date(?)", formattedDate', where formattedDate is formatted like '1980-12-31'

Problem with FMDB and insert value in the "executeQuery:" from a searchString

While building a Search for my app i ran into a problem whilst using the FMDB SQLite Wrapper (https://github.com/ccgus/fmdb).
When I search my database with this SQL Command, everything is fine. 13 objects are returned and I can use them.
FMResultSet *rs = [db executeQuery:#"SELECT * FROM ZARTICLE WHERE ZTITLEDE LIKE '%Daimler%'"];
But when i try to insert the searchQuery from the User Input like this:
FMResultSet *rs = [db executeQuery:#"SELECT * FROM ZARTICLE WHERE ZTITLEDE LIKE (?)", theSearchQuery];
... the value is dont be inserted into SQL Command. And I dont get any returned objects from the DB. even if the String (theSearchQuery) is the same written in the first example.
Additionaly I post a part from the documentation of FMDB for your convinience. :)
Data Sanitization
When providing a SQL statement to FMDB, you should not attempt to "sanitize" any values before insertion. Instead, you should use the standard SQLite binding syntax:
INSERT INTO myTable VALUES (?, ?, ?)
The ? character is recognized by SQLite as a placeholder for a value to be inserted. The execution methods all accept a variable number of arguments (or a representation of those arguments, such as an NSArray or a va_list), which are properly escaped for you.
Thus, you SHOULD NOT do this (or anything like this):
[db executeUpdate:[NSString stringWithFormat:#"INSERT INTO myTable VALUES (%#)", #"this has \" lots of ' bizarre \" quotes '"]];
Instead, you SHOULD do:
[db executeUpdate:#"INSERT INTO myTable VALUES (?)", #"this has \" lots of ' bizarre \" quotes '"];
All arguments provided to the -executeUpdate: method (or any of the variants that accept a va_list as a parameter) must be objects. The following will not work (and will result in a crash):
[db executeUpdate:#"INSERT INTO myTable VALUES (?)", 42];
The proper way to insert a number is to box it in an NSNumber object:
[db executeUpdate:#"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:42]];
Alternatively, you can use the -execute*WithFormat: variant to use NSString-style substitution:
[db executeUpdateWithFormat:#"INSERT INTO myTable VALUES (%d)", 42];
Internally, the -execute*WithFormat: methods are properly boxing things for you. The following percent modifiers are recognized: %#, %c, %s, %d, %D, %i, %u, %U, %hi, %hu, %qi, %qu, %f, %g, %ld, %lu, %lld, and %llu. Using a modifier other than those will have unpredictable results. If, for some reason, you need the % character to appear in your SQL statement, you should use %%.
NSString *search_text = [NSString stringWithFormat:#"%%%#%%", theSearchQuery];
FMResultSet *rs = [db executeQuery:#"SELECT * FROM ZARTICLE WHERE ZTITLEDE LIKE ?", search_text];
I would highly recommend to avoid creating queries with stringWithFormat:! There is a good reason why FMDB tries to force you to use their data sanitization. However, since FMDB is boxing your input, surrounding parenthesis in the following code are not needed and may cause your problem.
[db executeQuery:#"SELECT * FROM ZARTICLE WHERE ZTITLEDE LIKE (?)", theSearchQuery];
Simple add arguments without any parenthisis because you never know how FMDB boxes your argument internally.
[db executeQuery:#"SELECT * FROM ZARTICLE WHERE ZTITLEDE LIKE ?", theSearchQuery];
If this still doesn't work try to use the suggested executeQueryWithFormat: method of FMDB:
[db executeQueryWithFormat:#"SELECT * FROM ZARTICLE WHERE ZTITLEDE LIKE %#", theSearchQuery];

Resources