From 6753cba12d7dbad9abe0aaf500582b9a169e1f06 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Wed, 6 Mar 2019 00:33:40 -0600 Subject: [PATCH 1/6] pdo_mysql: Fix boolean parameter binds failing when not using emulation. --- ext/pdo_mysql/mysql_statement.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ext/pdo_mysql/mysql_statement.c b/ext/pdo_mysql/mysql_statement.c index ea4b13a5d227d..61d3ea9c2ab80 100644 --- a/ext/pdo_mysql/mysql_statement.c +++ b/ext/pdo_mysql/mysql_statement.c @@ -561,6 +561,14 @@ static int pdo_mysql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_da case IS_DOUBLE: mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, parameter, MYSQL_TYPE_DOUBLE); break; + case IS_TRUE: + case IS_FALSE: +#if SIZEOF_ZEND_LONG==8 + mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, parameter, MYSQL_TYPE_LONGLONG); +#elif SIZEOF_ZEND_LONG==4 + mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, parameter, MYSQL_TYPE_LONG); +#endif /* SIZEOF_LONG */ + break; default: PDO_DBG_RETURN(0); } From 5af74be4c90cd8a8556cb81ca42922c25e3d8e1e Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Wed, 6 Mar 2019 00:41:41 -0600 Subject: [PATCH 2/6] Fix pointless copy-paste in first commit. --- ext/pdo_mysql/mysql_statement.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/ext/pdo_mysql/mysql_statement.c b/ext/pdo_mysql/mysql_statement.c index 61d3ea9c2ab80..bd33eea979bf0 100644 --- a/ext/pdo_mysql/mysql_statement.c +++ b/ext/pdo_mysql/mysql_statement.c @@ -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: #if SIZEOF_ZEND_LONG==8 mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, parameter, MYSQL_TYPE_LONGLONG); #elif SIZEOF_ZEND_LONG==4 @@ -561,14 +563,6 @@ static int pdo_mysql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_da case IS_DOUBLE: mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, parameter, MYSQL_TYPE_DOUBLE); break; - case IS_TRUE: - case IS_FALSE: -#if SIZEOF_ZEND_LONG==8 - mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, parameter, MYSQL_TYPE_LONGLONG); -#elif SIZEOF_ZEND_LONG==4 - mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, parameter, MYSQL_TYPE_LONG); -#endif /* SIZEOF_LONG */ - break; default: PDO_DBG_RETURN(0); } From 03d5b78a85354e2cac48694a65dee5a8400988f4 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Wed, 6 Mar 2019 12:24:23 -0600 Subject: [PATCH 3/6] Add test for the fix. --- ext/pdo_mysql/tests/bug_38546.phpt | 132 +++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 ext/pdo_mysql/tests/bug_38546.phpt diff --git a/ext/pdo_mysql/tests/bug_38546.phpt b/ext/pdo_mysql/tests/bug_38546.phpt new file mode 100644 index 0000000000000..5bb7785343fff --- /dev/null +++ b/ext/pdo_mysql/tests/bug_38546.phpt @@ -0,0 +1,132 @@ +--TEST-- +PDO MySQL Bug #38546 (bindParam incorrect processing of bool types) +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_EMULATE_PREPARES, false); + +$db->exec("DROP TABLE IF EXISTS test"); + +$query = "CREATE TABLE test( + uid MEDIUMINT UNSIGNED NOT NULL, + some_bool_1 BOOL NOT NULL, + some_bool_2 BOOL NOT NULL, + some_int TINYINT NOT NULL + )"; +$db->exec($query); + +$db->exec("INSERT INTO test(uid, some_bool_1, some_bool_2, some_int) VALUES(6, 1, 1, 0)"); + +var_dump($db->query('SELECT * from test')); +foreach ($db->query('SELECT * from test') as $row) { + print_r($row); +} + +$st = $db->prepare("UPDATE test SET some_bool_1=?, some_bool_2=?, some_int=? WHERE uid=?"); + +$prefs = array( + 'uid' => 6, + 'some_bool_1' => (bool) 1, + 'some_bool_2' => (bool) 0, + 'some_int' => 1, +); + +$st->bindParam(1, $prefs['some_bool_1'], PDO::PARAM_BOOL); +$st->bindParam(2, $prefs['some_bool_2'], PDO::PARAM_BOOL); +$st->bindParam(3, $prefs['some_int'], PDO::PARAM_INT); +$st->bindParam(4, $prefs['uid'], PDO::PARAM_INT); + +$result = $st->execute(); + +if ($result === false) { + var_dump($st->errorInfo()); +} else { + print("ok\n"); +} + +foreach ($db->query('SELECT * from test') as $row) { + print_r($row); +} + +$st = $db->prepare("UPDATE test SET some_bool_1=?, some_bool_2=?, some_int=? WHERE uid=?"); + +$prefs = array( + 'uid' => 6, + 'some_bool_1' => (bool) 0, + 'some_bool_2' => (bool) 1, + 'some_int' => 2, +); + +$st->bindParam(1, $prefs['some_bool_1'], PDO::PARAM_BOOL); +$st->bindParam(2, $prefs['some_bool_2'], PDO::PARAM_BOOL); +$st->bindParam(3, $prefs['some_int'], PDO::PARAM_INT); +$st->bindParam(4, $prefs['uid'], PDO::PARAM_INT); + +$result = $st->execute(); + +if ($result === false) { + var_dump($st->errorInfo()); +} else { + print("ok\n"); +} + +foreach ($db->query('SELECT * from test') as $row) { + print_r($row); +} + +?> +--CLEAN-- + +--EXPECTF-- +object(PDOStatement)#2 (1) { + ["queryString"]=> + string(18) "SELECT * from test" +} +Array +( + [uid] => 6 + [0] => 6 + [some_bool_1] => 1 + [1] => 1 + [some_bool_2] => 1 + [2] => 1 + [some_int] => 0 + [3] => 0 +) +ok +Array +( + [uid] => 6 + [0] => 6 + [some_bool_1] => 1 + [1] => 1 + [some_bool_2] => 0 + [2] => 0 + [some_int] => 1 + [3] => 1 +) +ok +Array +( + [uid] => 6 + [0] => 6 + [some_bool_1] => 0 + [1] => 0 + [some_bool_2] => 1 + [2] => 1 + [some_int] => 2 + [3] => 2 +) \ No newline at end of file From 905a54793012f13fa1bb5e1466ffb69cce9d6c57 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Wed, 6 Mar 2019 12:39:14 -0600 Subject: [PATCH 4/6] Change test for bug 44707, which seems to weirdly enforce the unforgiving boolean parameter behavior. --- ext/pdo_mysql/tests/bug_44707.phpt | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/ext/pdo_mysql/tests/bug_44707.phpt b/ext/pdo_mysql/tests/bug_44707.phpt index d5d4539fcc01a..dff7e7c745018 100644 --- a/ext/pdo_mysql/tests/bug_44707.phpt +++ b/ext/pdo_mysql/tests/bug_44707.phpt @@ -37,8 +37,6 @@ function bug_44707($db) { $stmt = $db->prepare('INSERT INTO test(id, mybool) VALUES (?, ?)'); $stmt->bindParam(1, $id); - // From MySQL 4.1 on boolean and TINYINT don't match! INSERT will fail. - // Versions prior to 4.1 have a weak test and will accept this. $stmt->bindParam(2, $mybool, PDO::PARAM_BOOL); var_dump($mybool); @@ -78,8 +76,6 @@ Native Prepared Statements bool(false) bool(false) bool(false) -array(0) { -} array(1) { [0]=> array(2) { @@ -89,4 +85,20 @@ array(1) { string(1) "0" } } +array(2) { + [0]=> + array(2) { + ["id"]=> + string(1) "1" + ["mybool"]=> + string(1) "0" + } + [1]=> + array(2) { + ["id"]=> + string(1) "1" + ["mybool"]=> + string(1) "0" + } +} done! From b53f5e135e92214a050b35d510703a9635aa8c1d Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Sat, 20 Apr 2019 18:34:02 -0500 Subject: [PATCH 5/6] Add MYSQL_TYPE_TINY bind handling to mysqlnd. 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. --- ext/mysqlnd/mysqlnd_ps_codec.c | 10 +- ext/pdo_mysql/mysql_statement.c | 6 +- ext/pdo_mysql/tests/bug_38546.phpt | 202 +++++++++++++++++++++++++---- 3 files changed, 189 insertions(+), 29 deletions(-) diff --git a/ext/mysqlnd/mysqlnd_ps_codec.c b/ext/mysqlnd/mysqlnd_ps_codec.c index 641c7ee7834ff..bcf5cca0d4322 100644 --- a/ext/mysqlnd/mysqlnd_ps_codec.c +++ b/ext/mysqlnd/mysqlnd_ps_codec.c @@ -612,7 +612,7 @@ mysqlnd_stmt_execute_prepare_param_types(MYSQLND_STMT_DATA * stmt, zval ** copie zval *parameter = &stmt->param_bind[i].zv; ZVAL_DEREF(parameter); - if (!Z_ISNULL_P(parameter) && (current_type == MYSQL_TYPE_LONG || current_type == MYSQL_TYPE_LONGLONG)) { + if (!Z_ISNULL_P(parameter) && (current_type == MYSQL_TYPE_LONG || current_type == MYSQL_TYPE_LONGLONG || current_type == MYSQL_TYPE_TINY)) { /* always copy the var, because we do many conversions */ if (Z_TYPE_P(parameter) != IS_LONG && PASS != mysqlnd_stmt_copy_it(copies_param, parameter, stmt->param_count, i)) @@ -825,6 +825,14 @@ mysqlnd_stmt_execute_store_param_values(MYSQLND_STMT_DATA * stmt, zval * copies, int4store(*p, Z_LVAL_P(data)); (*p) += 4; break; + case MYSQL_TYPE_TINY: + if (Z_TYPE_P(data) == IS_STRING) { + goto send_string; + } else { + int1store(*p, Z_LVAL_P(data)); + (*p)++; + } + break; case MYSQL_TYPE_LONG_BLOB: if (stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED) { stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED; diff --git a/ext/pdo_mysql/mysql_statement.c b/ext/pdo_mysql/mysql_statement.c index bd33eea979bf0..43309be84fc4a 100644 --- a/ext/pdo_mysql/mysql_statement.c +++ b/ext/pdo_mysql/mysql_statement.c @@ -552,14 +552,16 @@ 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: #if SIZEOF_ZEND_LONG==8 mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, parameter, MYSQL_TYPE_LONGLONG); #elif SIZEOF_ZEND_LONG==4 mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, parameter, MYSQL_TYPE_LONG); #endif /* SIZEOF_LONG */ break; + case IS_TRUE: + case IS_FALSE: + mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, parameter, MYSQL_TYPE_TINY); + break; case IS_DOUBLE: mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, parameter, MYSQL_TYPE_DOUBLE); break; diff --git a/ext/pdo_mysql/tests/bug_38546.phpt b/ext/pdo_mysql/tests/bug_38546.phpt index 5bb7785343fff..962a404fc0be5 100644 --- a/ext/pdo_mysql/tests/bug_38546.phpt +++ b/ext/pdo_mysql/tests/bug_38546.phpt @@ -25,33 +25,51 @@ $query = "CREATE TABLE test( )"; $db->exec($query); -$db->exec("INSERT INTO test(uid, some_bool_1, some_bool_2, some_int) VALUES(6, 1, 1, 0)"); +$st = $db->prepare("INSERT INTO test (uid, some_bool_1, some_bool_2, some_int) VALUES (?, ?, ?, ?)"); + +$values = [ + 'uid' => 6, + 'some_bool_1' => false, + 'some_bool_2' => true, + 'some_int' => -23 +]; +$st->bindParam(1, $values['uid'], PDO::PARAM_INT); +$st->bindParam(2, $values['some_bool_1'], PDO::PARAM_BOOL); +$st->bindParam(3, $values['some_bool_2'], PDO::PARAM_BOOL); +$st->bindParam(4, $values['some_int'], PDO::PARAM_INT); + +$result = $st->execute(); + +if ($result === false) { + var_dump($st->errorInfo()); +} else { + print("ok insert\n"); +} -var_dump($db->query('SELECT * from test')); foreach ($db->query('SELECT * from test') as $row) { print_r($row); } $st = $db->prepare("UPDATE test SET some_bool_1=?, some_bool_2=?, some_int=? WHERE uid=?"); -$prefs = array( +$values = [ 'uid' => 6, 'some_bool_1' => (bool) 1, 'some_bool_2' => (bool) 0, 'some_int' => 1, -); +]; -$st->bindParam(1, $prefs['some_bool_1'], PDO::PARAM_BOOL); -$st->bindParam(2, $prefs['some_bool_2'], PDO::PARAM_BOOL); -$st->bindParam(3, $prefs['some_int'], PDO::PARAM_INT); -$st->bindParam(4, $prefs['uid'], PDO::PARAM_INT); +$st->bindParam(1, $values['some_bool_1'], PDO::PARAM_BOOL); +$st->bindParam(2, $values['some_bool_2'], PDO::PARAM_BOOL); +$st->bindParam(3, $values['some_int'], PDO::PARAM_INT); +$st->bindParam(4, $values['uid'], PDO::PARAM_INT); $result = $st->execute(); if ($result === false) { var_dump($st->errorInfo()); } else { - print("ok\n"); + print("ok prepare 1\n"); } foreach ($db->query('SELECT * from test') as $row) { @@ -60,24 +78,105 @@ foreach ($db->query('SELECT * from test') as $row) { $st = $db->prepare("UPDATE test SET some_bool_1=?, some_bool_2=?, some_int=? WHERE uid=?"); -$prefs = array( +$values = [ 'uid' => 6, 'some_bool_1' => (bool) 0, 'some_bool_2' => (bool) 1, 'some_int' => 2, -); +]; + +$st->bindParam(1, $values['some_bool_1'], PDO::PARAM_BOOL); +$st->bindParam(2, $values['some_bool_2'], PDO::PARAM_BOOL); +$st->bindParam(3, $values['some_int'], PDO::PARAM_INT); +$st->bindParam(4, $values['uid'], PDO::PARAM_INT); + +$result = $st->execute(); + +if ($result === false) { + var_dump($st->errorInfo()); +} else { + print("ok prepare 2\n"); +} + +foreach ($db->query('SELECT * from test') as $row) { + print_r($row); +} + +// String true and false should fail +$st = $db->prepare("UPDATE test SET some_bool_1=?, some_bool_2=?, some_int=? WHERE uid=?"); + +$values = [ + 'uid' => 6, + 'some_bool_1' => 'true', + 'some_bool_2' => 'false', + 'some_int' => 3, +]; -$st->bindParam(1, $prefs['some_bool_1'], PDO::PARAM_BOOL); -$st->bindParam(2, $prefs['some_bool_2'], PDO::PARAM_BOOL); -$st->bindParam(3, $prefs['some_int'], PDO::PARAM_INT); -$st->bindParam(4, $prefs['uid'], PDO::PARAM_INT); +$st->bindParam(1, $values['some_bool_1'], PDO::PARAM_BOOL); +$st->bindParam(2, $values['some_bool_2'], PDO::PARAM_BOOL); +$st->bindParam(3, $values['some_int'], PDO::PARAM_INT); +$st->bindParam(4, $values['uid'], PDO::PARAM_INT); $result = $st->execute(); if ($result === false) { var_dump($st->errorInfo()); } else { - print("ok\n"); + print("ok prepare 3\n"); +} + +foreach ($db->query('SELECT * from test') as $row) { + print_r($row); +} + +// Null should not be treated as false +$st = $db->prepare("UPDATE test SET some_bool_1=?, some_bool_2=?, some_int=? WHERE uid=?"); + +$values = [ + 'uid' => 6, + 'some_bool_1' => true, + 'some_bool_2' => null, + 'some_int' => 4, +]; + +$st->bindParam(1, $values['some_bool_1'], PDO::PARAM_BOOL); +$st->bindParam(2, $values['some_bool_2'], PDO::PARAM_BOOL); +$st->bindParam(3, $values['some_int'], PDO::PARAM_INT); +$st->bindParam(4, $values['uid'], PDO::PARAM_INT); + +$result = $st->execute(); + +if ($result === false) { + var_dump($st->errorInfo()); +} else { + print("ok prepare 4\n"); +} + +foreach ($db->query('SELECT * from test') as $row) { + print_r($row); +} + +// Integers converted correctly +$st = $db->prepare("UPDATE test SET some_bool_1=?, some_bool_2=?, some_int=? WHERE uid=?"); + +$values = [ + 'uid' => 6, + 'some_bool_1' => 256, + 'some_bool_2' => 0, + 'some_int' => 5, +]; + +$st->bindParam(1, $values['some_bool_1'], PDO::PARAM_BOOL); +$st->bindParam(2, $values['some_bool_2'], PDO::PARAM_BOOL); +$st->bindParam(3, $values['some_int'], PDO::PARAM_INT); +$st->bindParam(4, $values['uid'], PDO::PARAM_INT); + +$result = $st->execute(); + +if ($result === false) { + var_dump($st->errorInfo()); +} else { + print("ok prepare 5\n"); } foreach ($db->query('SELECT * from test') as $row) { @@ -91,22 +190,19 @@ require dirname(__FILE__) . '/mysql_pdo_test.inc'; MySQLPDOTest::dropTestTable(); ?> --EXPECTF-- -object(PDOStatement)#2 (1) { - ["queryString"]=> - string(18) "SELECT * from test" -} +ok insert Array ( [uid] => 6 [0] => 6 - [some_bool_1] => 1 - [1] => 1 + [some_bool_1] => 0 + [1] => 0 [some_bool_2] => 1 [2] => 1 - [some_int] => 0 - [3] => 0 + [some_int] => -23 + [3] => -23 ) -ok +ok prepare 1 Array ( [uid] => 6 @@ -118,7 +214,49 @@ Array [some_int] => 1 [3] => 1 ) -ok +ok prepare 2 +Array +( + [uid] => 6 + [0] => 6 + [some_bool_1] => 0 + [1] => 0 + [some_bool_2] => 1 + [2] => 1 + [some_int] => 2 + [3] => 2 +) + +Warning: PDOStatement::execute(): SQLSTATE[HY000]: General error: 1366 Incorrect integer value: 'true' for column 'some_bool_1' at row 1 in %s +array(3) { + [0]=> + string(5) "HY000" + [1]=> + int(1366) + [2]=> + string(65) "Incorrect integer value: 'true' for column 'some_bool_1' at row 1" +} +Array +( + [uid] => 6 + [0] => 6 + [some_bool_1] => 0 + [1] => 0 + [some_bool_2] => 1 + [2] => 1 + [some_int] => 2 + [3] => 2 +) + +Warning: PDOStatement::execute(): SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'some_bool_2' cannot be null in %s +array(3) { + [0]=> + string(5) "23000" + [1]=> + int(1048) + [2]=> + string(35) "Column 'some_bool_2' cannot be null" +} Array ( [uid] => 6 @@ -129,4 +267,16 @@ Array [2] => 1 [some_int] => 2 [3] => 2 +) +ok prepare 5 +Array +( + [uid] => 6 + [0] => 6 + [some_bool_1] => 1 + [1] => 1 + [some_bool_2] => 0 + [2] => 0 + [some_int] => 5 + [3] => 5 ) \ No newline at end of file From 675b1aa025bd37b75e6618055569539617665632 Mon Sep 17 00:00:00 2001 From: Cameron Porter Date: Sat, 20 Apr 2019 19:27:36 -0500 Subject: [PATCH 6/6] Remove else for consistency. --- ext/mysqlnd/mysqlnd_ps_codec.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ext/mysqlnd/mysqlnd_ps_codec.c b/ext/mysqlnd/mysqlnd_ps_codec.c index bcf5cca0d4322..02e02c951683c 100644 --- a/ext/mysqlnd/mysqlnd_ps_codec.c +++ b/ext/mysqlnd/mysqlnd_ps_codec.c @@ -828,10 +828,9 @@ mysqlnd_stmt_execute_store_param_values(MYSQLND_STMT_DATA * stmt, zval * copies, case MYSQL_TYPE_TINY: if (Z_TYPE_P(data) == IS_STRING) { goto send_string; - } else { - int1store(*p, Z_LVAL_P(data)); - (*p)++; } + int1store(*p, Z_LVAL_P(data)); + (*p)++; break; case MYSQL_TYPE_LONG_BLOB: if (stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED) {