-
Notifications
You must be signed in to change notification settings - Fork 7.9k
pdo_mysql: Fix boolean parameter binds failing when not using emulation. #3921
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
Could you please add a test for this change? |
There seems to be a disagreement with the test for Bug 44707 about how to handle a difference in data types for booleans. |
…ving boolean parameter behavior.
The last commit changes the test for that bug, because not modifying the variable that the boolean came from and enforcing boolean parameters seem to be separate concerns. |
ext/pdo_mysql/mysql_statement.c
Outdated
@@ -552,6 +552,8 @@ static int pdo_mysql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_da | |||
mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, parameter, MYSQL_TYPE_VAR_STRING); | |||
break; | |||
case IS_LONG: | |||
case IS_TRUE: | |||
case IS_FALSE: |
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.
It seems like this is okay due to the code in https://github.com/php/php-src/blob/master/ext/mysqlnd/mysqlnd_ps_codec.c#L615. This will end up casting the booleans to double and then to integer, ending up with the expected value. Might be good to implement that more explicitly, as right now it seems like this working is a lucky accident rather than how this API was intended to be used.
I'd also appreciate someone who understands MySQL take a look at this. Maybe @johannes?
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.
@nikic Disclaimer, I don't know a lot about C but do know a bit about MySQL.
I would not expect it to be casted to a double and then to an integer. What I would expect is that it is casted to an integer directly.
As per MySQL documentation the BOOL, BOOLEAN field is just a synonym for a TINYINT(1) field so I would expect this field type to also be used in the php mysql client. If I'm right there is already a type for this:
https://github.com/php/php-src/blob/master/ext/mysqlnd/mysqlnd_ps_codec.c#L405
Documentation of MySQL Bool type: https://dev.mysql.com/doc/refman/8.0/en/numeric-type-overview.html
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.
So perhaps it would be better to use MYSQL_TYPE_TINY for IS_TRUE and IS_FALSE instead?
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.
The cast to double is just a simple trick to see if the value might be bigger than maxint. Imagine you submit a large value on a platform where long is 32 bit, or an extremely huge value into an unsigned 64bit field. So there is no problem with applying this patch.
The whole type juggling might certainly be optimized ... but having a good test for this case is a good step towards making a refactoring there more possible with less risk of breaking anything :-)
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.
@camporter @johannes In that case I would still use the MYSQL_TYPE_TINY type but make a expicit implementation that casts the value to double and then to int and not piggyback on the LONG, LONGLONG type functionality for the reason that they can to be separately maintained IMO.
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.
Trying to use MYSQL_TYPE_TINY for IS_TRUE and IS_FALSE results in NULL values being sent to mysql. In the test on this PR:
Fatal error: Uncaught PDOException: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'some_bool_1' cannot be null in /home/cmporter/php-src/test.php:75
I'm possibly misunderstanding what the suggested change is.
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.
@camporter most likely you will need to add supporting code to the mysqlnd_stmt_execute_prepare_param_types
method to cast the booleans to double and then to integer when the type is MYSQL_TYPE_TINY
.
In mysql_statement.c it would be something like the following:
case IS_TRUE:
case IS_FALSE:
mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, parameter, MYSQL_TYPE_TINY);
break;
@nikic I think that is what you mean with making it explicit, or did you mean something else? Or would you rather see a MYSQL_TYPE_BOOL
added?
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.
For tiny the hack with checking double first isn't needed. A tiny integer will in all cases fit into a long (PHP's int type) The double hack is just just to see whether the value is bigger than maxint in which case it is passed along as string (which is the only generic way to transport large values without loosing precision until PHP has a built-in bigint support)
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.
Alright, so adding
case IS_TRUE:
case IS_FALSE:
mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, parameter, MYSQL_TYPE_TINY);
break;
requires mysqlnd_stmt_execute_store_param_values
to handle MYSQL_TYPE_TINY
specifically now, otherwise TRUE/FALSE values become NULL (default value stored). Storing 4 bytes the same as a long appears to work, but seems incorrect. Should it be storing 1 byte, and how would we go about handling that correctly? eg:
case MYSQL_TYPE_TINY:
if (Z_TYPE_P(data) == IS_STRING) {
goto send_string;
}
int4store(*p, Z_LVAL_P(data)); //int1store(?)
(*p) += 4; // += 1
break;
We probably want to avoid making assumptions that only TRUE/FALSE values will use the TINY data type?
Is there any harm in having TRUE/FALSE be dealt with the same as LONG values?
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.
So since it seems like we want to use MYSQL_TYPE_TINY so that TRUE/FALSE can use it from PDO, I made changes to support it in mysqlnd. Please check the new changes.
Pass MYSQL_TYPE_TINY if the bind value is TRUE/FALSE for pdo_mysql using mysqlnd. Store a single byte rather than 4 or 8.
Looks good to me. @johannes Do you also want to take a look? |
@camporter looks really good, thx for the effort. |
Looks good to me. Only small comment: If this is supported (supposed o be supported) by all drivers it might be a good idea to move the test to PDO's test suite as redirect test so it's run on all drivers. |
I can try moving and running the test for PDO in general. Not sure how much will work, it will be a few days until I can complete checking that. |
There seems to be quite a bit of variance between various drivers about how and whether they handle a boolean type, which doesn't exactly match what is being fixed on this specific pull request. I'm up for additional changes to get consistency between the drivers as far as handling boolean values, but I'm not sure what the expected behavior is supposed to be under every supported driver. Adding confusion is that boolean values fetched from (for example) mysql are returned as integers, instead of true/false specifically because they are represented as numbers. It's not obvious to me how much PDO can or should 'abstract' in various boolean type cases. |
Result types we can't unify further - he database tells a type and that's all info we have (till we know table schemas and have an sql engine or user requests a specific type - which both are out of scope for PDO) If writing a generic est is too hard then I wouldn't bother now but suggest to push the change. |
I was not able to come up with a generic test that would make sense for more than the mysql driver in PDO due to driver and database differences. |
Merged as 7d1aa75 into PHP 7.2 and up. Thanks a lot for working on this! |
Attempts to fix bug 38546.