From 72d1b292047cf55fbe12018b0159d4ae829f29fa 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 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. Closes GH-20214 --- ext/pdo/pdo_stmt.c | 4 ++++ ext/pdo_sqlite/tests/gh20214.phpt | 38 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 ext/pdo_sqlite/tests/gh20214.phpt diff --git a/ext/pdo/pdo_stmt.c b/ext/pdo/pdo_stmt.c index 73d4530e8f657..8ea8bac02447f 100644 --- a/ext/pdo/pdo_stmt.c +++ b/ext/pdo/pdo_stmt.c @@ -1628,6 +1628,10 @@ bool pdo_stmt_setup_fetch_mode(pdo_stmt_t *stmt, zend_long mode, uint32_t mode_a stmt->default_fetch_type = PDO_FETCH_BOTH; + if ((mode & ~PDO_FETCH_FLAGS) == PDO_FETCH_USE_DEFAULT) { + mode = stmt->dbh->default_fetch_type; + } + flags = mode & PDO_FETCH_FLAGS; if (!pdo_verify_fetch_mode(stmt->default_fetch_type, mode, mode_arg_num, false)) { diff --git a/ext/pdo_sqlite/tests/gh20214.phpt b/ext/pdo_sqlite/tests/gh20214.phpt new file mode 100644 index 0000000000000..29adc50b0b747 --- /dev/null +++ b/ext/pdo_sqlite/tests/gh20214.phpt @@ -0,0 +1,38 @@ +--TEST-- +GH-20214 (PDO::FETCH_DEFAULT unexpected behavior with PDOStatement::setFetchMode) +--EXTENSIONS-- +pdo_sqlite +--FILE-- +setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); + +// setFetchMode with FETCH_DEFAULT should use connection default (FETCH_OBJ) +$stmt = $db->query("SELECT 'v1' AS c1, 'v2' AS c2"); +$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 'v1' AS c1, 'v2' AS c2"); +$row = $stmt->fetch(PDO::FETCH_DEFAULT); +var_dump($row instanceof stdClass); + +// fetchAll with FETCH_DEFAULT should also use connection default +$stmt = $db->query("SELECT 'v1' AS c1, 'v2' AS c2"); +$rows = $stmt->fetchAll(PDO::FETCH_DEFAULT); +var_dump($rows[0] instanceof stdClass); + +// setFetchMode then fetch without argument +$stmt = $db->query("SELECT 'v1' AS c1, 'v2' AS c2"); +$stmt->setFetchMode(PDO::FETCH_DEFAULT); +$row = $stmt->fetch(); +var_dump($row instanceof stdClass); +?> +--EXPECT-- +bool(true) +string(2) "v1" +bool(true) +bool(true) +bool(true)