PHP PDO prepared query refuses to execute properly - escaping problem? - php

I'm having a problem with a query prepared in PHP with PDO. The code:
$link = new PDO("mysql:dbname=$dbname;host=127.0.0.1",$username,$password);
$link->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$query = $link->prepare("SELECT locality_name FROM :passedday GROUP BY locality_name ORDER BY locality_name DESC");
$query->bindParam(":passedday",$day); //Where day is, well, a day passed to the script elsewhere
$query->execute();
$result = $query->fetchAll();
$link = null;
//Do things with the $result.
The error message I am getting is:
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''05_26_09' GROUP BY locality_name ORDER BY locality_name DESC' at line 1
When I execute the query on the server directly, it returns the appropriate result set without any problem. Any ideas what I'm doing wrong?
TIA.
Edit:
$day is passed as a GET argument. So, http://127.0.0.1/day.php?day=05_26_09 leads to $day = $_GET['day'];.

If 05_26_09 is supposed to bet the table's name, then I guess you've an escaping problem. Is your local operating system different from the live server?
I don't think you can use bindValue()/bindParam() for something else than values (eg. table name, field name). So I'm a bit suprised, that it works on your local system.

PDO uses mysql's C-API for prepared statements.
http://dev.mysql.com/doc/refman/5.0/en/mysql-stmt-prepare.html says:The markers are legal only in certain places in SQL statements. [...] However, they are not allowed for identifiers (such as table or column names)As a rule of thumb I use: "if you can't wrap it in single-quotes in an ad-hoc query string you can't parametrize it in a prepared statement"

Related

Why can't I bind a variable to a 'SHOW VARIABLE LIKE' query with PDO/MySQL?

I've been digging over the PHP PDO documentation and I can't figure out why this query is failing.
Here's the query:
$var = 'information_schema_stats_expiry';
$stmt = $pdo->prepare('SHOW VARIABLES LIKE :var');
$stmt->execute([':var' => $var]);
When executed I get this error:
PDOException: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '?' at line 1
I know I can work around it with slightly altered queries. These are a few I've tested that do work.
$var = 'information_schema_stats_expiry';
$stmt = $this->pdo->prepare('SHOW VARIABLES LIKE "' . $var . '"');
$stmt->execute();
Or,
$var = 'information_schema_stats_expiry';
$stmt = $this->pdo->prepare('SHOW VARIABLES WHERE variable_name=:var');
$stmt->execute([':var' => $var]);
Or, the same as above, but with
SHOW VARIABLES WHERE variable_name LIKE :var
However, I'm trying to understand why the first query doesn't work. It reminds me of the issues where people try to bind a variable for a LIMIT clause, but it fails because the number gets quoted; but, in this case, the variable name needs to be quoted, so I would think the query would work fine. Is this a bug, or is there a documented reason why this query would be failing?
To sum up, this query works fine when I run it directly through a MySQL client, or as a static query in PHP:
SHOW VARIABLES LIKE "information_schema_stats_expiry"
However, if I try to make a prepared statement with the exact same syntax, it it will fail:
SHOW VARIABLES LIKE :var
Is there any obvious reason this isn't working?
This is running on PHP 8 with MySQL 8.
It's a MySQL limitation. Not all statements can be prepared. (There is a PREPARE syntax which sets up parameter binding, which we don't usually see in PHP/PDO. You ought to be getting a warning of sorts.)
SHOW VARIABLES is definitely exempt:
https://dev.mysql.com/doc/refman/8.0/en/sql-prepared-statements.html#prepared-statements-permitted

PHP - MySQLi - Insert with SELECT at the end not working

