From c044164a96553668cc6c4ae1eb32f18219308851 Mon Sep 17 00:00:00 2001 From: Keyur Govande Date: Thu, 14 Aug 2014 18:19:56 +0000 Subject: [PATCH 1/5] Patch for bug #67839 (mysqli does not handle 4-byte floats correctly) Before the patch, a value of 9.99 in a FLOAT column came out of mysqli as 9.9998998641968. This is because it would naively cast a 4-byte float into PHP's internal 8-byte double. To fix this, with GCC we use the built-in decimal support to "up-convert" the 4-byte float to a 8-byte double. When that is not available, we fall back to converting the float to a string and then converting the string to a double. This mimics what MySQL does. --- ext/mysqli/tests/bug67839.phpt | 58 ++++++++++++++++++++++++++++++++++ ext/mysqlnd/config9.m4 | 26 +++++++++++++++ ext/mysqlnd/mysqlnd_ps_codec.c | 49 +++++++++++++++++++++++++--- 3 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 ext/mysqli/tests/bug67839.phpt diff --git a/ext/mysqli/tests/bug67839.phpt b/ext/mysqli/tests/bug67839.phpt new file mode 100644 index 00000000000..b2821a21bc2 --- /dev/null +++ b/ext/mysqli/tests/bug67839.phpt @@ -0,0 +1,58 @@ +--TEST-- +mysqli_float_handling - ensure 4 byte float is handled correctly +--SKIPIF-- + +--FILE-- + +--CLEAN-- + +--EXPECTF-- +1: 9.9999: 9.9999 diff --git a/ext/mysqlnd/config9.m4 b/ext/mysqlnd/config9.m4 index 2c15c34e8d3..d730ba7e71f 100644 --- a/ext/mysqlnd/config9.m4 +++ b/ext/mysqlnd/config9.m4 @@ -51,3 +51,29 @@ if test "$PHP_MYSQLND" != "no" || test "$PHP_MYSQLND_ENABLED" = "yes" || test "$ #endif ]) fi + +dnl +dnl Check if the compiler supports Decimal32/64/128 types from the IEEE-754 2008 version +dnl References: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1657.pdf +dnl http://speleotrove.com/decimal/ +dnl +AC_CACHE_CHECK([whether whether compiler supports Decimal32/64/128 types], ac_cv_decimal_fp_supported,[ +AC_TRY_RUN( [ +#include + +int main(int argc, char **argv) { + typedef float dec32 __attribute__((mode(SD))); + dec32 k = 99.49f; + double d2 = (double)k; + return 0; +} +],[ + ac_cv_decimal_fp_supported=yes +],[ + ac_cv_decimal_fp_supported=no +],[ + ac_cv_decimal_fp_supported=no +])]) +if test "$ac_cv_decimal_fp_supported" = "yes"; then + AC_DEFINE(HAVE_DECIMAL_FP_SUPPORT, 1, [Define if the compiler supports Decimal32/64/128 types.]) +fi diff --git a/ext/mysqlnd/mysqlnd_ps_codec.c b/ext/mysqlnd/mysqlnd_ps_codec.c index e2640c775ba..3f7d31002f6 100644 --- a/ext/mysqlnd/mysqlnd_ps_codec.c +++ b/ext/mysqlnd/mysqlnd_ps_codec.c @@ -195,12 +195,53 @@ void ps_fetch_float(zval *zv, const MYSQLND_FIELD * const field, unsigned int pack_len, zend_uchar **row, zend_bool as_unicode TSRMLS_DC) { - float value; + float fval; + double dval; DBG_ENTER("ps_fetch_float"); - float4get(value, *row); - ZVAL_DOUBLE(zv, value); + float4get(fval, *row); (*row)+= 4; - DBG_INF_FMT("value=%f", value); + DBG_INF_FMT("value=%f", fval); + + /* + * The following is needed to correctly support 4-byte floats. + * Otherwise, a value of 9.99 in a FLOAT column comes out of mysqli + * as 9.9998998641968. + * + * For GCC, we use the built-in decimal support to "up-convert" a + * 4-byte float to a 8-byte double. + * When that is not available, we fall back to converting the float + * to a string and then converting the string to a double. This mimics + * what MySQL does. + */ +#ifdef HAVE_DECIMAL_FP_SUPPORT + { + typedef float dec32 __attribute__((mode(SD))); + dec32 d32val = fval; + + /* The following cast is guaranteed to do the right thing */ + dval = (double) d32val; + } +#else + { + char num_buf[2048]; /* Over allocated */ + char *s; + + /* Convert to string. Ignoring localization, etc. + * Following MySQL's rules. If precision is undefined (NOT_FIXED_DEC i.e. 31) + * or larger than 31, the value is limited to 6 (FLT_DIG). + */ + s = php_gcvt(fval, + field->decimals >= 31 ? 6 : field->decimals, + '.', + 'e', + num_buf); + + /* And now convert back to double */ + dval = zend_strtod(s, NULL); + } +#endif + + ZVAL_DOUBLE(zv, dval); DBG_VOID_RETURN; } /* }}} */ From 4e2c01617f207c039881f635d3beb77eff0d9669 Mon Sep 17 00:00:00 2001 From: Keyur Govande Date: Thu, 14 Aug 2014 18:20:26 +0000 Subject: [PATCH 2/5] Fix failing tests --- ext/mysqli/tests/010.phpt | 12 ++++++------ ext/mysqli/tests/011.phpt | 4 ++-- ext/mysqli/tests/012.phpt | 4 ++-- ext/mysqli/tests/mysqli_change_user.phpt | 7 ++++++- ext/mysqli/tests/mysqli_change_user_oo.phpt | 7 ++++++- ext/mysqli/tests/mysqli_real_escape_string_gbk.phpt | 2 +- ext/mysqli/tests/table.inc | 7 ++++++- 7 files changed, 29 insertions(+), 14 deletions(-) diff --git a/ext/mysqli/tests/010.phpt b/ext/mysqli/tests/010.phpt index 60ff8eac19b..83a43e06b67 100644 --- a/ext/mysqli/tests/010.phpt +++ b/ext/mysqli/tests/010.phpt @@ -62,18 +62,18 @@ mysqli_close($link); --EXPECT-- array(7) { [0]=> - float(3.14159274101) + float(3.141593) [1]=> - float(-9.99999997475E-7) + float(-1.0E-6) [2]=> float(0) [3]=> - float(999999995904) + float(1.0E+12) [4]=> - float(0.564642488956) + float(0.5646425) [5]=> float(1) [6]=> - float(8.88888914608E+14) + float(8.888889E+14) } -done! \ No newline at end of file +done! diff --git a/ext/mysqli/tests/011.phpt b/ext/mysqli/tests/011.phpt index db03abac83f..b14516ff781 100644 --- a/ext/mysqli/tests/011.phpt +++ b/ext/mysqli/tests/011.phpt @@ -67,7 +67,7 @@ array(8) { [3]=> int(4999999) [4]=> - float(2345.60009766) + float(2345.6) [5]=> float(5678.89563) [6]=> @@ -75,4 +75,4 @@ array(8) { [7]=> %unicode|string%(11) "mysql rulez" } -done! \ No newline at end of file +done! diff --git a/ext/mysqli/tests/012.phpt b/ext/mysqli/tests/012.phpt index 7cc34b0c00e..91abae48506 100644 --- a/ext/mysqli/tests/012.phpt +++ b/ext/mysqli/tests/012.phpt @@ -66,7 +66,7 @@ array(8) { [3]=> int(54) [4]=> - float(2.59999990463) + float(2.6) [5]=> float(58.89) [6]=> @@ -74,4 +74,4 @@ array(8) { [7]=> %unicode|string%(3) "6.7" } -done! \ No newline at end of file +done! diff --git a/ext/mysqli/tests/mysqli_change_user.phpt b/ext/mysqli/tests/mysqli_change_user.phpt index bfea423c9e4..3bfb9ebf7d1 100644 --- a/ext/mysqli/tests/mysqli_change_user.phpt +++ b/ext/mysqli/tests/mysqli_change_user.phpt @@ -41,6 +41,11 @@ require_once('skipifconnectfailure.inc'); if (false !== ($tmp = mysqli_change_user($link, $user, $passwd, $db . '_unknown_really'))) printf("[009] Expecting false, got %s/%s\n", gettype($tmp), $tmp); + // Reconnect because after 3 failed change_user attempts, the server blocks you off. + if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) + printf("[006] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + $host, $user, $db, $port, $socket); + if (!mysqli_query($link, 'SET @mysqli_change_user_test_var=1')) printf("[010] Failed to set test variable: [%d] %s\n", mysqli_errno($link), mysqli_error($link)); @@ -109,4 +114,4 @@ require_once('skipifconnectfailure.inc'); print "done!"; ?> --EXPECTF-- -done! \ No newline at end of file +done! diff --git a/ext/mysqli/tests/mysqli_change_user_oo.phpt b/ext/mysqli/tests/mysqli_change_user_oo.phpt index 61444ae2350..96c8a698bb6 100644 --- a/ext/mysqli/tests/mysqli_change_user_oo.phpt +++ b/ext/mysqli/tests/mysqli_change_user_oo.phpt @@ -43,6 +43,11 @@ if (!$IS_MYSQLND && (mysqli_get_server_version($link) < 50118 && mysqli_get_serv if (false !== ($tmp = $mysqli->change_user($user, $passwd, $db . '_unknown_really'))) printf("[008] Expecting false, got %s/%s\n", gettype($tmp), $tmp); + // Reconnect because after 3 failed change_user attempts, the server blocks you off. + if (!$mysqli = new my_mysqli($host, $user, $passwd, $db, $port, $socket)) + printf("[001] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + $host, $user, $db, $port, $socket); + if (!$mysqli->query('SET @mysqli_change_user_test_var=1')) printf("[009] Failed to set test variable: [%d] %s\n", $mysqli->errno, $mysqli->error); @@ -81,4 +86,4 @@ if (!$IS_MYSQLND && (mysqli_get_server_version($link) < 50118 && mysqli_get_serv print "done!"; ?> --EXPECTF-- -done! \ No newline at end of file +done! diff --git a/ext/mysqli/tests/mysqli_real_escape_string_gbk.phpt b/ext/mysqli/tests/mysqli_real_escape_string_gbk.phpt index 2fd1121a76a..991d3c345b1 100644 --- a/ext/mysqli/tests/mysqli_real_escape_string_gbk.phpt +++ b/ext/mysqli/tests/mysqli_real_escape_string_gbk.phpt @@ -36,7 +36,7 @@ $port, $socket, mysqli_connect_errno(), mysqli_connect_error()); mysqli_error($link)); } - if (!mysqli_query($link, 'CREATE TABLE test(id INT, label CHAR(1), PRIMARY + if (!mysqli_query($link, 'CREATE TABLE test(id INT, label CHAR(3), PRIMARY KEY(id)) ENGINE=' . $engine . " DEFAULT CHARSET=gbk")) { printf("Failed to create test table: [%d] %s\n", mysqli_errno($link), mysqli_error($link)); diff --git a/ext/mysqli/tests/table.inc b/ext/mysqli/tests/table.inc index aa1207af444..ec360c8f2cf 100644 --- a/ext/mysqli/tests/table.inc +++ b/ext/mysqli/tests/table.inc @@ -12,6 +12,11 @@ if (!mysqli_query($link, 'DROP TABLE IF EXISTS test')) { exit(1); } +if (!mysqli_query($link, 'SET SESSION sql_mode=\'\'')) { + printf("Failed to drop old test table: [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + exit(1); +} + if (!mysqli_query($link, 'CREATE TABLE test(id INT, label CHAR(1), PRIMARY KEY(id)) ENGINE=' . $engine)) { printf("Failed to create test table: [%d] %s\n", mysqli_errno($link), mysqli_error($link)); exit(1); @@ -20,4 +25,4 @@ if (!mysqli_query($link, 'CREATE TABLE test(id INT, label CHAR(1), PRIMARY KEY(i if (!mysqli_query($link, "INSERT INTO test(id, label) VALUES (1, 'a'), (2, 'b'), (3, 'c'), (4, 'd'), (5, 'e'), (6, 'f')")) { printf("[%d] %s\n", mysqli_errno($link), mysqli_error($link)); } -?> \ No newline at end of file +?> From 0407bdf252004cce08e383bc0f4aa0bbc69c9a25 Mon Sep 17 00:00:00 2001 From: Keyur Govande Date: Fri, 15 Aug 2014 23:08:29 +0000 Subject: [PATCH 3/5] Add NEWS --- NEWS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS b/NEWS index b771ce6fd55..97adf4fe1be 100644 --- a/NEWS +++ b/NEWS @@ -59,6 +59,9 @@ PHP NEWS . Fixed bug #67724 (chained zlib filters silently fail with large amounts of data). (Mike) +- MySQLi: + . Fixed bug #67839 (mysqli does not handle 4-byte floats correctly). (Keyur) + 24 Jul 2014, PHP 5.4.31 - Core: From 48dc203408672380545012d9b6ee73df23e139fb Mon Sep 17 00:00:00 2001 From: Keyur Govande Date: Fri, 15 Aug 2014 23:26:21 +0000 Subject: [PATCH 4/5] Fix another failing test --- ext/mysqli/tests/mysqli_change_user_old.phpt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ext/mysqli/tests/mysqli_change_user_old.phpt b/ext/mysqli/tests/mysqli_change_user_old.phpt index ddb49cd1896..96357ebf511 100644 --- a/ext/mysqli/tests/mysqli_change_user_old.phpt +++ b/ext/mysqli/tests/mysqli_change_user_old.phpt @@ -48,6 +48,14 @@ if (mysqli_get_server_version($link) >= 50600) if (false !== ($tmp = mysqli_change_user($link, $user, $passwd, $db . '_unknown_really'))) printf("[009] Expecting false, got %s/%s\n", gettype($tmp), $tmp); + // Reconnect because Percona and MariaDB block any commands after 3 failed + // change_user commands + mysqli_close($link); + + if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) { + printf("[020] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + $host, $user, $db, $port, $socket); + } if (!mysqli_query($link, 'SET @mysqli_change_user_test_var=1')) printf("[010] Failed to set test variable: [%d] %s\n", mysqli_errno($link), mysqli_error($link)); @@ -116,4 +124,4 @@ if (mysqli_get_server_version($link) >= 50600) print "done!"; ?> --EXPECTF-- -done! \ No newline at end of file +done! From 6279246d2c60437c583f9c273075579bf17be654 Mon Sep 17 00:00:00 2001 From: Keyur Govande Date: Fri, 15 Aug 2014 23:38:14 +0000 Subject: [PATCH 5/5] Update NEWS --- NEWS | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index e6a87e64c69..1f0dec6f04e 100644 --- a/NEWS +++ b/NEWS @@ -61,7 +61,10 @@ PHP NEWS - Zlib: . Fixed bug #67724 (chained zlib filters silently fail with large amounts of data). (Mike) - + +- MySQLi: + . Fixed bug #67839 (mysqli does not handle 4-byte floats correctly). (Keyur) + 24 Jul 2014, PHP 5.5.15 - Core: