-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Fixed GH-13167 Fixed the behavior of bindValue
and bindParam
.
#13384
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
bindValue
.bindValue
.
1fa82b1
to
b14f535
Compare
bindValue
.bindValue
.
ext/pdo_mysql/tests/gh13384.phpt
Outdated
$db->exec('DROP TABLE IF EXISTS test_gh13384'); | ||
?> | ||
--EXPECTF-- | ||
Fatal error: Uncaught Error: Object of class DateTime could not be converted to string in %s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this correct? You are binding the param as an int, so why is this trying to convert it to a string?
ext/pdo_mysql/tests/gh13384.phpt
Outdated
$db->exec('CREATE TABLE test_gh13384 (mode TINYINT)'); | ||
|
||
$stmt = $db->prepare('INSERT INTO test_gh13384 (mode) VALUES (?)'); | ||
$stmt->bindValue(1, new DateTime(), PDO::PARAM_INT); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if it's bindParam
instead? Does the type of the variable get cast too?
My initial thought is that this is a fix, but this whole thing should probably be properly cleaned up. I think we should merge your fix and think if and how we can tidy it all up. Ideally, PDO_MySQL should attempt to cast to the appropriate type. We need to make sure that there is no data loss, though. I appreciate your research. It helps a lot. |
Yes, I also think that this is not a fundamental solution. I mentioned this on the mailing list before, but it is also related to the following issue: Anyway, there is a "variable type" and a "type specified by the parameter" here, and the behavior when they conflict is really messed up. Of course, as you mentioned, there is no need for same behavior all drivers. However, if we modify these, it will definitely result in a BC Break, so I may need an RFC... |
Hi Saki, I have been thinking about it and I have some points to consider. First, let me preface this by stating that I was not able to find any functional specification for PDO The current PDO logic that applies to all drivers consists of these 3 actions:
When using I am not an expert on other DB types so I will focus on MySQL only. In PDO_MySQL we mostly ignore the type hint. Apart from PARAM_NULL and PARAM_LOB, we do not have/need extra logic, thus the type hints are unused. But mysqlnd has optimizations for doubles and integers. I believe this is why PDO_MySQL uses the variable type and tries to bind it as an INT or DOUBLE when the param type hint is not PARAM_STR. As the code comment says, I am not sure how wise this is, but it was like that forever, so we should keep it. When emulation mode is switched on, a different part of the code is used. Instead of binding the parameter values to the
PDO implements an EVENT system for PDO drivers to hook some logic, so it is possible for other PDO drivers to modify the value or type hint prior to this. I believe PGSQL does this in case of boolean as an example. MySQL doesn't use this feature. Now, when it comes to your PR and the reported issue, I believe it's a bug that MySQL silently borks when a non-scalar and non-null value is bound. When using emulation, it correctly throws an error or at least a warning due to the "else" part above. Binding a We should ideally not touch the emulation mode logic, and in non-emulation mode, we should respect the variable type according to the current logic. So when using PARAM_INT the following happens now:
With your current fix, we would get this:
So the question here is what should happen when a non-scalar value is bound using PARAM_INT in PDO_MySQL. As of now, there is no defined behaviour. If we try to convert the value to an integer to avail of the binary's protocol optimizations, it would be wrong. We cannot convert an array, resource, or object to an int in any meaningful way. If we say that the value should be converted to a string, we'd also be wrong as the documentation says "directly converting an array, object, or resource to a string does not provide any useful information about the value". We could implement a driver error, such as:
but then we would exclude Which is best? One way or another, we would have a different logic than what emulation mode offers, and I think that's ok. Casting to string or int is technically meaningless. The only special case is casting to string a stringable object. But if the user explicitly type hinted the param as an int, I doubt they expected it to get cast to a string. I'd assume 99.99% of users would be using PARAM_STR when binding a stringable object. The 0.01% that accidentally type-hinted it as an integer should see an error. Therefore, as of now, I have come to the conclusion that it would be better to just implement a driver error. I think we could even put the fix in a patch release, but it's probably safer to put it on master only. After all, we are adding an error in a place where there was none before. In regards to what I said earlier about the variable getting changed when using |
@kamil-tekiela
Agree. I think it's a good idea to raise an error for anything that is not a scalar value, including
Is my understanding correct?
This probably won't change. The following code:
result:
In this way, if we change the value of a variable after bindParam is executed, it will be reflected. And although |
Yes, you are correct.
Wow, I completely forgot how this works. I apologize as this is a large gap in my analysis. However, I still stand by my recommendation to raise an implementation error. The rest is probably out of the scope for this bug fix and I will rethink this more carefully. |
Considering that there could be a difference between and after when using
What do you think? |
Oh, that's nice! Makes sense to me. |
This error should behave the same even if we don't specify PARAM_INT, right? If it is not specified (that is, when it is PARAM_STR), a "warning" is displayed saying that the object cannot be converted to a string, so I will take a closer look. |
Probably here. This is a common specification for pdo, so we may not need to change it when using PARAM_STR and don't worry about it... Line 427 in 0941507
In the future, it may be necessary to change this from a warning to an error, but it seems better to discuss that separately. |
bindValue
.bindValue
and bindParam
.
I modified it a bit and the test passed with libmysql as well. |
`PDO_PARAM_EVT_EXEC_PRE` of `pdo_mysql_stmt_param_hook` unless it is a `Stringable` object.
d529a0c
to
f8a5f2e
Compare
Can you also add a test like this and after it passes this PR can be merged.
with expectation:
@nielsdos Could you have a second look over it, please, just to make sure it makes sense? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense to me.
Tested with both mysqlnd and libmysqlclient, works fine.
@kamil-tekiela |
Closes #13167