cannot use prepared statements [duplicate] - php

I'm having problems using params in the ORDER BY section of my SQL. It doesn't issue any warnings, but prints out nothing.
$order = 'columnName';
$direction = 'ASC';
$stmt = $db->prepare("SELECT field from table WHERE column = :my_param ORDER BY :order :direction");
$stmt->bindParam(':my_param', $is_live, PDO::PARAM_STR);
$stmt->bindParam(':order', $order, PDO::PARAM_STR);
$stmt->bindParam(':direction', $direction, PDO::PARAM_STR);
$stmt->execute();
The :my_param works, but not :order or :direction. Is it not being internally escaped correctly? Am I stuck inserting it directly in the SQL? Like so:
$order = 'columnName';
$direction = 'ASC';
$stmt = $db->prepare("SELECT * from table WHERE column = :my_param ORDER BY $order $direction");
Is there a PDO::PARAM_COLUMN_NAME constant or some equivalent?
Thanks!

Yes, you're stuck inserting it directly in the SQL. With some precautions, of course. Every operator/identifier must be hardcoded in your script, like this:
$orders=array("name","price","qty");
$key=array_search($_GET['sort'],$orders);
$order=$orders[$key];
$query="SELECT * from table WHERE is_live = :is_live ORDER BY $order";
Same for the direction.
I wrote a whitelisting helper function to be used in such cases, it greatly reduces the amount of code that needs to be written:
$order = white_list($order, ["name","price","qty"], "Invalid field name");
$direction = white_list($direction, ["ASC","DESC"], "Invalid ORDER BY direction");
$sql = "SELECT field from table WHERE column = ? ORDER BY $order $direction";
$stmt = $db->prepare($sql);
$stmt->execute([$is_live]);
The idea here is to check the value and raise an error in case it is not correct.

I don't think you can :
Use placeholders in an order by clause
Bind column names : you can only bind values -- or variables, and have their value injected in the prepared statement.

It's possible use prepared statements in ORDER BY clause, unfortunately you need pass the order of column insted of the name and is required set PDO_PARAM_INT with type.
In MySQL you can get the order of columns with this query:
SELECT column_name, ordinal_position FROM information_schema.columns
WHERE table_name = 'table' and table_schema = 'database'
PHP code:
$order = 2;
$stmt = $db->prepare("SELECT field from table WHERE column = :param ORDER BY :order DESC");
$stmt->bindParam(':param', $is_live, PDO::PARAM_STR);
$stmt->bindParam(':order', $order, PDO::PARAM_INT);
$stmt->execute();

I don't think you can get ASC/DESC as part of the prepared statement, but the column you can if you list them all in the sql query like so:
// Validate between 2 possible values:
$sortDir = isset($_GET['sortDir']) && $_GET['sortDir'] === 'ASC' ? 'ASC' : 'DESC';
$sql = "
...
order
by
case :orderByCol
when 'email' then email
when 'age' then age
else surname
end
$sortDir
";
$stmt = $db->prepare($sql);
$stmt->bindParam(':orderByCol', $someColumn);
$stmt->execute();
Since ASC/DESC is only two possible values, you can easily validate and select between them as hardcoded values using php code.
You could also make use of the ELT(FIELD(,,,,,),,,,,) functions for this, but then ordering will always be done as a string, even if the column is a numeric data type that should be sorted using numeric semantics / collation.

Unfortunely I guess you could not make it with prepared statements.
It would make it no cacheable since different columns may have values that could be sorted with special sorting strategies.
Create query by using standard escapes and execute it directly.

It is possible . You can use number instead of field name in the 'order by' clause. This is a number starting from 1 and is in the order of field names in the query. And you can concatenate a string in for ASC or DESC. For example
"Select col1,col2,col3 from tab1 order by ? " + strDesc + " limit 10,5".
strDesc=" ASC" / " DESC".

If I'm not entirely mistaken, Pascal is right.
The only binding possible in PDO is the binding of values, as you did with the ':my_param' parameter.
However, there's no harm done in:
$stmt = $db->prepare("SELECT field from table WHERE column = :my_param ORDER BY ".$order ." ".$direction);
$stmt->bindParam(':my_param', $is_live, PDO::PARAM_STR);
$stmt->execute();
The only thing to take notice of would be the correct escaping of $order and $direction, but since you set them manually and didn't set them via user input, I think you're all set.

Related

How to 'order by' prepared select statement without making a second search

