sqllite3 bind to text swift project - swift

I am having a problem accessing sqlite3 database in an xcode swift project. Specifically when binding host parameters in a prepared sql statement to a text (string) value the bind always binds all ? rather than just one. So the results are not what is expected. (NOTE: I am just using the builtin sqlite3 lib with bridging header file specified, not any 3rd party wrappers)
For example: my test database table has a column named str1 whose values are years such as "2010", "2011", etc (not Integers, but Strings). If I use the SQL statement:
SELECT * FROM table1 WHERE str1 BETWEEN '2011' AND '2012'
it gives me all the rows that I expected to get. But if I use the statement:
SELECT * FROM table1 WHERE str1 BETWEEN ? AND ?
and bind the prepared statement with:
sqlite3_bind_text(statement, 1, "2011", -1, nil)
sqlite3_bind_text(statement, 2, "2012", -1, nil)
it returns only the rows for "2012". The prepare was done with:
sqlite3_prepare_v2(db, sql, -1, &statement, tail)
So, the bind is somewhat working, but it seems to always replace ALL ? with the value rather than just one. (It works no differently if I use unique parameter names such as ?1 and ?2 rather than just ? ?)
So, my question is: how to bind sqlite3 host parameter to Swift string value?

Looking at the source code of GRDB.swift (https://github.com/groue/GRDB.swift/blob/v2.9.0/GRDB/Core/Statement.swift#L179 and https://github.com/groue/GRDB.swift/blob/v2.9.0/GRDB/Core/Database.swift#L14) I think the following code will work:
let SQLITE_TRANSIENT = unsafeBitCast(OpaquePointer(bitPattern: -1), to: sqlite3_destructor_type.self)
sqlite3_bind_text(statement, index, string, -1, SQLITE_TRANSIENT)

Related

sqlite3 update not working in ios

I am trying to update sqlite db. This is the code I am using
if(sqlite3_open([dbPath UTF8String], &database) == SQLITE_OK)
{
const char * sql;
sql = "update tms set name = ?,place=?,stars=? where id=?";
sqlite3_stmt *selectStatement;
//prepare the select statement
int returnValue = sqlite3_prepare_v2(database, sql, -1, &selectStatement, NULL);
if(returnValue == SQLITE_OK)
{
sqlite3_bind_text(selectStatement, 1,[[payloadDict valueForKey:#"userName"] UTF8String] , [[payloadDict valueForKey:#"userName"] length],SQLITE_STATIC);
sqlite3_bind_text(selectStatement, 2,[[payloadDict valueForKey:#"locName"] UTF8String], [[payloadDict valueForKey:#"locName"] length],SQLITE_STATIC);
sqlite3_bind_int(selectStatement, 3, [[payloadDict valueForKey:#"starCount"] integerValue]);
sqlite3_bind_int(selectStatement, 4, [[payloadDict valueForKey:#"rowid"] integerValue]);
int success = sqlite3_step(selectStatement);
if(success == SQLITE_DONE)
{
isExist = TRUE;
}
else {
//NSAssert1(0,#"Error: Failed to Update %s",sqlite3_errmsg(database));
}
}
I am getting value 101 as success when sqlite3_step is executed. But database is not updated with new values.
How can I do this properly?
Thanks
I agree with #ott's excellent suggestion of making sure the database is located in the Documents directory (though I would have thought that that would have given you an error).
I'd also double check the value returned by [[payloadDict valueForKey:#"rowid"] integerValue] to make sure it matches a value in the id column for one of the existing rows in your table. If it doesn't match anything, sqlite3_step will return SQLITE_DONE even if nothing was updated.
Also note that you might also want to make sure that the id values are stored as numeric values, not text strings as sqlite is pretty lax about letting you store values in whatever data type you originally specified when you first inserted the data, regardless of how the table was defined), and I'm not sure if a WHERE clause looking for a numeric match will succeed if the data was originally stored as a text value. If you used an id column definition like id INTEGER PRIMARY KEY AUTOINCREMENT, where the system defined the values automatically for you, this isn't an issue, but if you manually populated the id column, it might be something to double check. (Generally it does a pretty good job in interpreting strings as numbers on the fly, but there are some weird situations that are problematic: For example, if you stored a string value of "5,127" in a numeric field, if you later then try to retrieve its numeric value, sqlite won't know what to do with the comma in the text value "5,127" and will interpret the numeric value as 5, not as 5127.)

sqlite3_bind_parameter_index returns 0?

The count returns a count of the parameters and is good. However the index is returning 0.
Any ideas?
sqlite3 *database;
sqlite3_stmt *updateStmt;
int ID;
const char *sql;
sql = "update User set Name = ? , Dev = ?,ActiveLevel = ? Where _id = ?";
if(sqlite3_prepare_v2(database, sql, -1, &updateStmt, NULL) != SQLITE_OK)
NSAssert1(0, #"Error while creating update statement. '%s'", sqlite3_errmsg(database));
NSLog(#"count %d",sqlite3_bind_parameter_count(updateStmt));
NSLog(#"Index %d",sqlite3_bind_parameter_index(updateStmt,"ActiveLevel"));
From the fine manual:
int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName);
Return the index of an SQL parameter given its name.
And for parameters:
?
A question mark that is not followed by a number creates a parameter with a number one greater than the largest parameter number already assigned.
...
:AAAA
A colon followed by an identifier name holds a spot for a named parameter with the name :AAAA.
Emphasis mine in the second section.
Your SQL doesn't have any named parameters at all, you just have plain old placeholders. The parameter name is the name of the placeholder (:AAAA), not the name of the column in question; remember that you can use placeholders in places where no name could be automatically derived so you have to name them yourself.
If you want to use ActiveLevel as a named parameter, then your SQL should look like this:
update User set Name = ? , Dev = ?, ActiveLevel = :ActiveLevel Where _id = ?
And you'd probably want to replace the other placeholders (?) with named parameters for consistency.

Sorting selection results using a bound parameter in sqlite with the C/C++ API

I'm wondering if it's possible to parameterize the sort column in sqlite.
I'd like to prepare a single sqlite3_stmt like this (omitting checking the return codes for simplicity's sake).
sqlite3_stmt* the_statement;
const char *sql = "SELECT column1, column2, column3 FROM table1 WHERE column4 = ? ORDER BY ? ASC"
sqlite3_prepare_v2(database, sql, -1, &the_statement, NULL)
and use it like this
sqlite3_bind_int(the_statement, 1, 1);
sqlite3_bind_int(the_statement, 2, COLUMN_TO_SORT_BY);
while (sqlite3_step(the_statement) == SQLITE_ROW){
//Do something with each row.
}
sqlite3_reset()
When I execute this code changing the value of COLUMN_TO_SORT_BY from 1 to 3 my results are always returned in the same order (I think the order they are stored in the table, but it could also be as sorted by column1).
So, my question is: Can you parameterize the ORDER BY argument in a sqlite expression when using the C API?
The problem is that the value you pass in is being treated as an expression, which evaluates to a fixed value - '1' in the first case, '3' in the second. You could compose a CASE statement to order by, but it could get unwieldy quickly. Perhaps something like:
ORDER BY
CASE ?
WHEN 1 THEN column1
WHEN 2 THEN column2
WHEN 3 THEN column3
END
The CASE ... END expression isn't necessary, since the SQLite SELECT docs mention:
If the ORDER BY expression is a
constant integer K then the expression
is considered an alias for the K-th
column of the result set (columns are
numbered from left to right starting
with 1).
So simply use the following statement:
SELECT column1, column2, column3 FROM sometable ORDER BY ?
And bind the column index:
sqlite3_bind_int(stmt, 1, col_index);

iPhone SQLite Database Reading And Writing

So I am trying to work with SQLite in one of iPhone applications and I am using the sqlite3 library. I am able to access the database and even make a query; in fact the query accesses the exact data but for some reason the string I am getting back is a long integer and not the string I was looking for. Here is the database and code:
Filename: Package.sql
Table Lessons
LessonID VARCHAR(64) Primary Key | LessonName VARCHAR(100) | EntryDate (DATETIME) | Chrono VARCHAR (20)
bfow02nso9xjdo40wksbfkekakoe29ak | Learning The History | 2010-08-05 16:24:35 | 0001
And the iPhone Code
...
-(NSString *)getRow:(NSString *)tablename where:(NSString *)column equals:(NSString *)value {
  const char *query = [[[[[[[#"SELECT * FROM `" stringByAppendingString:tablename] stringByAppendingString:#"` WHERE `"] stringByAppendingString:column] stringByAppendingString:#"` = '"] stringByAppendingString:value] stringByAppendingString:#"';"] cStringUsingEncoding:NSUTF8StringEncoding];
  NSString *result;
  if(sqlite3_open([dbpath UTF8String], &database) == SQLITE_OK) {
    sqlite3_stmt *compiledQuery;
    if(sqlite3_prepare_v2(database, query, -1, &compiledQuery, NULL) == SQLITE_OK) {
      while(sqlite3_step(compiledQuery) == SQLITE_ROW) {
        NSString *str_temp = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledQuery, 2)];
        result = str_temp;
        
      }
      sqlite3_finalize(compiledQuery);
    }
    sqlite3_close(database);
  }
 
  return result;
}
...
When the code executes:
CDatabase *db = [[CDatabase alloc]initWithDatabase:#"Package.sql"];
NSString *result = [db getRow:#"Lessons" where:#"Chrono" equals:#"0001"];
the returned value NSString *result has a value of "1,364,111". Why is it doing that??? It should be "Learning The History"
Are you sure that any of your SQLite calls are successful? You should initialize result to nil so that your function returns nil if any errors are caught.
Three (probably related) issues with your code:
The index to sqlite3_column_text should be zero-based; you're passing 2, which should refer to the third column. You probably mean to pass 1. From the docs:
...the second argument is the index of the column for which information should be returned. The leftmost column of the result set has the index 0.
You really shouldn't use SELECT *. Specify the columns you want!
You should specialize your query by binding values, not by concatenating strings! Your code is rife with the possibility of SQL injections (not to mention incorrect queries).
For example (with no error checking):
const char *query = "SELECT * FROM ? WHERE ?=?";
sqlite3_stmt *compiledQuery;
sqlite3_prepare_v2(database, query, -1, &compiledQuery, NULL);
sqlite3_bind_text(compiledQuery, 1, "Lessons", -1, SQLITE_TRANSIENT);
sqlite3_bind_text(compiledQuery, 2, "Chrono", -1, SQLITE_TRANSIENT);
sqlite3_bind_text(compiledQuery, 3, "0001", -1, SQLITE_TRANSIENT);
Note that the index here is 1-based (I don't know why they do that). From the docs:
The second argument is the index of the SQL parameter to be set. The leftmost SQL parameter has an index of 1.
Haha whoops I realized that I was just displaying the string as a data format by using the %d string format. when i changed it to %# i got the string format

How to append an integer to a const char value in iPhone?

I have SQL query statement which used to display the contents in the table. The SQL statement consist of a where clause which is to be appended with numeric value as 1 ,2 3 etc depends upon the previously selected content. I am having the numeric value as int and I want it to append to SQL statement which is const char. How can I append both the values?
My query is:
select * from Book where id=1;
I have the id value is integer
You simply bind the parameter. E.g.:
sqlite3 *db;
... // open database
sqlite3_stmt *pStmt;
sqlite3_prepare_v2(
db,
"select * from book where id = ?",
-1,
&pStmt,
NULL
);
sqlite3_bind_int(pStmt, 1, bookId);
See the SQLite documentation on compiling and binding prepared statements. You can reuse the same statement more than once. sqlite3_clear_bindings lets you reset the values.
Is this what you're looking for?
NSString* result = [NSString stringWithFormat:#"SELECT * FROM Book WHERE id=%d", myInt];
to compose your SQL string?