Skip to content

Commit 7bd27e7

Browse files
iliaalSakiTakamachi
authored andcommitted
Fix GH-20214: PDO::FETCH_DEFAULT unexpected behavior with PDOStatement::setFetchMode (#21434)
When setFetchMode(PDO::FETCH_DEFAULT) is called, mode=0 (PDO_FETCH_USE_DEFAULT) gets stored as the statement's default fetch type. Later, do_fetch() tries to resolve PDO_FETCH_USE_DEFAULT by reading stmt->default_fetch_type, which is also 0 — circular reference that on 8.4 silently fell through to FETCH_BOTH and on master throws a ValueError. Resolve PDO_FETCH_USE_DEFAULT to the connection-level default early in pdo_stmt_setup_fetch_mode(), before flags extraction and the mode switch, so the rest of the function processes the actual fetch mode. fixes #20214 closes #21434
1 parent ea43822 commit 7bd27e7

3 files changed

Lines changed: 71 additions & 0 deletions

File tree

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ PHP NEWS
22
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
33
?? ??? ????, PHP 8.4.22
44

5+
- PDO:
6+
. Fixed bug GH-20214 (PDO::FETCH_DEFAULT unexpected behavior with
7+
setFetchMode). (iliaal)
58

69
07 May 2026, PHP 8.4.21
710

ext/pdo/pdo_stmt.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1733,6 +1733,10 @@ bool pdo_stmt_setup_fetch_mode(pdo_stmt_t *stmt, zend_long mode, uint32_t mode_a
17331733

17341734
flags = mode & PDO_FETCH_FLAGS;
17351735

1736+
if ((mode & ~PDO_FETCH_FLAGS) == PDO_FETCH_USE_DEFAULT) {
1737+
mode = stmt->dbh->default_fetch_type | flags;
1738+
}
1739+
17361740
if (!pdo_stmt_verify_mode(stmt, mode, mode_arg_num, false)) {
17371741
return false;
17381742
}

ext/pdo/tests/gh20214.phpt

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
--TEST--
2+
GH-20214 (PDO::FETCH_DEFAULT unexpected behavior with PDOStatement::setFetchMode)
3+
--EXTENSIONS--
4+
pdo
5+
--SKIPIF--
6+
<?php
7+
$dir = getenv('REDIR_TEST_DIR');
8+
if (false == $dir) die('skip no driver');
9+
require_once $dir . 'pdo_test.inc';
10+
PDOTest::skip();
11+
?>
12+
--FILE--
13+
<?php
14+
if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.__DIR__ . '/../../pdo/tests/');
15+
require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
16+
$db = PDOTest::factory();
17+
18+
$db->exec('CREATE TABLE gh20214 (c1 VARCHAR(10), c2 VARCHAR(10))');
19+
$db->exec("INSERT INTO gh20214 VALUES ('v1', 'v2')");
20+
21+
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
22+
23+
// setFetchMode with FETCH_DEFAULT should use connection default (FETCH_OBJ)
24+
$stmt = $db->query('SELECT c1, c2 FROM gh20214');
25+
$stmt->setFetchMode(PDO::FETCH_DEFAULT);
26+
$row = $stmt->fetch();
27+
var_dump($row instanceof stdClass);
28+
var_dump($row->c1);
29+
30+
// fetch with FETCH_DEFAULT should also use connection default
31+
$stmt = $db->query('SELECT c1, c2 FROM gh20214');
32+
$row = $stmt->fetch(PDO::FETCH_DEFAULT);
33+
var_dump($row instanceof stdClass);
34+
35+
// fetchAll with FETCH_DEFAULT should also use connection default
36+
$stmt = $db->query('SELECT c1, c2 FROM gh20214');
37+
$rows = $stmt->fetchAll(PDO::FETCH_DEFAULT);
38+
var_dump($rows[0] instanceof stdClass);
39+
40+
// setFetchMode then fetch without argument
41+
$stmt = $db->query('SELECT c1, c2 FROM gh20214');
42+
$stmt->setFetchMode(PDO::FETCH_DEFAULT);
43+
$row = $stmt->fetch();
44+
var_dump($row instanceof stdClass);
45+
46+
// query() with FETCH_DEFAULT as second argument
47+
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_NUM);
48+
$stmt = $db->query('SELECT c1, c2 FROM gh20214', PDO::FETCH_DEFAULT);
49+
$row = $stmt->fetch();
50+
var_dump(is_array($row) && isset($row[0]));
51+
?>
52+
--CLEAN--
53+
<?php
54+
require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
55+
$db = PDOTest::factory();
56+
PDOTest::dropTableIfExists($db, "gh20214");
57+
?>
58+
--EXPECT--
59+
bool(true)
60+
string(2) "v1"
61+
bool(true)
62+
bool(true)
63+
bool(true)
64+
bool(true)

0 commit comments

Comments
 (0)