Is there a way to order the prepared sql without preparing another select?
$stmt = $conexao->stmt_init();
$stmt->prepare("SELECT * FROM esc_usuarios WHERE usu_codigo = ?");
$stmt->bind_param("s", $usu_codigo);
$stmt->execute();
I wanna get the data from usu_datacadastro and then order:
SELECT * FROM esc_usuarios WHERE usu_indicador_codigo = ? ORDER BY usu_datacadastro DESC LIMIT 5
But I already have this data provided by the first sql, I just need to do the order by and echo somewhere.
As mentioned, something like this might help you http://php.net/manual/en/function.usort.php
usort(
$data,
function($arr1, $arr2) {
// descending
return strcmp($arr2['usu_datacadastro'], $arr1['usu_datacadastro']);
});
If you want to preserve the original order of the array, I would suggest adding pointers/references to an array in the order you desire, as opposed to cloning the array.

PDO adds the apostrophe to the mySQL query

After years of reading it's time to ask first question :)
My problem is that after migrating the code from mySQLi to PDO we have got a problem as it seems PDO adds the apostrophes to the query.
PHP code goes like that:
$sort = $_GET['sort']; << table column name (mySQL VARCHAR only columns)
....
$query = 'SELECT * FROM table WHERE xxx > 0';
$query .= ' ORDER BY :sort ASC ;';
$qry_result= $db->prepare($query);
$qry_result->execute(array(':sort'=>$sort));
mysqli version went smoothly but now queries (mysql log file) looks like this:
SELECT * FROM table where xxx > 0 ORDER BY 'SORT_VAR_VALUE' ASC;
^ 2 problems ^
So the table is NOT sorted, as sort order (from mySQL point of view) is wrong.
phpinfo() does not get any results for search on "magic" nor "quotes" btw.
Any idea ??
The placeholders in PDO statements are for values only. If you want to add actual SQL to the query you need to do it another way.
First, you should sanitize $sort and surround it with backticks in the query.
$sort = preg_replace('/^[a-zA-Z0-9_]/', '', $sort);
Then you could double quote the query string and PHP will replace $sort with it's value for you:
$query = "SELECT * FROM table WHERE xxx > 0 ORDER BY `$sort` ASC";
Or you could replace it with preg_replace like so:
$query = 'SELECT * FROM table WHERE xxx > 0 ORDER BY `:sort` ASC';
$query = preg_replace('/:sort/', $sort, $query, 1);
I would use the preg_replace method because it allows you to reuse the query if you assign the results from preg_replace to another variable instead of overwriting the original variable.
by default pdo binds values as strings.
To fix this you will want to check that the column is actually a valid name and then add it to the query, you can do it the following way:
function validName($string){
return !preg_match("/[^a-zA-Z0-9\$_\.]/i", $string);
}
if(validName($sort)){
$db->prepare("SELECT * FROM table where xxx > 0 ORDER BY $sort ASC");
}
With PDO it's not possible to bind other things that variables in the WHERE statement. So you have to hard code the names of the columns you order by.
See How do I set ORDER BY params using prepared PDO statement?
or Can PHP PDO Statements accept the table or column name as parameter? for further explanations.

PDO compare 2 fields

probably a simple case. Basically I have a Database with 5 fields, which are showing Orders and their progress:
idRefs, Quantity, refsDone, refsRunning, refsUntouched
Now I want to select all those Rows, where Quantity = refsDone (this means the order is done). I am wondering how I can do this using PDO ? This is what I've tried:
$filter = $_POST['filter'];
$limiter = $_POST['limiter'];
$refid = $_POST['refid'];
if($refid == "") $refid="%";
case("finished"):
$sql = "SELECT * FROM Orders WHERE REFID LIKE :refid AND quantity=refsDone ORDER BY dateAdded DESC LIMIT :min,:max";
$ps_orders = $db->prepare($sql);
$ps_orders->bindParam(':refid', $refid, PDO::PARAM_STR);
$ps_orders->bindParam(':min', $min, PDO::PARAM_INT);
$ps_orders->bindParam(':max', $limiter, PDO::PARAM_INT);
$ps_orders->execute();
$rowCount = $ps_orders->fetchColumn();
$data = $ps_orders->fetchAll();
break;
Cant say whats happening wrong, but I am kinda sure it is the part "AND quantity=refsDone" which causes it.
I tested your query in this SQLFiddle: http://sqlfiddle.com/#!2/32ae40/8
The query returns an empty set in several cases:
if $refid is null
if $refid is empty string ('')
if $min is 1 or more
if $limiter is 0
I think you should make sure your variables are values you expect them to be, because if they aren't the query is likely to return an empty set.
You can troubleshoot parameterized queries yourself by enabling the MySQL general query log, which logs both the prepared query and the query with parameter values included.

PDO bindParam not working as expected

