From 7bd27e73e9df4dbd6af8ea79125dd7e08ba414ec Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Fri, 13 Mar 2026 18:17:40 -0400 Subject: [PATCH] Fix GH-20214: PDO::FETCH_DEFAULT unexpected behavior with PDOStatement::setFetchMode (#21434) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- NEWS | 3 ++ ext/pdo/pdo_stmt.c | 4 +++ ext/pdo/tests/gh20214.phpt | 64 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 ext/pdo/tests/gh20214.phpt diff --git a/NEWS b/NEWS index b110b17bef21c..b9576a4560825 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.4.22 +- PDO: + . Fixed bug GH-20214 (PDO::FETCH_DEFAULT unexpected behavior with + setFetchMode). (iliaal) 07 May 2026, PHP 8.4.21 diff --git a/ext/pdo/pdo_stmt.c b/ext/pdo/pdo_stmt.c index 9ba82e822b676..2ae3b1dd9e8ca 100644 --- a/ext/pdo/pdo_stmt.c +++ b/ext/pdo/pdo_stmt.c @@ -1733,6 +1733,10 @@ bool pdo_stmt_setup_fetch_mode(pdo_stmt_t *stmt, zend_long mode, uint32_t mode_a flags = mode & PDO_FETCH_FLAGS; + if ((mode & ~PDO_FETCH_FLAGS) == PDO_FETCH_USE_DEFAULT) { + mode = stmt->dbh->default_fetch_type | flags; + } + if (!pdo_stmt_verify_mode(stmt, mode, mode_arg_num, false)) { return false; } diff --git a/ext/pdo/tests/gh20214.phpt b/ext/pdo/tests/gh20214.phpt new file mode 100644 index 0000000000000..8afd667558c4e --- /dev/null +++ b/ext/pdo/tests/gh20214.phpt @@ -0,0 +1,64 @@ +--TEST-- +GH-20214 (PDO::FETCH_DEFAULT unexpected behavior with PDOStatement::setFetchMode) +--EXTENSIONS-- +pdo +--SKIPIF-- + +--FILE-- +exec('CREATE TABLE gh20214 (c1 VARCHAR(10), c2 VARCHAR(10))'); +$db->exec("INSERT INTO gh20214 VALUES ('v1', 'v2')"); + +$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); + +// setFetchMode with FETCH_DEFAULT should use connection default (FETCH_OBJ) +$stmt = $db->query('SELECT c1, c2 FROM gh20214'); +$stmt->setFetchMode(PDO::FETCH_DEFAULT); +$row = $stmt->fetch(); +var_dump($row instanceof stdClass); +var_dump($row->c1); + +// fetch with FETCH_DEFAULT should also use connection default +$stmt = $db->query('SELECT c1, c2 FROM gh20214'); +$row = $stmt->fetch(PDO::FETCH_DEFAULT); +var_dump($row instanceof stdClass); + +// fetchAll with FETCH_DEFAULT should also use connection default +$stmt = $db->query('SELECT c1, c2 FROM gh20214'); +$rows = $stmt->fetchAll(PDO::FETCH_DEFAULT); +var_dump($rows[0] instanceof stdClass); + +// setFetchMode then fetch without argument +$stmt = $db->query('SELECT c1, c2 FROM gh20214'); +$stmt->setFetchMode(PDO::FETCH_DEFAULT); +$row = $stmt->fetch(); +var_dump($row instanceof stdClass); + +// query() with FETCH_DEFAULT as second argument +$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_NUM); +$stmt = $db->query('SELECT c1, c2 FROM gh20214', PDO::FETCH_DEFAULT); +$row = $stmt->fetch(); +var_dump(is_array($row) && isset($row[0])); +?> +--CLEAN-- + +--EXPECT-- +bool(true) +string(2) "v1" +bool(true) +bool(true) +bool(true) +bool(true)