I have this function in PHP. I am trying to insert (if it's necessary) and then get the app_id from the table.
private function addApp($bundle_identifier,$os_id) {
$driver = new mysqli_driver();
$driver->report_mode = MYSQLI_REPORT_ALL;
//Insert or update app details
if ($stmt = $this->db->prepare("INSERT IGNORE INTO app (app_identifier,os_id) VALUES (?,?); SELECT app_id FROM app WHERE app_identifier = ? AND os_id = ?")){
$stmt->bind_param("ssss", $bundle_identifier,$os_id,$bundle_identifier,$os_id);
$stmt->execute();
$stmt->bind_result($app_id);
if (!isset($app_id)) {
echo "is set";
$app_id=$stmt->insert_id;
}
}
if($this->db->commit()){
return $app_id;
}
return 0;
}
The issue here is that stmt is always false with the error:
Uncaught exception 'mysqli_sql_exception' with message 'You have an
error in your SQL syntax; check the manual that corresponds to your
MySQL server version for the right syntax to use near 'SELECT app_id
FROM app WHERE app_identifier = ? AND os_id = ?' at line 1'
The weird thing is that this query works fine in my SQL.
Is that a limitation of mysqli?
According to http://php.net/manual/en/mysqli.prepare.php :
The query must consist of a single SQL statement.
Which basically answers your question. You have to use two db calls for two queries. Or use something like http://php.net/manual/en/mysqli.multi-query.php
The below is kept for information only as it refers PDO, while the question is about mysqli. It's generally useful though.
I think the reason for this working in mysql, but not in mysqli is that the latter supports prepared statements natively, while the former uses emulation. As your expression contains two queries, all bound parameters are given by driver to the first query (out of which is uses two and discards the other two). The second query then gets no parameters and therefore question marks are syntax errors. With prepared statements emulation PHP actually substitutes question marks with the properly escaped values and so forms two valid queries.
You can enable emulation using $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, true), however, this may slightly affect performance.
See also http://www.php.net/manual/en/pdo.setattribute.php

PDO bindParam/Value not working

