Bind parameter in FROM clause in SQLite - ios

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).

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.

Getting distinct rows from Realm table in iOS

I am using Realm database for iOS application where i have a use case in which i want to filter result set by distinct values for a particular field. This field is not a primary key of the realm table.
I was not able to construct query for that.
Sample query :
RLMResults *allFiles = [FileRLMObject objectsInRealm:realmObject where:#"colA == %#", #"test1"];
FileRLMObject is a subclass of RLMObject from realm library
here table contains one column with name colB. While getting allFiles results, i want to get rows which are having distinct colB values.
Any suggestions how i can achieve this?
Realm doesn't support distinct queries yet. You can subscribe issue #1103 to track progress on that.
As a workaround, you could query for all values for colB first and then select objects for each value of it, as seen below:
NSArray *values = [FileRLMObject.allObjects valueForKey:"type"];
NSSet *distinctValues = [NSSet setWithArray:values];
NSMutableArray *allFiles = [NSMutableArray new];
for (NSString *colB in distinctValues) {
// This takes the firstObject.
// You might want to modify the sort order to make sure
// you get a certain object in case that there may exist
// multiple objects per distinct value.
FileRLMObject *object = [FileRLMObject objectsWhere:#"colB == ?", colB].firstObject;
[allFiles appendObject:object];
}

Escape special characters in FMDB iOS query

I wrote a query with arguments in FMDB using ? mark symbol.I want to get details of users who has any of info in the list(hope i can give different info separated by commas in "in" statement in sql). Since my arguments having special symbols, It is throwing error. How to escape there special symbols. I tried different methods but none worked yet
My code is like:
FMResultSet *results = [db executeQuery:#"select details from user where info in(?)",infoList];
while([results next])
{...}
Info is a string combined by different string seperated by commas.For example:
'C-note','Cuban missile crisis, the','cubbyhole','I Love Lucy','I'm a Celebrity ... Get me Out of Here!','Iacocca, Lee','-iana','Ivy League, the'
Thanks in advance
You can't use a single ? with an in clause unless you only bind a single value. It doesn't work for a list of values.
Since infoList is an array of string values, one option is to add a ? for each value in the list.
NSMutableString *query = [NSMutableString stringWithString:#"select details from user where info in ("];
for (NSInteger i = 0; i < infoList.count; i++) {
if (i) {
[query appendString:#","];
}
[query appendString:#"?"];
}
[query appendString:#")"];
FMResultSet *results = [db executeQuery:query withArgumentsInArray:infoList];

Unable to check the text value in my table in sqlite

I have one value which looks like this "00123-23" ,I have no idea about the datatype to be used to store in the table. So I used Text data type to store this value.But When I try to check this value, the query is saying that there is no such value in my table, but actually there is a value. Here is my query:
NSString *x_accountNo;
x_accountNo=[_substrings objectAtIndex:2]; //x_accountNo=00123-23
NSString *query_newAccount = [NSString stringWithFormat:#"SELECT * FROM MYTABLE WHERE ACCOUNT=%# ",x_accountNo];
BOOL recordExist_newAccount = [self recordExistOrNot_newAccount:query_newAccount];
if (!recordExist_newAccount) {
nslog(#"no data");
}
Everytime I execute this statement, It is giving me no data . Could you please tell me what I am doing wrong here?
You might need to have string inside quotes:
SELECT * FROM MYTABLE WHERE ACCOUNT='%#'

Multiple Queries not Running in FMDB

I'm using FMDB to create a SQLite database on iPhone. I have a initial.sql that is of the form
CREATE TABLE Abc ... ;
CREATE TABLE Def ... ;
I load this by loading the file into an NSString and running it
NSString * str = // string from file initial.sql
[db executeUpdate: str];
This succeeds but later on I get a failure:
no such table: Def
It's clear that the second statement is not being called. How can I do this so that all of the queries will be called?
According to the SQLite documentation:
"The routines sqlite3_prepare_v2(), sqlite3_prepare(), sqlite3_prepare16(), sqlite3_prepare16_v2(), sqlite3_exec(), and sqlite3_get_table() accept an SQL statement list (sql-stmt-list) which is a semicolon-separated list of statements."
So, this should all work.
I got bitten by this one too; it took me an entire morning of stepping through FMDatabase and reading the sqlite3 API documentation to find it. I am still not entirely sure about the root cause of the issue, but according to this bug in PHP, it is necessary to call sqlite3_exec instead of preparing the statement with sqlite3_prepare_v2 and then calling sqlite3_step.
The documentation does not seem to suggest that this behaviour would happen, hence our confusion, and I would love for someone with more experience with sqlite to come forward with some hypotheses.
I solved this by developing a method to execute a batch of queries. Please find the code below. If you prefer, you could rewrite this into a category instead of just adding it to FMDatabase.h, your call.
Add this to the FMDatabase interface in FMDatabase.h:
- (BOOL)executeBatch:(NSString*)sql error:(NSError**)error;
Add this to the FMDatabase implementation in FMDatabase.m:
- (BOOL)executeBatch:(NSString *)sql error:(NSError**)error
{
char* errorOutput;
int responseCode = sqlite3_exec(db, [sql UTF8String], NULL, NULL, &errorOutput);
if (errorOutput != nil)
{
*error = [NSError errorWithDomain:[NSString stringWithUTF8String:errorOutput]
code:responseCode
userInfo:nil];
return false;
}
return true;
}
Please note that there are many features missing from executeBatch which make it unsuitable for a lot of purposes. Specifically, it doesn't check to see if the database is locked, it doesn't make sure FMDatabase itself isn't locked, it doesn't support statement caching.
If you need that, the above is a good starting point to code it yourself. Happy hacking!
FMDB v2.3 now has a native wrapper for sqlite3_exec called executeStatements:
BOOL success;
NSString *sql = #"create table bulktest1 (id integer primary key autoincrement, x text);"
"create table bulktest2 (id integer primary key autoincrement, y text);"
"create table bulktest3 (id integer primary key autoincrement, z text);"
"insert into bulktest1 (x) values ('XXX');"
"insert into bulktest2 (y) values ('YYY');"
"insert into bulktest3 (z) values ('ZZZ');";
success = [db executeStatements:sql];
It also has a variant that employs the sqlite3_exec callback, implemented as a block:
sql = #"select count(*) as count from bulktest1;"
"select count(*) as count from bulktest2;"
"select count(*) as count from bulktest3;";
success = [db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) {
NSInteger count = [dictionary[#"count"] integerValue];
NSLog(#"Count = %d", count);
return 0; // if you return 0, it continues execution; return non-zero, it stops execution
}];
Split Batch Statement
Add in .h file:
#import "FMSQLStatementSplitter.h"
#import "FMDatabaseQueue.h"
FMSQLStatementSplitter can split batch sql statement into several separated statements, then [FMDatabase executeUpdate:] or other methods can be used to execute each separated statement:
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:databasePath];
NSString *batchStatement = #"insert into ftest values ('hello;');"
#"insert into ftest values ('hi;');"
#"insert into ftest values ('not h!\\\\');"
#"insert into ftest values ('definitely not h!')";
NSArray *statements = [[FMSQLStatementSplitter sharedInstance] statementsFromBatchSqlStatement:batchStatement];
[queue inDatabase:^(FMDatabase *adb) {
for (FMSplittedStatement *sqlittedStatement in statements)
{
[adb executeUpdate:sqlittedStatement.statementString];
}
}];

Resources