Skip to content

Commit 793bf12

Browse files
committed
Merge branch 'PHP-7.4' into PHP-8.0
* PHP-7.4: Fix bug #79375
2 parents 2693f79 + b03776a commit 793bf12

File tree

7 files changed

+305
-7
lines changed

7 files changed

+305
-7
lines changed

ext/mysqli/mysqli_api.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1070,7 +1070,8 @@ void mysqli_stmt_fetch_mysqlnd(INTERNAL_FUNCTION_PARAMETERS)
10701070
}
10711071
MYSQLI_FETCH_RESOURCE_STMT(stmt, mysql_stmt, MYSQLI_STATUS_VALID);
10721072

1073-
if (FAIL == mysqlnd_stmt_fetch(stmt->stmt, &fetched_anything)) {
1073+
if (FAIL == mysqlnd_stmt_fetch(stmt->stmt, &fetched_anything)) {
1074+
MYSQLI_REPORT_STMT_ERROR(stmt->stmt);
10741075
RETURN_BOOL(FALSE);
10751076
} else if (fetched_anything == TRUE) {
10761077
RETURN_BOOL(TRUE);

ext/mysqli/tests/bug79375.phpt

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
--TEST--
2+
Bug #79375: mysqli_store_result does not report error from lock wait timeout
3+
--SKIPIF--
4+
<?php
5+
require_once('skipif.inc');
6+
require_once('skipifconnectfailure.inc');
7+
if (!defined('MYSQLI_STORE_RESULT_COPY_DATA')) die('skip requires mysqlnd');
8+
?>
9+
--FILE--
10+
<?php
11+
12+
require_once("connect.inc");
13+
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
14+
$mysqli = new my_mysqli($host, $user, $passwd, $db, $port, $socket);
15+
$mysqli2 = new my_mysqli($host, $user, $passwd, $db, $port, $socket);
16+
17+
$mysqli->query('DROP TABLE IF EXISTS test');
18+
$mysqli->query('CREATE TABLE test (first int) ENGINE = InnoDB');
19+
$mysqli->query('INSERT INTO test VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9)');
20+
21+
function testStmtStoreResult(mysqli $mysqli, string $name) {
22+
$mysqli->query("SET innodb_lock_wait_timeout = 1");
23+
$mysqli->query("START TRANSACTION");
24+
$query = "SELECT first FROM test WHERE first = 1 FOR UPDATE";
25+
echo "Running query on $name\n";
26+
$stmt = $mysqli->prepare($query);
27+
$stmt->execute();
28+
try {
29+
$stmt->store_result();
30+
echo "Got {$stmt->num_rows} for $name\n";
31+
} catch(mysqli_sql_exception $e) {
32+
echo $e->getMessage()."\n";
33+
}
34+
}
35+
function testStmtGetResult(mysqli $mysqli, string $name) {
36+
$mysqli->query("SET innodb_lock_wait_timeout = 1");
37+
$mysqli->query("START TRANSACTION");
38+
$query = "SELECT first FROM test WHERE first = 1 FOR UPDATE";
39+
echo "Running query on $name\n";
40+
$stmt = $mysqli->prepare($query);
41+
$stmt->execute();
42+
try {
43+
$res = $stmt->get_result();
44+
echo "Got {$res->num_rows} for $name\n";
45+
} catch(mysqli_sql_exception $e) {
46+
echo $e->getMessage()."\n";
47+
}
48+
}
49+
function testNormalQuery(mysqli $mysqli, string $name) {
50+
$mysqli->query("SET innodb_lock_wait_timeout = 1");
51+
$mysqli->query("START TRANSACTION");
52+
$query = "SELECT first FROM test WHERE first = 1 FOR UPDATE";
53+
echo "Running query on $name\n";
54+
try {
55+
$res = $mysqli->query($query);
56+
echo "Got {$res->num_rows} for $name\n";
57+
} catch(mysqli_sql_exception $e) {
58+
echo $e->getMessage()."\n";
59+
}
60+
}
61+
function testStmtUseResult(mysqli $mysqli, string $name) {
62+
$mysqli->query("SET innodb_lock_wait_timeout = 1");
63+
$mysqli->query("START TRANSACTION");
64+
$query = "SELECT first FROM test WHERE first = 1 FOR UPDATE";
65+
echo "Running query on $name\n";
66+
$stmt = $mysqli->prepare($query);
67+
$stmt->execute();
68+
try {
69+
$stmt->fetch(); // should throw an error
70+
$stmt->fetch();
71+
echo "Got {$stmt->num_rows} for $name\n";
72+
} catch (mysqli_sql_exception $e) {
73+
echo $e->getMessage()."\n";
74+
}
75+
}
76+
function testResultFetchRow(mysqli $mysqli, string $name) {
77+
$mysqli->query("SET innodb_lock_wait_timeout = 1");
78+
$mysqli->query("START TRANSACTION");
79+
$query = "SELECT first FROM test WHERE first = 1 FOR UPDATE";
80+
echo "Running query on $name\n";
81+
$res = $mysqli->query($query, MYSQLI_USE_RESULT);
82+
try {
83+
$res->fetch_row();
84+
$res->fetch_row();
85+
echo "Got {$res->num_rows} for $name\n";
86+
} catch(mysqli_sql_exception $e) {
87+
echo $e->getMessage()."\n";
88+
}
89+
}
90+
91+
testStmtStoreResult($mysqli, 'first connection');
92+
testStmtStoreResult($mysqli2, 'second connection');
93+
94+
$mysqli->close();
95+
$mysqli2->close();
96+
97+
echo "\n";
98+
// try it again for get_result
99+
$mysqli = new my_mysqli($host, $user, $passwd, $db, $port, $socket);
100+
$mysqli2 = new my_mysqli($host, $user, $passwd, $db, $port, $socket);
101+
102+
testStmtGetResult($mysqli, 'first connection');
103+
testStmtGetResult($mysqli2, 'second connection');
104+
105+
$mysqli->close();
106+
$mysqli2->close();
107+
108+
echo "\n";
109+
// try it again with unprepared query
110+
$mysqli = new my_mysqli($host, $user, $passwd, $db, $port, $socket);
111+
$mysqli2 = new my_mysqli($host, $user, $passwd, $db, $port, $socket);
112+
113+
testNormalQuery($mysqli, 'first connection');
114+
testNormalQuery($mysqli2, 'second connection');
115+
116+
$mysqli->close();
117+
$mysqli2->close();
118+
119+
echo "\n";
120+
// try it again with unprepared query
121+
$mysqli = new my_mysqli($host, $user, $passwd, $db, $port, $socket);
122+
$mysqli2 = new my_mysqli($host, $user, $passwd, $db, $port, $socket);
123+
124+
testStmtUseResult($mysqli, 'first connection');
125+
testStmtUseResult($mysqli2, 'second connection');
126+
127+
$mysqli->close();
128+
$mysqli2->close();
129+
130+
echo "\n";
131+
// try it again using fetch_row on a result object
132+
$mysqli = new my_mysqli($host, $user, $passwd, $db, $port, $socket);
133+
$mysqli2 = new my_mysqli($host, $user, $passwd, $db, $port, $socket);
134+
135+
testResultFetchRow($mysqli, 'first connection');
136+
testResultFetchRow($mysqli2, 'second connection');
137+
138+
$mysqli->close();
139+
$mysqli2->close();
140+
141+
?>
142+
--CLEAN--
143+
<?php
144+
require_once("clean_table.inc");
145+
?>
146+
--EXPECTF--
147+
Running query on first connection
148+
Got %d for first connection
149+
Running query on second connection
150+
Lock wait timeout exceeded; try restarting transaction
151+
152+
Running query on first connection
153+
Got %d for first connection
154+
Running query on second connection
155+
Lock wait timeout exceeded; try restarting transaction
156+
157+
Running query on first connection
158+
Got %d for first connection
159+
Running query on second connection
160+
Lock wait timeout exceeded; try restarting transaction
161+
162+
Running query on first connection
163+
Got %d for first connection
164+
Running query on second connection
165+
Lock wait timeout exceeded; try restarting transaction
166+
167+
Running query on first connection
168+
Got 1 for first connection
169+
Running query on second connection
170+
171+
Warning: mysqli_result::fetch_row(): Error while reading a row in %s on line %d
172+
Got 0 for second connection

ext/mysqlnd/mysqlnd_ps.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,11 @@ MYSQLND_METHOD(mysqlnd_stmt, store_result)(MYSQLND_STMT * const s)
119119
stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED;
120120
} else {
121121
COPY_CLIENT_ERROR(conn->error_info, result->stored_data->error_info);
122+
COPY_CLIENT_ERROR(stmt->error_info, result->stored_data->error_info);
122123
stmt->result->m.free_result_contents(stmt->result);
123124
stmt->result = NULL;
124125
stmt->state = MYSQLND_STMT_PREPARED;
126+
DBG_RETURN(NULL);
125127
}
126128

127129
DBG_RETURN(result);
@@ -176,7 +178,7 @@ MYSQLND_METHOD(mysqlnd_stmt, get_result)(MYSQLND_STMT * const s)
176178
break;
177179
}
178180