I am trying to get a database query page to work but cant seem to do so.
my Code so far (here I tried bindValue, but previously tried bindParam and got the same result):
var_dump($_POST);
$dbh = new PDO ("mysql:host=$myServer;dbname=$myDB", $myUser, $myPw);
$columName = $_POST["columName"];
$tblName = $_POST["tblName"];
$valueName = $_POST["valueName"];
$specificValue = $_POST["specificValue"];
$stmt = $dbh->prepare("SELECT :columName FROM :tblName Where :valueName = :specificValue");
$stmt->bindValue(":columName", $columName);
$stmt->bindValue(":valueName", $valueName);
$stmt->bindValue(":tblName", $tblName);
$stmt->bindValue(":specificValue", $specificValue);
$stmt->execute();
$result = $stmt->fetch();
if(empty($result)){echo "empty";}
print_r ($stmt);
print_r($result);
Printing result and $stmt brings following results:
empty
PDOStatement Object ( [queryString] => SELECT :columName FROM :tblName Where :valueName = :specificValue )
What did I do wrong? What could I try to get it to work?
I am new to the whole coding thing, so please ask if I forgot any code or other important information!
Thanks!
Placeholder parameters can only represent VALUES in the query. Tables, field names, sql key words, etc.. are all impossible to use placeholders on.
If you need to build a dynamic query and replace field/table names, then you'll have to use good old string construction methods, and be aware that you'll be opening yourself to SQL injection attacks again:
$sql = "SELECT * FROM $foo WHERE $bar = :baz";
$stmt = $dbh->prepare($sql);
$stmt->bindValue(':baz', $baz);
I'm afraid you need to rethink how parameterised queries work. It's not just a case of magically inserting data in a safe way. It's about distingushing between the structure of a query and the data.
So the database name, the column names, the table names and any SQL keywords are part of the structure of the query. Every time you run the query, they will be the same.
The data, however, can change between running the query.
So the structure needs to be in place when the query is prepared. However, you obviously can't just plonk the $columName variable etc into the query for SQL injection reasons. If you really need to have flexible queries like this (nb that you probably don't) you need to create a whitelist of allowed values, either in your code or retrieved from the database.
Your query is invalid (you're using parameters for object identifiers) but you are not getting any notification because you have neither configured PDO to throw exceptions nor are calling the error check functions manually.
Add the PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION option to PDO's constructor:
$dbh = new PDO ("mysql:host=$myServer;dbname=$myDB", $myUser, $myPw, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
Once you do so, you'll get a prompt exception on the exact issue, e.g.:
PDOException: SQLSTATE[42000]: Syntax error or access violation: 1064
You have an error in your SQL syntax; check the manual that
corresponds to your MySQL server version for the right syntax to use
near ''user' Where 'login' = 'john'' at line 1 in [...]
As you can see, this is trying to run a query like (e.g.):
SELECT 'user_id'
FROM 'user'
Where 'login' = 'john'
Additionally, beware of SQL injection. It's terribly unsafe to compose SQL queries using data from $_POST.

Select Table (FROM) by string

Is it possible, to use a dynamic value inside a SELECT FROM sql command?
Database->prepare("SELECT bomb FROM ? WHERE id=?")
->execute($strTable,$strID);
result:
Fatal error: Uncaught exception Exception with message Query error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near...
No. The table name cannot be a parameter of the query. You'd need to construct your query string first, concatenating the table name in.
Assuming $strTable is from a safe source just use
Database->prepare("SELECT bomb FROM $strTable WHERE id=?")
->execute($strID);
Your missing te bindParam statements. I don't think you can do the table name other than doing a php variable like #Alex did. Here is code for a prepared statement:
<?php
$stmt = $dbh->prepare("SELECT bomb FROM $tablename WHERE id=:strID");
$stmt->bindParam(':strID', $id);
$stmt->execute();
?>
PHP Manaual refernce: http://php.net/manual/en/pdo.prepared-statements.php
usually those database abstraction layer relies on prepared statement to handle the place holder feature. The prepared statement feature of the RDBM compile the query, I don't think it's possible to prepare the query if the table is not specified.
You should use the escapement function of your library and include the table name in the query.
example:
$tablename = escapement_function($strTable);
Database->prepare("SELECT bomb FROM {$tablename} WHERE id=?")
->execute($strID);

How do I use pdo's prepared statement for order by and limit clauses?

I want to use a prepared statement in which the passed-in parameters are for the ORDER BY and LIMIT clauses, like so:
$sql = 'SELECT * FROM table ORDER BY :sort :dir LIMIT :start, :results';
$stmt = $dbh->prepare($sql);
$stmt->execute(array(
'sort' => $_GET['sort'],
'dir' => $_GET['dir'],
'start' => $_GET['start'],
'results' => $_GET['results'],
)
);
But $stmt->fetchAll(PDO::FETCH_ASSOC); returns nothing.
Can someone point out what's the wrong thing I am doing? Can it be done? If not,what should I reference for a complete list of clauses where parameters can be used?
After using :
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
I got the message :
Uncaught exception 'PDOException' with
message 'SQLSTATE[42000]: Syntax error
or access violation: 1064 You have an
error in your SQL syntax; check the
manual that corresponds to your MySQL
server version for the right syntax to
use near ''0', '10'' at line 1
So, when you use an array for execute, it consider your inputs as string which is not a good idea for LIMIT
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql = "SELECT * FROM table ORDER BY :sort :dir LIMIT :start, :results";
$stmt = $dbh->prepare($sql);
$stmt->bindParam(':start', $_GET['start'], PDO::PARAM_INT);
$stmt->bindParam(':results', $_GET['results'], PDO::PARAM_INT);
$stmt->bindParam(':sort', $_GET['sort']);
$stmt->bindParam(':dir', $_GET['dir']);
$stmt->execute();
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
print_r($data);
Prepared statements allow the DBMS to generate a query plan for your query before actually executing the query for your supplied parameters. Changing the fields for ORDER BY requires a different query plan, because ordering you data in different ways can drastically affect how the DBMS might choose to get the data: for instance, certain indexes may help in one case but not in another. For this reason the ORDER BY fields should form part of the SQL string passed into the prepare() method, rather than being bound to the query prior to execute().
As for the LIMIT clause, it's not clear whether its parameters would affect the query plan, so these may be bound later, possibly depending upon your DBMS. According to this SO answer it should be allowed.
You can't bind a parameter to specify a language keyword or a field name - it has to be replacing a literal. Therefore, your limit values I think are fine, but your order by is not. It will be best for you to manually replace sort and dir in the string. Escape them but don't use the DB tools to do so, since they aren't string literals. Basically ensure no special characters are present.
Although this question is rather old, I think it might still be of interest. For me it worked after I
used bindParam in combination with PDO::PARAM_INT like suggested before
converted the variable content into an integer value by invoking intval()
The relevant part of the code then looks like this:
$stmt->bindParam(':start', intval($_GET['start']), PDO::PARAM_INT);
$stmt->bindParam(':number', intval($_GET['number']), PDO::PARAM_INT);
Without using intval() I also received the error Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''0', 10' at line 1

Categories