This has been bugging for a long time, and I still can't figure out what i'm doing wrong.
In the code I want to select a few users with a comma separated string. The string will always be legit and valid.
In the first example, the one I would like to use, uses bindParam to assign the value of $postId to the SQL query. I have been using bindParam() for lots of other calls, but in this specific case, it fails.
$postId = "1,2,3";
$stm = $this->db->prepare('SELECT * FROM posts WHERE find_in_set(userId, "?") ORDER BY id DESC');
$stm->bindParam(1, $postId, PDO::PARAM_STR);
$stm->setFetchMode(PDO::FETCH_ASSOC);
$stm->execute();
$results = $stm->fetchAll();
return print_r($results,true);
This code returns:
array (
)
In this other code which I really wouldn't like to use, I just pass the value of $postId right into the sql query.
$stm = $this->db->prepare('SELECT * FROM posts WHERE find_in_set(userId, "'.$postId.'") ORDER BY id DESC');
$stm->setFetchMode(PDO::FETCH_ASSOC);
$stm->execute();
$results = $stm->fetchAll();
return print_r($results,true);
This code returns all the rows it is supposed to retrieve.
My question is; What is the specific problem and how can I avoid doing this again?
You shouldn't have quotes around the placeholder in yout query:
$stm = $this->db->prepare('SELECT * FROM posts WHERE find_in_set(userId, ?) ORDER BY id DESC');
See additional docs here.
Although it's not directly related to the question, it's also a handy habit to get into to use named params. When you have only one param to pass, it's not too bad, but when you start getting five or so question marks in the query, it's MUCH easier to actually read if you used named params:
SELECT * FROM posts WHERE find_in_set(userId, :someID) ORDER BY id DESC
Then you bind them as named params in your code:
$sth->bindParam(':someID', $postId, PDO::PARAM_STR);
You don't need to add the double quotes "?" when referencing the value
'SELECT * FROM posts WHERE find_in_set(userId, "?") ORDER BY id DESC'
Should be
'SELECT * FROM posts WHERE find_in_set(userId, ?) ORDER BY id DESC'

PHP PDO custom sql query preparation

I'm having a little problem getting a sql query with prepare on PDO, I have this code:
$portfolio = $db->prepare("SELECT * FROM `news`, `:sub` WHERE `news`.`id` = `:sub`.`id_news` AND `page` = `:under` ORDER BY `date` DESC LIMIT :start, :limit");
$portfolio->bindParam(':under', $_GET['under'], PDO::PARAM_STR);
$portfolio->bindParam(':sub', $_GET['sub'], PDO::PARAM_STR);
$portfolio->bindParam(':start', $start, PDO::PARAM_INT);
$portfolio->bindParam(':limit', $limit, PDO::PARAM_INT);
$portfolio->execute();
But this doesn't give any value and my DB has the values correct, any one knows why this doesn't work?
PS: var $start and $limit are fine, no problem with it cuz it's pagination script, that work very fine in all the pages.
For exemple i'm in the url: mysite.com/index.php?sub=vid&under=info
so the query should be like this:
"SELECT * FROM `news`, `vid` WHERE `news`.`id` = `vid`.`id_news` AND `page` = `info` ORDER BY `date` DESC LIMIT 0, 10"
So for what i understood having this code before should work and still be safe right?
switch($_GET['sub']){
case "vid":
$table = "vid";
break;
case "img":
$table = "img";
break;
}
$portfolio = $db->prepare("SELECT * FROM `news`, `$table` WHERE `news`.`id` = `$table`.`id_news` AND `page` = :under ORDER BY `date` DESC LIMIT :start, :limit");
You can't use query parameter placeholders for table names or column names.
Use query parameters only to substitute for a literal value in an expression. I.e. a quoted string, quoted date, or numeric value.
Also, even if you are using a parameter for a string or date, the parameter doesn't go inside quotes.
To make table names or column names dynamic, you have to interpolate application variables into your SQL string before you submit the string to prepare().
But be careful to validate user input (e.g. $_GET variables) so you avoid SQL injection. For instance, test the input against a list of known legitimate table names.
Example:
$subtables = array(
"DEFAULT" => "text",
"text" => "text",
"vid" => "vid",
"pic" => "pic"
);
// if the key exists, use the table name, else use the default table name
$subtable = $subtables[ $_GET["sub"] ] ?: $subtables[ "DEFAULT" ];
// now $subtable is effectively whitelisted, and it is safe to use
// without risk of SQL injection
$portfolio = $db->prepare("SELECT *
FROM news, `$subtable` AS sub
WHERE news.id = sub.id_news
AND page = :under
ORDER BY date DESC LIMIT :start, :limit");
You can't use parameters for the names of tables and table object (i.e fields). See this question where this is covered.
Can PHP PDO Statements accept the table or column name as parameter?
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute( PDO::ATTR_EMULATE_PREPARES, false );
are two magical lines that will solve all your minor problems after you fix the BIGGEST one - a dynamically linked table.
What it have to be is a single table where "sub" is a field name to distinguish a category
SELECT * FROM news n, subnews s
WHERE n.id = id_news AND s.sub =:sub AND `page` = :under
ORDER BY `date` DESC LIMIT :start, :limit
Also you have to quit that habit of wrapping in backticks everything that moves.

Categories