Add PDO::PARAM_INT or PDO::PARAM_STR have any meaning in Mysql query?
$sql = 'SELECT TagId FROM tagthread WHERE ThreadId = :ThreadId';
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':ThreadId', $threadid, PDO::PARAM_INT);
$stmt->execute();
Yes, use it.
I did a few tests (with PDO::ATTR_EMULATE_PREPARES false) and I found out that the quotes around the values will be different.
When you bind an integer value with PARAM_INT there will be no quotes in the query (A string value with PARAM_INT has quotes). If you bind an integer value with PDO::PARAM_STR there will be quotes and mysql has to cast to integer.
Examples:
$stmt->bindParam(':ThreadId', $threadid, PDO::PARAM_INT);
$threadid = 123;
// SELECT TagId FROM tagthread WHERE ThreadId = 123
$threadid = '123test';
// SELECT TagId FROM tagthread WHERE ThreadId = '123test'
// mysql will cast 123test to 123
EDIT:
I further tested and read on that topic. Conclusion: Implicit casting is dangerous and can lead to unexpected results. Read more on that here. Another disadvantage to always use PDO::PARAM_STR is the performance. Read more on performance Disadvantages of quoting integers in a Mysql query?
So if your column is of type [TINY|SMALL|MEDIUM|BIG]INT than use PARAM_INT. And in case it is a LIMIT clause than cast to integer if the variable type in PHP is not integer.
Edit: Depends! See Your Common Sense comment below.
If the value is a integer it should be treated as an integer.
Apply this with as many datatypes as possible.
If you don't set the Attribute of PDO::ATTR_EMULATE_PREPARES to false, you will get a nasty error.
Solid example:
$stmt = $dbh->prepare("SELECT * FROM table123 WHERE raw_field = :field LIMIT 1 OFFSET :offset;");
$stmt->bindParam(':field', $field);
$stmt->bindParam(':offset', $offset);
if ($map_stmt->execute())
{
$data = stmt->fetch(PDO::FETCH_ASSOC);
}
else
{
echo 'Error :';
echo '<pre>';
print_r($map_stmt->errorInfo());
print_r($map_stmt->debugDumpParams());
echo '</pre>';
}
Will return back a nasty error containing:
Error Code: 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'' at line 1
Query: SELECT * FROM table123 WHERE raw_field = 'home' LIMIT 1 OFFSET '0'
Useless you treat it as an integer, and it will remove the string (e.g.: ' ').
$stmt->bindParam(':offset', $offset, PDO::PARAM_INT);
In a nutshell:
You choose! Strict data or not..
I cannot tell for all the drivers supported by PDO, but for mysql it's ok not to use PDO::PARAM_INT most of time.
Therefore, it makes no sense to bloat your code with numerous bindParam calls. As a rule, just send your variables directly into execute():
$sql = 'SELECT TagId FROM tagthread WHERE ThreadId = ?';
$stmt = $this->db->prepare($sql);
$stmt->execute([$threadid]);
Here your $threadid variable will be silently bound as a string, but it will make not a single problem for mysql to compare it with integer value stored in database. In reality, everyone does it this way and never has any problem.
The problem with string type bindnig in LIMIT clause can be easily solved by switfhing the emulation mode OFF.
Note that PDO::PARAM_INT doesn't cast your value. Means if you're trying to bind a string type value using this mode, it will be bound as a string nevertheless, even if you explicitly set type to PDO::PARAM_INT. This mode will be actually applied only for integer values.
There are few edge cases where you may want to bind an integer explicitly though:
peculiar column types, like BIGINT or BOOLEAN that require an operand of exact type to be bound (note that in order to bind a BIGINT value with PDO::PARAM_INT you need a mysqlnd-based installation).
some DBAs claim that complex queries with non-trivial query plan can be affected by a wrong operand type. though noone provided a verifiable example yet
All other issues are common for the loose typing and neither mysql nor PDO binding has any special effect in them.
Also, to avoid possible problems you should choose right column types for your data. Say, for big integers you should use BIGINT, while for any price-like data it have to be DECIMAL. And there will be not a single issue with comparison.
Related
From PHP, i am querying MySQL database using PDO. Query is
$id = 5;
$stmt = $con >prepare("select name from employee where id= ? ");
$stmt ->bindValue(1, $id, PDO::PARAM_INT);
This is working as expected and the name of the employee with id 5 is fetched. However from the logs I could see that the query actually executed is
select name from employee where id= '5'
id was int type and binding was done using PDO::PARAM_INT. so the query executed should have been id= 5 and not id= '5'. MySql had to possibly covert string to int due to this,
Is this expected behavior with PDO or is there an error in my understanding?
It's possibly a bug in PHP, this ticket or related to this one. A commit to fix this bug have been submitted (Tue, 11 Oct 2016), wich says :
The prepared statement emulator (pdo_sql_parser.) figures out how to quote
each query parameter. The intended type is specified by the PDO::PARAM_
consts, but this direction wasn't always followed
What is your version of PHP? An update can probably fix it.
A user-contributed note in http://php.net/manual/en/pdostatement.bindvalue.php specifies the following:
"Emulated prepares work more stable in this cases, because they convert everything to strings and just decide whenever to quote argument or not to quote."
Ref: http://php.net/manual/en/pdostatement.bindvalue.php#119956
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!
Hi im trying to fix a bug with my script. The problem is my get query still works with extra letters.
So, edit.php?id=1 works and so does edit.php?id=1hello (obviously it shouldnt). Whats happening?
$idtoedit = mysql_real_escape_string($_GET["id"]);
//Check if ID exists
$doesidexist = mysql_query("SELECT `id` FROM Data WHERE `id` = \"$idtoedit\"");
if (mysql_num_rows($doesidexist) == 0) {
die("<div class=\"alert alert-error\"><h4 class=\"alert-heading\">Error</h4><p>ID does not exist.</p><p><a class=\"btn btn-danger\" href=\"javascript:history.go(-1)\">Go Back</a></p></div></div></body></html>");
}
This is because of MySQL's "anything goes" handling of input, when converting strings to numbers.
The SQL query you build pass the input in as a string (the "s around it) so mysql will try to convert it to a number, if the column on the left hand side of the = has a number type (the id column in your example).
Check this section of the manual for further information.
mysql_real_escape_string only "cleans" the variable so it's safe to use in the query, however it doesn't cast it into an integer and it's not supposed to. You can use PDO to and parameterized queries to do it, otherwise you have to manually cast it yourself i.e.
$idtoedit = (int) mysql_real_escape_string($_GET["id"]);
PDO example:
$sth = $pdoObject->prepare('SELECT `id` FROM Data WHERE `id` = ?');
$sth->bindParam(1, $_GET["id"], PDO::PARAM_INT);
$sth->execute();
Edit:
Haven't really answered your question so to clarify:
Just like complex857 said, MySQL tries to convert it based on the column type, therefore casting 1hello results in an integer value of 1.
The PDOStatement::bindValue() method offers a way to specify the type of the variable bound:
PDOStatement::bindValue ( $parameter , $value [, $data_type = PDO::PARAM_STR ] )
I'm wondering, what's the purpose of specifying the data type, whereas when leaved as default (PARAM_STR) eventually the database will anyway cast the value to the proper type before using it?
For example, if you have these queries over an INTEGER field:
INSERT INTO table (integerField) VALUES (?) ;
SELECT * FROM table WHERE integerField = ? ;
And you bind an integer in PHP, PDO will by default bind it as a string, which is equivalent as:
INSERT INTO table (integerField) VALUES ("1") ;
SELECT * FROM table WHERE integerField = "1" ;
That will work flawlessly, because the SQL database (at least MySQL, I'm not really aware of how that would work on other RDBMS) knows how to convert the string back to an integer before using it.
What are the use cases where it would make a difference to bound typed parameters vs strings?
I'm no PDO-expert, but I can think of a few scenarioes where the data_type parameter is both useful and even needed.
Output parameters
When you define output or input/output parameters, you must provide both type and length of the expected output parameter.
Ref: http://www.php.net/manual/en/pdo.prepared-statements.php
Example #4
$stmt = $dbh->prepare("CALL sp_returns_string(?)");
$stmt->bindParam(1, $return_value, PDO::PARAM_STR, 4000);
Example #5
$stmt = $dbh->prepare("CALL sp_takes_string_returns_string(?)");
$value = 'hello';
$stmt->bindParam(1, $value, PDO::PARAM_STR|PDO::PARAM_INPUT_OUTPUT, 4000);
DBMs without implicit casting
Explained in another answer to this question...
When parameter is not bound to castable data
Even databases with casting abilities will not always be able to cast you variable correctly.
Ref: Reasons to strongly type parameters in PDO?
$limit = 1;
$dbh->prepare("SELECT * FROM items LIMIT :limit");
$dbh->bindParam(":limit", $limit, PDO::PARAM_STR);
// Will throw "You have an error in your SQL syntax..."
That's mainly for interacting with databases that require correct typing. For example, if you enable strict mode in MySQL, you will get errors (failed queries) instead of warnings when there are type mismatches.
By default, MySQL does its best to convert data properly. But if you have ever seen 0000-00-00 in a date field, that is very likely the result of mysql trying to convert a string to a date and failing. In strict mode, the query would fail instead of trying to convert and using whatever the result is.
The data type parameter to PDOStatement::bindValue() isn't terribly useful. Essentially:
If you tell it PDO::PARAM_STR, it converts your value into a string.
If you tell it PDO::PARAM_INT and you pass a boolean, it converts it into a long.
If you tell it PDO::PARAM_BOOL and you pass it a long, it converts it into a boolean.
Nothing else seems to be converted. See here for a quick look at the source code and a little better explanation. Perhaps most importantly, PDO will not throw an exception or produce an error if you pass data with a type that doesn't match the data type you passed.
When you bind parameters to SQL statement, you can provide parameter type like PDO::PARAM_STR. If you don't, type defaults to PDO::PARAM_STR. What can be the reasons to specifically set the type of each parameter? PDO::PARAM_STR works with any parameter as I know at least in MySQL. I think even with PDO::PARAM_STR can be used even with BLOB columns.
PDO::PARAM_STR does not introduce any SQL injection because you still have prepared queries.
Using PARAM_STR happens to always work in column values because mySQL implicitly converts values to the correct type where it can, but it will fail for example in this query:
$limit = 1;
$dbh->prepare("SELECT * FROM items LIMIT :limit");
$dbh->bindParam(":limit", $limit, PDO::PARAM_STR);
// Will throw "You have an error in your SQL syntax..."
one should absolutely use PARAM_INT where appropriate - for cases like the one above, and to prepare for database engines other than mySQL that may be more strict in what they expect.
personally I see no reason, as long as you have this attribute set:
$dbh->setAttribute( PDO::ATTR_EMULATE_PREPARES, false );
so, it would detect LIMIT case automatically
anyway I'd prefer to define type in a placeholder, not in binding function.
However, my experience with PDO is not that strong. I just tried it out and decided to turn back to plain mysql.