Skip to content

Commit bc16684

Browse files
committed
MySQLnd: Support cursors in store/get result
This fixes two related issues: 1. When a PS with cursor is used in store_result/get_result, perform a COM_FETCH with maximum number of rows rather than silently switching to an unbuffered result set (in the case of store_result) or erroring (in the case of get_result). In the future, we might want to make get_result unbuffered for PS with cursors, as using cursors with buffered result sets doesn't really make sense. Unlike store_result, get_result isn't very explicit about what kind of result set is desired. 2. If the client did not request a cursor, but the server reports that a cursor exists, ignore this and treat the PS as if it has no cursor (i.e. to not use COM_FETCH). It appears to be a server side bug that a cursor used inside an SP will be reported to the client, even though the client cannot use the cursor. Fixes bug #64638, bug #72862, bug #77935. Closes GH-6518.
1 parent 315f3f8 commit bc16684

File tree

5 files changed

+231
-92
lines changed

5 files changed

+231
-92
lines changed

NEWS

+6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ PHP NEWS
88
- MySQLi:
99
. Fixed bug #67983 (mysqlnd with MYSQLI_OPT_INT_AND_FLOAT_NATIVE fails to
1010
interpret bit columns). (Nikita)
11+
. Fixed bug #64638 (Fetching resultsets from stored procedure with cursor
12+
fails). (Nikita)
13+
. Fixed bug #72862 (segfault using prepared statements on stored procedures
14+
that use a cursor). (Nikita)
15+
. Fixed bug #77935 (Crash in mysqlnd_fetch_stmt_row_cursor when calling an SP
16+
with a cursor). (Nikita)
1117

1218
07 Jan 2021, PHP 7.4.14
1319

ext/mysqli/tests/bug77935.phpt

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
--TEST--
2+
Bug #77935: Crash in mysqlnd_fetch_stmt_row_cursor when calling an SP with a cursor
3+
--SKIPIF--
4+
<?php
5+
require_once('skipif.inc');
6+
require_once('skipifconnectfailure.inc');
7+
?>
8+
--FILE--
9+
<?php
10+
require_once(__DIR__ . '/connect.inc');
11+
12+
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
13+
$db = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket);
14+
$db->query('DROP PROCEDURE IF EXISTS testSp');
15+
$db->query(<<<'SQL'
16+
CREATE
17+
PROCEDURE `testSp`()
18+
BEGIN
19+
DECLARE `cur` CURSOR FOR SELECT 1;
20+
OPEN `cur`;
21+
CLOSE `cur`;
22+
SELECT 1;
23+
END;
24+
SQL);
25+
26+
$stmt = $db->prepare("CALL testSp()");
27+
$stmt->execute();
28+
$result = $stmt->get_result();
29+
while ($row = $result->fetch_assoc()) {
30+
var_dump($row);
31+
}
32+
33+
?>
34+
--EXPECT--
35+
array(1) {
36+
[1]=>
37+
int(1)
38+
}

ext/mysqli/tests/mysqli_stmt_get_result.phpt

+16-33
Original file line numberDiff line numberDiff line change
@@ -110,21 +110,7 @@ if (!function_exists('mysqli_stmt_get_result'))
110110

111111
mysqli_stmt_close($stmt);
112112

