PDO queries with integer type - php

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

Related

Protect Oracle database against SQL Injection

I'm on Symfony and I don't know how protect my database against sql injection. If you have some idea, I will be gratefull.
My function with sql :
public function getResult($$value)
{
$sql = "SELECT SOMETHING FROM SOMETHING smt
WHERE smt.THING = '".$value."'";
return $this->egee->executeQuery($sql);
}
And here is my executeQuery funciton :
public function executeQuery($sql) {
$entityManager = $this->em->getConnection('xxx');
$stmt = $entityManager->prepare($sql);
$stmt->execute();
return $stmt->fetch();
}
I allready try with BindParam, but it's didn't work with Oracle.
With BindParam I have this response :
Error 503 : Service Unavailable
The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later.
Here's how you do it ... with any and every database: parameterized queries.
Your SQL string now becomes:
SELECT SOMETHING FROM SOMETHING smt WHERE smt.THING = ?
Notice the ? (which is not in quotes ... this is not a one-character literal string) This indicates a query parameter.
Now, each time you execute the query, you supply an array() containing each of the parameter-values you want to substitute, in order left-to-right. Different values may be used each time the query is executed (without re-preparing it), because these values are not "part of" the query: they are inputs.
No matter what the parameter-value contains, the database engine will never see it as anything other than the numeric or string value that it is. It will never regard it as "part of the SQL." Thus, SQL-injection becomes impossible.
Furthermore, the [binary] value is used directly, instead of being decoded from a character string. So, say, if you want to use quote-marks as part of your string parameter-value, you would not "encode" them with backslashes. (If you provided \", then "a backslash followed by a quote mark" is what SQL would see as the parameter's value ... a perfectly acceptable two-character value.)
Here's a nice write-up: https://www.w3schools.com/php/php_mysql_prepared_statements.asp
The documentation for Doctrine ORM in the Symfony manual shows an example of using a query parameter:
https://symfony.com/doc/current/doctrine.html#querying-with-sql
$sql = '
SELECT * FROM product p
WHERE p.price > :price
ORDER BY p.price ASC
';
$stmt = $conn->prepare($sql);
$stmt->execute(['price' => $price]);
You don't need to use BindParam. Just pass a hash array to execute(), where the hash keys are the named query parameter placeholders you put in your SQL query.

PHP PDO UPDATE statement with subquery

I'm trying to create a query using PDO, where the query includes a subquery. The code isn't working. Using the workbench, I can do the query and it does perform.
I feel like there is nuance here when it comes to deriving a table while using PDO.
$turn = 1;
$phase = -1;
$status = "waiting";
$gameid = 1;
$stmt = $this->connection->prepare("
UPDATE playerstatus
SET
turn = :turn,
phase = :phase,
status = :status,
value = value + (SELECT reinforce FROM games where id = :gameid)
WHERE
gameid = :gameid
");
$stmt->bindParam(":turn", $turn);
$stmt->bindParam(":phase", $phase);
$stmt->bindParam(":status", $status);
$stmt->bindParam(":gameid", $gameid);
$stmt->execute();
I tried a multitude of adjustments, it simply fails upon executing.
EDIT error:
Fatal error: Uncaught PDOException: SQLSTATE[HY093]: Invalid parameter number
A known (but not well documented) limitation with PDO named placholders: the same bind placeholder can't be used more than one time in a statement. Workaround is to use distinct bind placeholder names.
(This limitation in PDO may have been addressed in a later versions(?). I think the root cause is that "behind the scenes", PDO is replacing the named placeholders with positional notation question marks. This problem is not restricted to just UPDATE statements, this same problem plagues all PDO SQL statements using named placeholders.)
Also, not related to the problem, I recommend using bindValue in place of bindParam.
Change the bind placeholder name to be distinct/unique. Shown here, changing one of the occurrences of :gameid to :gameid2
value = value + (SELECT reinforce FROM games where id = :gameid)
WHERE
gameid = :gameid2
^
And we need to supply a value for each bind placeholder. Which means we need to add another line. With bindValue, we can reference the same variable without needing make a copy of it.
$stmt->bindValue(":gameid", $gameid);
$stmt->bindValue(":gameid2", $gameid);
^

oci_bind_by_name not working when unescaped works

Working on an UPDATE query for an Oracle database. The field in question is of the type NCHAR(25), which accepts a 25 character UTF-8 byte string. My input values are in ASCII which should work no problem.
The following snippet uses the oci_bind_by_name function to escape the variable in the WHERE clause and insert into the placeholder variable :herp.
$sql = "UPDATE MYTABLE SET OPT = '1' WHERE FIELD = :herp";
$stmt = oci_parse($this->conn, $sql);
oci_bind_by_name($stmt, ":herp", $record['value'], -1, SQLT_CHR);
This next snippet does not use the oci_bind_by_name function and instead inserts the variable into the SQL statement unescaped (YOLO).
$sql = "UPDATE MYTABLE SET OPT = '1' WHERE FIELD = '".$record['value']."'";
$stmt = oci_parse($this->conn, $sql);
My problem
The first snippet does not work, while the second one works fine, i.e. the UPDATE statement succeeds every time on the second method while it fails every time on the first.
Both versions of the UPDATE should work. However when I use the oci_bind_by_name function for a few fields, somehow the variable is getting changed. (I am doing more rigorous error checking in the actual code).
My question
What is going on here? How can I still use the oci_bind_by_name instead of just concatenating the variable directly into the SQL statement?
Per the developers:
Neither PHP OCI8 or PDO_OCI support NVARCHAR, NCHAR or NCLOB types.

PDO::PARAM_INT is important in bindParam?

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.

How define the variable type in PDOStatement::bindValue()?

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.

Categories