Skip to content

Commit 7d1aa75

Browse files
camporternikic
authored andcommitted
Fixed bug #38546
Properly support binding boolean parameters with emulated prepared statements disabled. Also add the necessary mysqlnd support for MYSQL_TYPE_TINY.
1 parent 2df3294 commit 7d1aa75

File tree

5 files changed

+312
-5
lines changed

5 files changed

+312
-5
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ PHP NEWS
1313
- MySQLi:
1414
. Fixed bug #77956 (When mysqli.allow_local_infile = Off, use a meaningful
1515
error message). (Sjon Hortensius)
16+
. Fixed bug #38546 (bindParam incorrect processing of bool types).
17+
(camporter)
1618

1719
- OpenSSL:
1820
. Fixed bug #78079 (openssl_encrypt_ccm.phpt fails with OpenSSL 1.1.1c).

ext/mysqlnd/mysqlnd_ps_codec.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ mysqlnd_stmt_execute_prepare_param_types(MYSQLND_STMT_DATA * stmt, zval ** copie
581581
zval *parameter = &stmt->param_bind[i].zv;
582582

583583
ZVAL_DEREF(parameter);
584-
if (!Z_ISNULL_P(parameter) && (current_type == MYSQL_TYPE_LONG || current_type == MYSQL_TYPE_LONGLONG)) {
584+
if (!Z_ISNULL_P(parameter) && (current_type == MYSQL_TYPE_LONG || current_type == MYSQL_TYPE_LONGLONG || current_type == MYSQL_TYPE_TINY)) {
585585
/* always copy the var, because we do many conversions */
586586
if (Z_TYPE_P(parameter) != IS_LONG &&
587587
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,
798798
int4store(*p, Z_LVAL_P(data));
799799
(*p) += 4;
800800
break;
801+
case MYSQL_TYPE_TINY:
802+
if (Z_TYPE_P(data) == IS_STRING) {
803+
goto send_string;
804+
}
805+
int1store(*p, Z_LVAL_P(data));
806+
(*p)++;
807+
break;
801808
case MYSQL_TYPE_LONG_BLOB:
802809
if (stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED) {
803810
stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED;

ext/pdo_mysql/mysql_statement.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,10 @@ static int pdo_mysql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_da
560560
mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, parameter, MYSQL_TYPE_LONG);
561561
#endif /* SIZEOF_LONG */
562562
break;
563+
case IS_TRUE:
564+
case IS_FALSE:
565+
mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, parameter, MYSQL_TYPE_TINY);
566+
break;
563567
case IS_DOUBLE:
564568
mysqlnd_stmt_bind_one_param(S->stmt, param->paramno, parameter, MYSQL_TYPE_DOUBLE);
565569
break;

ext/pdo_mysql/tests/bug_38546.phpt

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
--TEST--
2+
PDO MySQL Bug #38546 (bindParam incorrect processing of bool types)
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) die('skip not loaded');
6+
require dirname(__FILE__) . '/config.inc';
7+
require dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
8+
PDOTest::skip();
9+
?>
10+
--FILE--
11+
<?php
12+
require dirname(__FILE__) . '/config.inc';
13+
require dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
14+
$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
15+
16+
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
17+
18+
$db->exec("DROP TABLE IF EXISTS test");
19+
20+
$query = "CREATE TABLE test(
21+
uid MEDIUMINT UNSIGNED NOT NULL,
22+
some_bool_1 BOOL NOT NULL,
23+
some_bool_2 BOOL NOT NULL,
24+
some_int TINYINT NOT NULL
25+
)";
26+
$db->exec($query);
27+
28+
$st = $db->prepare("INSERT INTO test (uid, some_bool_1, some_bool_2, some_int) VALUES (?, ?, ?, ?)");
29+
30+
$values = [
31+
'uid' => 6,
32+
'some_bool_1' => false,
33+
'some_bool_2' => true,
34+
'some_int' => -23
35+
];
36+
$st->bindParam(1, $values['uid'], PDO::PARAM_INT);
37+
$st->bindParam(2, $values['some_bool_1'], PDO::PARAM_BOOL);
38+
$st->bindParam(3, $values['some_bool_2'], PDO::PARAM_BOOL);
39+
$st->bindParam(4, $values['some_int'], PDO::PARAM_INT);
40+
41+
$result = $st->execute();
42+
43+
if ($result === false) {
44+
var_dump($st->errorInfo());
45+
} else {
46+
print("ok insert\n");
47+
}
48+
49+
foreach ($db->query('SELECT * from test') as $row) {
50+
print_r($row);
51+
}
52+
53+
$st = $db->prepare("UPDATE test SET some_bool_1=?, some_bool_2=?, some_int=? WHERE uid=?");
54+
55+
$values = [
56+
'uid' => 6,
57+
'some_bool_1' => (bool) 1,
58+
'some_bool_2' => (bool) 0,
59+
'some_int' => 1,
60+
];
61+
62+
$st->bindParam(1, $values['some_bool_1'], PDO::PARAM_BOOL);
63+
$st->bindParam(2, $values['some_bool_2'], PDO::PARAM_BOOL);
64+
$st->bindParam(3, $values['some_int'], PDO::PARAM_INT);
65+
$st->bindParam(4, $values['uid'], PDO::PARAM_INT);
66+
67+
$result = $st->execute();
68+
69+
if ($result === false) {
70+
var_dump($st->errorInfo());
71+
} else {
72+
print("ok prepare 1\n");
73+
}
74+
75+
foreach ($db->query('SELECT * from test') as $row) {
76+
print_r($row);
77+
}
78+
79+
$st = $db->prepare("UPDATE test SET some_bool_1=?, some_bool_2=?, some_int=? WHERE uid=?");
80+
81+
$values = [
82+
'uid' => 6,
83+
'some_bool_1' => (bool) 0,
84+
'some_bool_2' => (bool) 1,
85+
'some_int' => 2,
86+
];
87+
88+
$st->bindParam(1, $values['some_bool_1'], PDO::PARAM_BOOL);
89+
$st->bindParam(2, $values['some_bool_2'], PDO::PARAM_BOOL);
90+
$st->bindParam(3, $values['some_int'], PDO::PARAM_INT);
91+
$st->bindParam(4, $values['uid'], PDO::PARAM_INT);
92+
93+
$result = $st->execute();
94+
95+
if ($result === false) {
96+
var_dump($st->errorInfo());
97+
} else {
98+
print("ok prepare 2\n");
99+
}
100+
101+
foreach ($db->query('SELECT * from test') as $row) {
102+
print_r($row);
103+
}
104+
105+
// String true and false should fail
106+
$st = $db->prepare("UPDATE test SET some_bool_1=?, some_bool_2=?, some_int=? WHERE uid=?");
107+
108+
$values = [
109+
'uid' => 6,
110+
'some_bool_1' => 'true',
111+
'some_bool_2' => 'false',
112+
'some_int' => 3,
113+
];
114+
115+
$st->bindParam(1, $values['some_bool_1'], PDO::PARAM_BOOL);
116+
$st->bindParam(2, $values['some_bool_2'], PDO::PARAM_BOOL);
117+
$st->bindParam(3, $values['some_int'], PDO::PARAM_INT);
118+
$st->bindParam(4, $values['uid'], PDO::PARAM_INT);
119+
120+
$result = $st->execute();
121+
122+
if ($result === false) {
123+
var_dump($st->errorInfo());
124+
} else {
125+
print("ok prepare 3\n");
126+
}
127+
128+
foreach ($db->query('SELECT * from test') as $row) {
129+
print_r($row);
130+
}
131+
132+
// Null should not be treated as false
133+
$st = $db->prepare("UPDATE test SET some_bool_1=?, some_bool_2=?, some_int=? WHERE uid=?");
134+
135+
$values = [
136+
'uid' => 6,
137+
'some_bool_1' => true,
138+
'some_bool_2' => null,
139+
'some_int' => 4,
140+
];
141+
142+
$st->bindParam(1, $values['some_bool_1'], PDO::PARAM_BOOL);
143+
$st->bindParam(2, $values['some_bool_2'], PDO::PARAM_BOOL);
144+
$st->bindParam(3, $values['some_int'], PDO::PARAM_INT);
145+
$st->bindParam(4, $values['uid'], PDO::PARAM_INT);
146+
147+
$result = $st->execute();
148+
149+
if ($result === false) {
150+
var_dump($st->errorInfo());
151+
} else {
152+
print("ok prepare 4\n");
153+
}
154+
155+
foreach ($db->query('SELECT * from test') as $row) {
156+
print_r($row);
157+
}
158+
159+
// Integers converted correctly
160+
$st = $db->prepare("UPDATE test SET some_bool_1=?, some_bool_2=?, some_int=? WHERE uid=?");
161+
162+
$values = [
163+
'uid' => 6,
164+
'some_bool_1' => 256,
165+
'some_bool_2' => 0,
166+
'some_int' => 5,
167+
];
168+
169+
$st->bindParam(1, $values['some_bool_1'], PDO::PARAM_BOOL);
170+
$st->bindParam(2, $values['some_bool_2'], PDO::PARAM_BOOL);
171+
$st->bindParam(3, $values['some_int'], PDO::PARAM_INT);
172+
$st->bindParam(4, $values['uid'], PDO::PARAM_INT);
173+
174+
$result = $st->execute();
175+
176+
if ($result === false) {
177+
var_dump($st->errorInfo());
178+
} else {
179+
print("ok prepare 5\n");
180+
}
181+
182+
foreach ($db->query('SELECT * from test') as $row) {
183+
print_r($row);
184+
}
185+
186+
?>
187+
--CLEAN--
188+
<?php
189+
require dirname(__FILE__) . '/mysql_pdo_test.inc';
190+
MySQLPDOTest::dropTestTable();
191+
?>
192+
--EXPECTF--
193+
ok insert
194+
Array
195+
(
196+
[uid] => 6
197+
[0] => 6
198+
[some_bool_1] => 0
199+
[1] => 0
200+
[some_bool_2] => 1
201+
[2] => 1
202+
[some_int] => -23
203+
[3] => -23
204+
)
205+
ok prepare 1
206+
Array
207+
(
208+
[uid] => 6
209+
[0] => 6
210+
[some_bool_1] => 1
211+
[1] => 1
212+
[some_bool_2] => 0
213+
[2] => 0
214+
[some_int] => 1
215+
[3] => 1
216+
)
217+
ok prepare 2
218+
Array
219+
(
220+
[uid] => 6
221+
[0] => 6
222+
[some_bool_1] => 0
223+
[1] => 0
224+
[some_bool_2] => 1
225+
[2] => 1
226+
[some_int] => 2
227+
[3] => 2
228+
)
229+
230+
Warning: PDOStatement::execute(): SQLSTATE[HY000]: General error: 1366 Incorrect integer value: 'true' for column 'some_bool_1' at row 1 in %s
231+
array(3) {
232+
[0]=>
233+
string(5) "HY000"
234+
[1]=>
235+
int(1366)
236+
[2]=>
237+
string(65) "Incorrect integer value: 'true' for column 'some_bool_1' at row 1"
238+
}
239+
Array
240+
(
241+
[uid] => 6
242+
[0] => 6
243+
[some_bool_1] => 0
244+
[1] => 0
245+
[some_bool_2] => 1
246+
[2] => 1
247+
[some_int] => 2
248+
[3] => 2
249+
)
250+
251+
Warning: PDOStatement::execute(): SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'some_bool_2' cannot be null in %s
252+
array(3) {
253+
[0]=>
254+
string(5) "23000"
255+
[1]=>
256+
int(1048)
257+
[2]=>
258+
string(35) "Column 'some_bool_2' cannot be null"
259+
}
260+
Array
261+
(
262+
[uid] => 6
263+
[0] => 6
264+
[some_bool_1] => 0
265+
[1] => 0
266+
[some_bool_2] => 1
267+
[2] => 1
268+
[some_int] => 2
269+
[3] => 2
270+
)
271+
ok prepare 5
272+
Array
273+
(
274+
[uid] => 6
275+
[0] => 6
276+
[some_bool_1] => 1
277+
[1] => 1
278+
[some_bool_2] => 0
279+
[2] => 0
280+
[some_int] => 5
281+
[3] => 5
282+
)

ext/pdo_mysql/tests/bug_44707.phpt

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@ function bug_44707($db) {
3737

3838
$stmt = $db->prepare('INSERT INTO test(id, mybool) VALUES (?, ?)');
3939
$stmt->bindParam(1, $id);
40-
// From MySQL 4.1 on boolean and TINYINT don't match! INSERT will fail.
41-
// Versions prior to 4.1 have a weak test and will accept this.
4240
$stmt->bindParam(2, $mybool, PDO::PARAM_BOOL);
4341
var_dump($mybool);
4442

@@ -78,8 +76,6 @@ Native Prepared Statements
7876
bool(false)
7977
bool(false)
8078
bool(false)
81-
array(0) {
82-
}
8379
array(1) {
8480
[0]=>
8581
array(2) {
@@ -89,4 +85,20 @@ array(1) {
8985
string(1) "0"
9086
}
9187
}
88+
array(2) {
89+
[0]=>
90+
array(2) {
91+
["id"]=>
92+
string(1) "1"
93+
["mybool"]=>
94+
string(1) "0"
95+
}
96+
[1]=>
97+
array(2) {
98+
["id"]=>
99+
string(1) "1"
100+
["mybool"]=>
101+
string(1) "0"
102+
}
103+
}
92104
done!

0 commit comments

Comments
 (0)