113-
if (!$stmt = mysqli_stmt_init($link))
114-
printf("[032] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
115-
116-
if (!mysqli_stmt_prepare($stmt, "SELECT id, label FROM test ORDER BY id LIMIT 2"))
117-
printf("[033] [%d] %s\n", mysqli_stmt_errno($stmt), mysqli_stmt_error($stmt));
118-
119-
if (!mysqli_stmt_execute($stmt))
120-
printf("[034] [%d] %s\n", mysqli_stmt_errno($stmt), mysqli_stmt_error($stmt));
121-
122-
$id = NULL;
123-
$label = NULL;
124-
if (true !== ($tmp = mysqli_stmt_bind_result($stmt, $id, $label)))
125-
printf("[035] Expecting boolean/true, got %s/%s\n", gettype($tmp), var_export($tmp, 1));
126-
127-
// get_result cannot be used in PS cursor mode
113+
// get_result can be used in PS cursor mode
128114
if (!$stmt = mysqli_stmt_init($link))
129115
printf("[030] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
130116

@@ -137,23 +123,10 @@ if (!function_exists('mysqli_stmt_get_result'))
137123
if (!mysqli_stmt_execute($stmt))
138124
printf("[033] [%d] %s\n", mysqli_stmt_errno($stmt), mysqli_stmt_error($stmt));
139125

140-
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
141-
try {
142-
$res = mysqli_stmt_get_result($stmt);
143-
// we expect no segfault if we try to fetch a row because get_result should throw an error or return false
144-
mysqli_fetch_assoc($res);
145-
} catch (\mysqli_sql_exception $e) {
146-
echo $e->getMessage() . "\n";
147-
}
148-
149-
try {
150-
$res = $stmt->get_result();
151-
// we expect no segfault if we try to fetch a row because get_result should throw an error or return false
152-
$res->fetch_assoc();
153-
} catch (\mysqli_sql_exception $e) {
154-
echo $e->getMessage() . "\n";
126+
$result = mysqli_stmt_get_result($stmt);
127+
while ($row = mysqli_fetch_assoc($result)) {
128+
var_dump($row);
155129
}
156-
mysqli_report(MYSQLI_REPORT_OFF);
157130

158131
if (!$stmt = mysqli_stmt_init($link))
159132
printf("[034] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
@@ -213,8 +186,18 @@ Warning: mysqli_stmt_fetch(): invalid object or resource mysqli_stmt
213186

214187
Warning: mysqli_stmt_get_result(): invalid object or resource mysqli_stmt
215188
in %s on line %d
216-
mysqli_stmt_get_result() cannot be used with cursors
217-
get_result() cannot be used with cursors
189+
array(2) {
190+
["id"]=>
191+
int(1)
192+
["label"]=>
193+
string(1) "a"
194+
}
195+
array(2) {
196+
["id"]=>
197+
int(2)
198+
["label"]=>
199+
string(1) "b"
200+
}
218201
[040] [2014] [Commands out of sync; you can't run this command now]
219202
[041] [0] []
220203
array(2) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
--TEST--
2+
PS using cursor and returning multiple result sets
3+
--SKIPIF--
4+
<?php
5+
require_once('skipif.inc');
6+
require_once('skipifconnectfailure.inc');
7+
?>
8+
--FILE--
9+
<?php
10+
require_once(__DIR__ . '/connect.inc');
11+
12+
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
13+
$db = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket);
14+
$db->query('DROP PROCEDURE IF EXISTS testPs');
15+
$db->query(<<<'SQL'
16+
CREATE PROCEDURE testPs() BEGIN
17+
DECLARE testCursor CURSOR FOR SELECT 'stuff';
18+
OPEN testCursor;
19+
CLOSE testCursor;
20+
SELECT 1 as a, 2 as b;
21+
SELECT 3 as a, 4 as b;
22+
END
23+
SQL
24+
);
25+
26+
echo "use_result:\n";
27+
$stmt = $db->prepare("call testPs()");
28+
$stmt->execute();
29+
$stmt->bind_result($v1, $v2);
30+
while ($stmt->fetch()) {
31+
var_dump($v1, $v2);
32+
}
33+
34+
$stmt->next_result();
35+
$stmt->bind_result($v1, $v2);
36+
while ($stmt->fetch()) {
37+
var_dump($v1, $v2);
38+
}
39+
$stmt->next_result();
40+
41+
echo "\nstore_result:\n";
42+
$stmt = $db->prepare("call testPs()");
43+
$stmt->execute();
44+
$stmt->store_result();
45+
$stmt->bind_result($v1, $v2);
46+
while ($stmt->fetch()) {
47+
var_dump($v1, $v2);
48+
}
49+
50+
$stmt->next_result();
51+
$stmt->store_result();
52+
$stmt->bind_result($v1, $v2);
53+
while ($stmt->fetch()) {
54+
var_dump($v1, $v2);
55+
}
56+
$stmt->next_result();
57+
58+
echo "\nget_result:\n";
59+
$stmt = $db->prepare("call testPs()");
60+
$stmt->execute();
61+
$result = $stmt->get_result();
62+
while ($row = $result->fetch_assoc()) {
63+
var_dump($row);
64+
}
65+
66+
$stmt->next_result();
67+
$result = $stmt->get_result();
68+
while ($row = $result->fetch_assoc()) {
69+
var_dump($row);
70+
}
71+
$stmt->next_result();
72+
73+
?>
74+
--EXPECT--
75+
use_result:
76+
int(1)
77+
int(2)
78+
int(3)
79+
int(4)
80+
81+
store_result:
82+
int(1)
83+
int(2)
84+
int(3)
85+
int(4)
86+
87+
get_result:
88+
array(2) {
89+
["a"]=>
90+
int(1)
91+
["b"]=>
92+
int(2)
93+
}
94+
array(2) {
95+
["a"]=>
96+
int(3)
97+
["b"]=>
98+
int(4)
99+
}

0 commit comments

Comments
 (0)