]> granicus.if.org Git - php/commitdiff
Fixed bug #38546
authorCameron Porter <camporter1@gmail.com>
Wed, 6 Mar 2019 06:33:40 +0000 (00:33 -0600)
committerNikita Popov <nikita.ppv@gmail.com>
Fri, 7 Jun 2019 07:48:43 +0000 (09:48 +0200)
Properly support binding boolean parameters with emulated prepared
statements disabled. Also add the necessary mysqlnd support for
MYSQL_TYPE_TINY.

NEWS
ext/mysqlnd/mysqlnd_ps_codec.c
ext/pdo_mysql/mysql_statement.c
ext/pdo_mysql/tests/bug_38546.phpt [new file with mode: 0644]
ext/pdo_mysql/tests/bug_44707.phpt

diff --git a/NEWS b/NEWS
index 438c962759ec17879ad25430b67c2183b4c17104..c065a39b8d655a0a29dc143681884b0754f489cb 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -13,6 +13,8 @@ PHP                                                                        NEWS
 - MySQLi:
   . Fixed bug #77956 (When mysqli.allow_local_infile = Off, use a meaningful
     error message). (Sjon Hortensius)
+  . Fixed bug #38546 (bindParam incorrect processing of bool types).
+    (camporter)
 
 - OpenSSL:
   . Fixed bug #78079 (openssl_encrypt_ccm.phpt fails with OpenSSL 1.1.1c).
index 24f68867236144302beec59b4f37e29d538b538d..e884ad7461d81c969d7ce7915ea53aaaa3edae7a 100644 (file)
@@ -581,7 +581,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))
@@ -798,6 +798,13 @@ 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;
+                                       }
+                                       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;
index 2777f3ccc44a28dbe7ea38e4c7998c3e98c039f6..e230cfbf05a5da8e289e576b43b053c5f77bf2f9 100644 (file)
@@ -560,6 +560,10 @@ 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_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
new file mode 100644 (file)
index 0000000..962a404
--- /dev/null
@@ -0,0 +1,282 @@
+--TEST--
+PDO MySQL Bug #38546 (bindParam incorrect processing of bool types)
+--SKIPIF--
+<?php
+if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) die('skip not loaded');
+require dirname(__FILE__) . '/config.inc';
+require dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+require dirname(__FILE__) . '/config.inc';
+require dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
+$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
+
+$db->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);
+
+$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");
+}
+
+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=?");
+
+$values = [
+       'uid' => 6,
+       'some_bool_1' => (bool) 1,
+       'some_bool_2' => (bool) 0,
+       'some_int' => 1,
+];
+
+$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 1\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=?");
+
+$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, $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 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) {
+       print_r($row);
+}
+
+?>
+--CLEAN--
+<?php
+require dirname(__FILE__) . '/mysql_pdo_test.inc';
+MySQLPDOTest::dropTestTable();
+?>
+--EXPECTF--
+ok insert
+Array
+(
+    [uid] => 6
+    [0] => 6
+    [some_bool_1] => 0
+    [1] => 0
+    [some_bool_2] => 1
+    [2] => 1
+    [some_int] => -23
+    [3] => -23
+)
+ok prepare 1
+Array
+(
+    [uid] => 6
+    [0] => 6
+    [some_bool_1] => 1
+    [1] => 1
+    [some_bool_2] => 0
+    [2] => 0
+    [some_int] => 1
+    [3] => 1
+)
+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
+    [0] => 6
+    [some_bool_1] => 0
+    [1] => 0
+    [some_bool_2] => 1
+    [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
index 90dc4c20f46900299d4ea71ef4992e9fcb80702c..5f6ee021b9033149979113537bb906524d9636a8 100644 (file)
@@ -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!