179-
if ((result = result->m.store_result(result, conn, MYSQLND_STORE_PS | MYSQLND_STORE_NO_COPY))) {
181+
if (result->m.store_result(result, conn, MYSQLND_STORE_PS | MYSQLND_STORE_NO_COPY)) {
180182
UPSERT_STATUS_SET_AFFECTED_ROWS(stmt->upsert_status, result->stored_data->row_count);
181183
stmt->state = MYSQLND_STMT_PREPARED;
182184
result->type = MYSQLND_RES_PS_BUF;
@@ -879,7 +881,9 @@ mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES * result, void * param, const unsi
879881
} else if (ret == FAIL) {
880882
if (row_packet->error_info.error_no) {
881883
COPY_CLIENT_ERROR(conn->error_info, row_packet->error_info);
882-
COPY_CLIENT_ERROR(stmt->error_info, row_packet->error_info);
884+
if (stmt) {
885+
COPY_CLIENT_ERROR(stmt->error_info, row_packet->error_info);
886+
}
883887
}
884888
SET_CONNECTION_STATE(&conn->state, CONN_READY);
885889
result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */

ext/mysqlnd/mysqlnd_result.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -905,7 +905,7 @@ MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row)(MYSQLND_RES * result, void
905905
result->memory_pool->checkpoint = checkpoint;
906906

907907
DBG_INF_FMT("ret=%s fetched=%u", ret == PASS? "PASS":"FAIL", *fetched_anything);
908-
DBG_RETURN(PASS);
908+
DBG_RETURN(ret);
909909
}
910910
/* }}} */
911911

ext/pdo_mysql/mysql_statement.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,10 @@ static int pdo_mysql_stmt_execute_prepared_libmysql(pdo_stmt_t *stmt) /* {{{ */
255255

256256
/* if buffered, pre-fetch all the data */
257257
if (H->buffered) {
258-
mysql_stmt_store_result(S->stmt);
258+
if (mysql_stmt_store_result(S->stmt)) {
259+
pdo_mysql_error_stmt(stmt);
260+
PDO_DBG_RETURN(0);
261+
}
259262
}
260263
}
261264
}
@@ -298,6 +301,7 @@ static int pdo_mysql_stmt_execute_prepared_mysqlnd(pdo_stmt_t *stmt) /* {{{ */
298301
/* if buffered, pre-fetch all the data */
299302
if (H->buffered) {
300303
if (mysql_stmt_store_result(S->stmt)) {
304+
pdo_mysql_error_stmt(stmt);
301305
PDO_DBG_RETURN(0);
302306
}
303307
}
@@ -386,7 +390,8 @@ static int pdo_mysql_stmt_next_rowset(pdo_stmt_t *stmt) /* {{{ */
386390
/* if buffered, pre-fetch all the data */
387391
if (H->buffered) {
388392
if (mysql_stmt_store_result(S->stmt)) {
389-
PDO_DBG_RETURN(1);
393+
pdo_mysql_error_stmt(stmt);
394+
PDO_DBG_RETURN(0);
390395
}
391396
}
392397
}
@@ -621,6 +626,7 @@ static int pdo_mysql_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori
621626
PDO_DBG_INF_FMT("stmt=%p", S->stmt);
622627
if (S->stmt) {
623628
if (FAIL == mysqlnd_stmt_fetch(S->stmt, &fetched_anything) || fetched_anything == FALSE) {
629+
pdo_mysql_error_stmt(stmt);
624630
PDO_DBG_RETURN(0);
625631
}
626632

ext/pdo_mysql/tests/bug79375.phpt

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
--TEST--
2+
Bug #79375: mysqli_store_result does not report error from lock wait timeout
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) die('skip not loaded');
6+
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'skipif.inc');
7+
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
8+
MySQLPDOTest::skip();
9+
?>
10+
--FILE--
11+
<?php
12+
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
13+
14+
function createDB(): PDO {
15+
$db = MySQLPDOTest::factory();
16+
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
17+
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
18+
return $db;
19+
}
20+
21+
$db = createDB();
22+
$db2 = createDB();
23+
$db->query('DROP TABLE IF EXISTS test');
24+
$db->query('CREATE TABLE test (first int) ENGINE = InnoDB');
25+
$db->query('INSERT INTO test VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9)');
26+
27+
function testNormalQuery(PDO $db, string $name) {
28+
$db->exec("SET innodb_lock_wait_timeout = 1");
29+
$db->exec("START TRANSACTION");
30+
$query = "SELECT first FROM test WHERE first = 1 FOR UPDATE";
31+
echo "Running query on $name\n";
32+
try {
33+
$stmt = $db->query($query);
34+
echo "Got {$stmt->rowCount()} for $name\n";
35+
} catch (PDOException $e) {
36+
echo $e->getMessage()."\n";
37+
}
38+
}
39+
40+
function testPrepareExecute(PDO $db, string $name) {
41+
$db->exec("SET innodb_lock_wait_timeout = 1");
42+
$db->exec("START TRANSACTION");
43+
$query = "SELECT first FROM test WHERE first = 1 FOR UPDATE";
44+
echo "Running query on $name\n";
45+
$stmt = $db->prepare($query);
46+
try {
47+
$stmt->execute();
48+
echo "Got {$stmt->rowCount()} for $name\n";
49+
} catch (PDOException $e) {
50+
echo $e->getMessage()."\n";
51+
}
52+
}
53+
54+
function testUnbuffered(PDO $db, string $name) {
55+
$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
56+
$db->exec("SET innodb_lock_wait_timeout = 1");
57+
$db->exec("START TRANSACTION");
58+
$query = "SELECT first FROM test WHERE first = 1 FOR UPDATE";
59+
echo "Running query on $name\n";
60+
$stmt = $db->prepare($query);
61+
$stmt->execute();
62+
try {
63+
$rows = $stmt->fetchAll();
64+
$count = count($rows);
65+
echo "Got $count for $name\n";
66+
} catch (PDOException $e) {
67+
echo $e->getMessage()."\n";
68+
}
69+
}
70+
71+
testNormalQuery($db, 'first connection');
72+
testNormalQuery($db2, 'second connection');
73+
unset($db);
74+
unset($db2);
75+
echo "\n";
76+
77+
$db = createDB();
78+
$db2 = createDB();
79+
testPrepareExecute($db, 'first connection');
80+
testPrepareExecute($db2, 'second connection');
81+
unset($db);
82+
unset($db2);
83+
echo "\n";
84+
85+
$db = createDB();
86+
$db2 = createDB();
87+
testUnbuffered($db, 'first connection');
88+
testUnbuffered($db2, 'second connection');
89+
unset($db);
90+
unset($db2);
91+
echo "\n";
92+
93+
?>
94+
--CLEAN--
95+
<?php
96+
require __DIR__ . '/mysql_pdo_test.inc';
97+
MySQLPDOTest::dropTestTable();
98+
?>
99+
--EXPECT--
100+
Running query on first connection
101+
Got 1 for first connection
102+
Running query on second connection
103+
SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction
104+
105+
Running query on first connection
106+
Got 1 for first connection
107+
Running query on second connection
108+
SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction
109+
110+
Running query on first connection
111+
Got 1 for first connection
112+
Running query on second connection
113+
SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction

ext/pdo_mysql/tests/bug_74376.phpt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,7 @@ $stmt = $db->query("select (select 1 union select 2)");
2323

2424
print "ok";
2525
?>
26-
--EXPECT--
26+
--EXPECTF--
27+
28+
Warning: PDO::query(): SQLSTATE[21000]: Cardinality violation: 1242 Subquery returns more than 1 row in %s on line %d
2729
ok

0 commit comments

Comments
 (0)