So the following code has been bugging me a bit:
$stm = $pdo->prepare("SELECT * FROM urls WHERE account=? AND NOT deleted LIMIT ?, 4");
$stm->execute($user, ($request-1)*4);
Whenever I execute this query it returns this 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', 4' at line 1'
Upon looking it up it seems that using the ? placeholders automatically puts quotes around it unless you specify with a bindParam. Is there any way to fix this without having to use the bingparam function?
This is a known issue and why the limit should not really be used with bound parameters. However, you can overcome this by binding the parameter individually and naming it an int.
$stm = $pdo->prepare("SELECT * FROM urls WHERE account=? AND NOT deleted LIMIT ?, 4");
$stm->bindValue(1, $user);
$stm->bindValue(2, ($request-1)*4), PDO::PARAM_INT);
$stm->execute();
If you read PDOStatement::execute() it is caused because binding on the execute causes all parameters to be bound as strings.
As Your Common Sense pointed out, you can disable emulation mode and let MySQL sort out the placeholders by itself, though this may not work with all DB drivers (though will with MySQL) by:
$pdo->setAttribute( PDO::ATTR_EMULATE_PREPARES, false );
Or you can do an intval or type-cast to an int and put it directly within your statement if you'd prefer to do it that way.
Related
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
as a newbie, I've followed PHP MySQL tutorials advising the use of regular MySQL php functions. However, since I've been told that PDO is the better alternative, I've been converting my code to that. I just ran into the following problem:
$query = $uspdb->prepare("SELECT post_id, is_approved, reports FROM ? WHERE id=? AND ?");
$query->bindValue(1, $table, PDO::PARAM_INT);
$query->bindValue(2, $id, PDO::PARAM_INT);
$query->bindValue(3, checkPermission("comment_moderation"),PDO::PARAM_BOOL);
$query->execute;
$result = $query->fetch(PDO::FETCH_ASSOC);
The first line throws the following PDO exception:
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 '? WHERE id=? AND ?' at line 1
Why is that? I have no idea what could be wrong with the syntax. The tutorial I'm reading tells me that I should be using bindValue or execute(array(stuff)) to add parameters rather than ".$id." and the likes, since it's safer, but this isn't working for whatever reason.
Unfortunately, prepared statement can represent a data literal only. (in case of emulated prepares).
So, a developer have to take care of identifiers oneself - PDO offers no help for this matter.
To make a dynamical identifier safe, one have to follow 2 strict rules:
To format identifier properly. Means
enclose identifier in backticks.
escape backticks inside by doubling them.
To verify it against a hardcoded whitelist.
After the formatting, it is safe to insert the $table variable into query. So, the code would be:
$field = "`".str_replace("`","``",$field)."`";
$sql = "SELECT * FROM t ORDER BY $field";
However, although such a formatting would be enough for the cases like ORDER BY, for the most other cases there is a possibility for a different sort of injection: letting a user to choose a table or a field they can see, we may reveal some sensitive information, like password or other personal data. So, it's always better to check dynamical identifiers against a list of allowed values. Here is a brief example:
$allowed = array("name","price","qty");
$key = array_search($_GET['field'], $allowed));
if ($key === false) {
throw new Exception('Wrong field name');
}
$field = $allowed[$key];
$query = "SELECT $field FROM t"; //value is safe
As is typical, I solve my problem seconds after posing the question.
The problem is that you can only bind key values like this, not table or column names. I'll have to keep inserting the table and column names manually just as before:
$query = $uspdb->prepare("SELECT post_id, is_approved, reports FROM $table WHERE id=? AND ?");
$query->execute(array($id,checkPermission("comment_moderation")));
$result = $query->fetch(PDO::FETCH_ASSOC);
If the table or column name is left to the user's discretion, you should go through additional steps to sanitize it, which are detailed in Your Common Sense's response above. In my case it was the following code:
$type = $_GET[type];
switch($type) {
case "review":
$table = "site_cmt_reviews";
break;
default:
$table = "site_cmt_articles";
}
Still, thanks for reading!
I use the following sql to get the value value of the field que_id of a specific line of my table and it is working fine. Do note that que_id(auto-incremented) and line numbers is not the same.
$qry_que_getid = $connexion->query('SELECT somefield FROM table ORDER BY somefield ASC LIMIT '.$lineNumberSeeked.', 1');
$row = $qry_que_getid->fetch(PDO::FETCH_ASSOC);
echo $row['que_id'];
When I try to transform that query into a prepared query as follows I have an error and I do not get it:
$qry_que_getid = $connexion->prepare('SELECT somefield FROM table ORDER BY somefield ASC LIMIT ?, 1');
$qry_que_getid->execute(array(4));
$row = $qry_que_getid->fetch(PDO::FETCH_ASSOC);
echo $row['que_id'];
I get the following error 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 ''3', 1' at line 1
Hope somene can help me understand. Thank you in advance. Cheers. Marc.
From the PHP manual at http://www.php.net/manual/en/pdostatement.execute.php:
An array of values with as many elements as there are bound parameters in the SQL statement being executed. All values are treated as PDO::PARAM_STR.
The LIMIT clause is expecting an integer I believe so you should use the bindParam() method instead.
$limit = 4;
$qry_que_getid->bindParam(1, $limit, PDO::PARAM_INT);
$qry_que_getid->execute();
Otherwise, the parameter will be passed as type PDO::PARAM_STR instead of the expected PDO::PARAM_INT.
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
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"