From b5a8d39700e63234580fd1889d10045925a794b4 Mon Sep 17 00:00:00 2001 From: AllenJB Date: Sat, 18 Oct 2025 09:40:35 +0100 Subject: [PATCH 01/14] PDO: Tests for fetch modes uncovered behavior --- .../fetch-modes/named/fetch-methods.phpt | 100 +++++++++ ext/pdo/tests/fetch-modes/named/group.phpt | 201 ++++++++++++++++++ ext/pdo/tests/fetch-modes/named/unique.phpt | 96 +++++++++ ext/pdo/tests/fetch-modes/setup.php | 51 +++++ 4 files changed, 448 insertions(+) create mode 100644 ext/pdo/tests/fetch-modes/named/fetch-methods.phpt create mode 100644 ext/pdo/tests/fetch-modes/named/group.phpt create mode 100644 ext/pdo/tests/fetch-modes/named/unique.phpt create mode 100644 ext/pdo/tests/fetch-modes/setup.php diff --git a/ext/pdo/tests/fetch-modes/named/fetch-methods.phpt b/ext/pdo/tests/fetch-modes/named/fetch-methods.phpt new file mode 100644 index 0000000000000..f52af9763e31d --- /dev/null +++ b/ext/pdo/tests/fetch-modes/named/fetch-methods.phpt @@ -0,0 +1,100 @@ +--TEST-- +PDO fetch mode NAMED +--EXTENSIONS-- +pdo +--SKIPIF-- + +--FILE-- +query($query); +while (false !== ($result = $stmt->fetch($fetchMode))) { + var_dump($result); +} + +print "\nfetchAll:\n"; +$stmt = $db->query($query); +$result = $stmt->fetchAll($fetchMode); +var_dump($result); + +--EXPECT-- +fetch: +array(4) { + ["userid"]=> + int(104) + ["name"]=> + array(2) { + [0]=> + string(5) "Chris" + [1]=> + NULL + } + ["country"]=> + string(7) "Ukraine" + ["referred_by_userid"]=> + NULL +} +array(4) { + ["userid"]=> + int(107) + ["name"]=> + array(2) { + [0]=> + string(5) "Robin" + [1]=> + string(5) "Chris" + } + ["country"]=> + string(7) "Germany" + ["referred_by_userid"]=> + int(104) +} + +fetchAll: +array(2) { + [0]=> + array(4) { + ["userid"]=> + int(104) + ["name"]=> + array(2) { + [0]=> + string(5) "Chris" + [1]=> + NULL + } + ["country"]=> + string(7) "Ukraine" + ["referred_by_userid"]=> + NULL + } + [1]=> + array(4) { + ["userid"]=> + int(107) + ["name"]=> + array(2) { + [0]=> + string(5) "Robin" + [1]=> + string(5) "Chris" + } + ["country"]=> + string(7) "Germany" + ["referred_by_userid"]=> + int(104) + } +} diff --git a/ext/pdo/tests/fetch-modes/named/group.phpt b/ext/pdo/tests/fetch-modes/named/group.phpt new file mode 100644 index 0000000000000..d7c032b04ecc2 --- /dev/null +++ b/ext/pdo/tests/fetch-modes/named/group.phpt @@ -0,0 +1,201 @@ +--TEST-- +PDO fetch mode NAMED with GROUP +--EXTENSIONS-- +pdo +--SKIPIF-- + +--FILE-- +query($query . " WHERE users.userid IN (104, 107)"); +while (false !== ($result = $stmt->fetch($fetchMode))) { + var_dump($result); +} + +print "\nfetchAll:\n"; +$stmt = $db->query($query); +$result = $stmt->fetchAll($fetchMode); +var_dump($result); + +--EXPECT-- +fetch: +array(4) { + ["country"]=> + array(2) { + [0]=> + string(7) "Ukraine" + [1]=> + string(7) "Ukraine" + } + ["userid"]=> + int(104) + ["name"]=> + array(2) { + [0]=> + string(5) "Chris" + [1]=> + NULL + } + ["referred_by_userid"]=> + NULL +} +array(4) { + ["country"]=> + array(2) { + [0]=> + string(7) "Germany" + [1]=> + string(7) "Germany" + } + ["userid"]=> + int(107) + ["name"]=> + array(2) { + [0]=> + string(5) "Robin" + [1]=> + string(5) "Chris" + } + ["referred_by_userid"]=> + int(104) +} + +fetchAll: +array(4) { + ["Ukraine"]=> + array(2) { + [0]=> + array(4) { + ["userid"]=> + int(104) + ["name"]=> + array(2) { + [0]=> + string(5) "Chris" + [1]=> + NULL + } + ["country"]=> + string(7) "Ukraine" + ["referred_by_userid"]=> + NULL + } + [1]=> + array(4) { + ["userid"]=> + int(108) + ["name"]=> + array(2) { + [0]=> + string(4) "Sean" + [1]=> + NULL + } + ["country"]=> + string(7) "Ukraine" + ["referred_by_userid"]=> + NULL + } + } + ["England"]=> + array(1) { + [0]=> + array(4) { + ["userid"]=> + int(105) + ["name"]=> + array(2) { + [0]=> + string(5) "Jamie" + [1]=> + NULL + } + ["country"]=> + string(7) "England" + ["referred_by_userid"]=> + NULL + } + } + ["Germany"]=> + array(3) { + [0]=> + array(4) { + ["userid"]=> + int(107) + ["name"]=> + array(2) { + [0]=> + string(5) "Robin" + [1]=> + string(5) "Chris" + } + ["country"]=> + string(7) "Germany" + ["referred_by_userid"]=> + int(104) + } + [1]=> + array(4) { + ["userid"]=> + int(109) + ["name"]=> + array(2) { + [0]=> + string(4) "Toni" + [1]=> + NULL + } + ["country"]=> + string(7) "Germany" + ["referred_by_userid"]=> + NULL + } + [2]=> + array(4) { + ["userid"]=> + int(110) + ["name"]=> + array(2) { + [0]=> + string(4) "Toni" + [1]=> + NULL + } + ["country"]=> + string(7) "Germany" + ["referred_by_userid"]=> + NULL + } + } + ["France"]=> + array(1) { + [0]=> + array(4) { + ["userid"]=> + int(111) + ["name"]=> + array(2) { + [0]=> + string(4) "Sean" + [1]=> + NULL + } + ["country"]=> + string(6) "France" + ["referred_by_userid"]=> + NULL + } + } +} diff --git a/ext/pdo/tests/fetch-modes/named/unique.phpt b/ext/pdo/tests/fetch-modes/named/unique.phpt new file mode 100644 index 0000000000000..221d5d8fde07d --- /dev/null +++ b/ext/pdo/tests/fetch-modes/named/unique.phpt @@ -0,0 +1,96 @@ +--TEST-- +PDO fetch mode NAMED with UNIQUE +--EXTENSIONS-- +pdo +--SKIPIF-- + +--FILE-- +query($query); +while (false !== ($result = $stmt->fetch($fetchMode))) { + var_dump($result); +} + +print "\nfetchAll:\n"; +$stmt = $db->query($query); +$result = $stmt->fetchAll($fetchMode); +var_dump($result); + +--EXPECT-- +fetch: +array(4) { + ["userid"]=> + int(104) + ["name"]=> + array(2) { + [0]=> + string(5) "Chris" + [1]=> + NULL + } + ["country"]=> + string(7) "Ukraine" + ["referred_by_userid"]=> + NULL +} +array(4) { + ["userid"]=> + int(107) + ["name"]=> + array(2) { + [0]=> + string(5) "Robin" + [1]=> + string(5) "Chris" + } + ["country"]=> + string(7) "Germany" + ["referred_by_userid"]=> + int(104) +} + +fetchAll: +array(2) { + [104]=> + array(3) { + ["name"]=> + array(2) { + [0]=> + string(5) "Chris" + [1]=> + NULL + } + ["country"]=> + string(7) "Ukraine" + ["referred_by_userid"]=> + NULL + } + [107]=> + array(3) { + ["name"]=> + array(2) { + [0]=> + string(5) "Robin" + [1]=> + string(5) "Chris" + } + ["country"]=> + string(7) "Germany" + ["referred_by_userid"]=> + int(104) + } +} diff --git a/ext/pdo/tests/fetch-modes/setup.php b/ext/pdo/tests/fetch-modes/setup.php new file mode 100644 index 0000000000000..c832a3c1bbf84 --- /dev/null +++ b/ext/pdo/tests/fetch-modes/setup.php @@ -0,0 +1,51 @@ +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + +# Note: code below is written to be compatible in PHP 5.1+ (and sqlite of that era) (when PDO was introduced) +# This allows it to easily be used with 3v4l (et al) for historical behavior checks + +# This data set specifically includes the following features: +# * referred_by_userid for JOINs and duplicated column name +# * duplicated record (ignoring userid) +# * multiple entries using the same country (for FETCH_GROUP and FETCH_UNIQUE) +# * 2 countries which have the same name (but aren't otherwise duplicates) (for FETCH_GROUP and FETCH_UNIQUE) +# * names and countries between 4 and 7 chars for nice TSV alignment + +$db->exec( + "CREATE TABLE users ( + userid INT PRIMARY KEY, + name TEXT, + country TEXT, + referred_by_userid INT + )" +); + +$records = array( + array(104, 'Chris', 'Ukraine', NULL), + array(105, 'Jamie', 'England', NULL), + array(107, 'Robin', 'Germany', 104), + array(108, 'Sean', 'Ukraine', NULL), + array(109, 'Toni', 'Germany', NULL), + array(110, 'Toni', 'Germany', NULL), + array(111, 'Sean', 'France', NULL) +); +$insertSql = "INSERT INTO users + (userid, name, country, referred_by_userid) + VALUES (:userid, :name, :country, :refid)"; +$insert = $db->prepare($insertSql); + +foreach ($records as $record) { + $insert->execute(array( + 'userid' => $record[0], + 'name' => $record[1], + 'country' => $record[2], + 'refid' => $record[3] + )); +} From 743ea094b2c9e8a1efd91ab5f68a9641e6057d5f Mon Sep 17 00:00:00 2001 From: AllenJB Date: Sat, 18 Oct 2025 10:52:13 +0100 Subject: [PATCH 02/14] PDO: Tests for fetch modes uncovered behavior (fixing test setup) --- ...fetch-methods.phpt => pdo_fetch_named.phpt} | 18 ++++++++++++------ .../group.phpt => pdo_fetch_named_group.phpt} | 18 ++++++++++++------ ...unique.phpt => pdo_fetch_named_unique.phpt} | 18 ++++++++++++------ .../setup.php => pdo_fetch_setup.php} | 16 ++++++++-------- 4 files changed, 44 insertions(+), 26 deletions(-) rename ext/pdo/tests/{fetch-modes/named/fetch-methods.phpt => pdo_fetch_named.phpt} (70%) rename ext/pdo/tests/{fetch-modes/named/group.phpt => pdo_fetch_named_group.phpt} (81%) rename ext/pdo/tests/{fetch-modes/named/unique.phpt => pdo_fetch_named_unique.phpt} (68%) rename ext/pdo/tests/{fetch-modes/setup.php => pdo_fetch_setup.php} (85%) diff --git a/ext/pdo/tests/fetch-modes/named/fetch-methods.phpt b/ext/pdo/tests/pdo_fetch_named.phpt similarity index 70% rename from ext/pdo/tests/fetch-modes/named/fetch-methods.phpt rename to ext/pdo/tests/pdo_fetch_named.phpt index f52af9763e31d..87b92d996f7df 100644 --- a/ext/pdo/tests/fetch-modes/named/fetch-methods.phpt +++ b/ext/pdo/tests/pdo_fetch_named.phpt @@ -1,5 +1,5 @@ --TEST-- -PDO fetch mode NAMED +PDO Common: PDO::FETCH_NAMED --EXTENSIONS-- pdo --SKIPIF-- @@ -11,12 +11,18 @@ PDOTest::skip(); ?> --FILE-- --FILE-- query($query . " WHERE users.userid IN (104, 107)"); +$stmt = $db->query($query . " WHERE {$table}.userid IN (104, 107)"); while (false !== ($result = $stmt->fetch($fetchMode))) { var_dump($result); } diff --git a/ext/pdo/tests/fetch-modes/named/unique.phpt b/ext/pdo/tests/pdo_fetch_named_unique.phpt similarity index 68% rename from ext/pdo/tests/fetch-modes/named/unique.phpt rename to ext/pdo/tests/pdo_fetch_named_unique.phpt index 221d5d8fde07d..ac365d129fefa 100644 --- a/ext/pdo/tests/fetch-modes/named/unique.phpt +++ b/ext/pdo/tests/pdo_fetch_named_unique.phpt @@ -1,5 +1,5 @@ --TEST-- -PDO fetch mode NAMED with UNIQUE +PDO Common: PDO::FETCH_NAMED with PDO::FETCH_UNIQUE --EXTENSIONS-- pdo --SKIPIF-- @@ -11,12 +11,18 @@ PDOTest::skip(); ?> --FILE-- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); # Note: code below is written to be compatible in PHP 5.1+ (and sqlite of that era) (when PDO was introduced) # This allows it to easily be used with 3v4l (et al) for historical behavior checks @@ -18,8 +13,13 @@ # * 2 countries which have the same name (but aren't otherwise duplicates) (for FETCH_GROUP and FETCH_UNIQUE) # * names and countries between 4 and 7 chars for nice TSV alignment +if (! isset($table)) { + die("Must set \$table before including pdo_fetch_setup.php"); +} +PDOTest::dropTableIfExists($db, $table); + $db->exec( - "CREATE TABLE users ( + "CREATE TABLE {$table} ( userid INT PRIMARY KEY, name TEXT, country TEXT, @@ -36,7 +36,7 @@ array(110, 'Toni', 'Germany', NULL), array(111, 'Sean', 'France', NULL) ); -$insertSql = "INSERT INTO users +$insertSql = "INSERT INTO {$table} (userid, name, country, referred_by_userid) VALUES (:userid, :name, :country, :refid)"; $insert = $db->prepare($insertSql); From ebd93ab440a8120e290c7a37372ff17a3e3e2fbf Mon Sep 17 00:00:00 2001 From: AllenJB Date: Sat, 18 Oct 2025 11:20:17 +0100 Subject: [PATCH 03/14] PDO: Tests for fetch modes uncovered behavior (fixing test setup) --- ext/pdo/tests/pdo_fetch_named.phpt | 2 -- ext/pdo/tests/pdo_fetch_named_group.phpt | 2 -- ext/pdo/tests/pdo_fetch_named_unique.phpt | 2 -- ext/pdo/tests/pdo_fetch_setup.php | 4 +++- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/ext/pdo/tests/pdo_fetch_named.phpt b/ext/pdo/tests/pdo_fetch_named.phpt index 87b92d996f7df..33f33b3f0503d 100644 --- a/ext/pdo/tests/pdo_fetch_named.phpt +++ b/ext/pdo/tests/pdo_fetch_named.phpt @@ -14,8 +14,6 @@ PDOTest::skip(); if (getenv('REDIR_TEST_DIR') === false) { putenv('REDIR_TEST_DIR=' . __DIR__ . '/../../pdo/tests/'); } -require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc'; -$db = PDOTest::factory(); $table = str_replace('.php', '', basename(__FILE__)); require_once getenv('REDIR_TEST_DIR') . "/pdo_fetch_setup.php"; diff --git a/ext/pdo/tests/pdo_fetch_named_group.phpt b/ext/pdo/tests/pdo_fetch_named_group.phpt index 26e58c920ee7e..226068d2f4968 100644 --- a/ext/pdo/tests/pdo_fetch_named_group.phpt +++ b/ext/pdo/tests/pdo_fetch_named_group.phpt @@ -14,8 +14,6 @@ PDOTest::skip(); if (getenv('REDIR_TEST_DIR') === false) { putenv('REDIR_TEST_DIR=' . __DIR__ . '/../../pdo/tests/'); } -require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc'; -$db = PDOTest::factory(); $table = str_replace('.php', '', basename(__FILE__)); require_once getenv('REDIR_TEST_DIR') . "/pdo_fetch_setup.php"; diff --git a/ext/pdo/tests/pdo_fetch_named_unique.phpt b/ext/pdo/tests/pdo_fetch_named_unique.phpt index ac365d129fefa..6babcb16f21f6 100644 --- a/ext/pdo/tests/pdo_fetch_named_unique.phpt +++ b/ext/pdo/tests/pdo_fetch_named_unique.phpt @@ -14,8 +14,6 @@ PDOTest::skip(); if (getenv('REDIR_TEST_DIR') === false) { putenv('REDIR_TEST_DIR=' . __DIR__ . '/../../pdo/tests/'); } -require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc'; -$db = PDOTest::factory(); $table = str_replace('.php', '', basename(__FILE__)); require_once getenv('REDIR_TEST_DIR') . "/pdo_fetch_setup.php"; diff --git a/ext/pdo/tests/pdo_fetch_setup.php b/ext/pdo/tests/pdo_fetch_setup.php index 75186464e4de1..e441a5dcd1f24 100644 --- a/ext/pdo/tests/pdo_fetch_setup.php +++ b/ext/pdo/tests/pdo_fetch_setup.php @@ -1,6 +1,8 @@ setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc'; +$db = PDOTest::factory(); +$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); # Note: code below is written to be compatible in PHP 5.1+ (and sqlite of that era) (when PDO was introduced) From c95f73b6dd6fd920443a711b24e5a5fd80b047f3 Mon Sep 17 00:00:00 2001 From: AllenJB Date: Sat, 18 Oct 2025 14:14:19 +0100 Subject: [PATCH 04/14] PDO: Tests for fetch modes uncovered behavior (KEY_PAIR and DEFAULT) --- ext/pdo/tests/pdo_034.phpt | 69 ---------------- ext/pdo/tests/pdo_fetch_default.phpt | 81 ++++++++++++++++++ ext/pdo/tests/pdo_fetch_keypair.phpt | 119 +++++++++++++++++++++++++++ 3 files changed, 200 insertions(+), 69 deletions(-) delete mode 100644 ext/pdo/tests/pdo_034.phpt create mode 100644 ext/pdo/tests/pdo_fetch_default.phpt create mode 100644 ext/pdo/tests/pdo_fetch_keypair.phpt diff --git a/ext/pdo/tests/pdo_034.phpt b/ext/pdo/tests/pdo_034.phpt deleted file mode 100644 index 89b691e6cfc3f..0000000000000 --- a/ext/pdo/tests/pdo_034.phpt +++ /dev/null @@ -1,69 +0,0 @@ ---TEST-- -PDO Common: PDO::FETCH_KEY_PAIR fetch mode test ---EXTENSIONS-- -pdo ---SKIPIF-- - ---FILE-- -exec("CREATE TABLE test034 (a varchar(100), b varchar(100), c varchar(100))"); - -for ($i = 0; $i < 5; $i++) { - $db->exec("INSERT INTO test034 (a,b,c) VALUES('test".$i."','".$i."','".$i."')"); -} - -var_dump($db->query("SELECT a,b FROM test034")->fetch(PDO::FETCH_KEY_PAIR)); -var_dump($db->query("SELECT a,b FROM test034")->fetchAll(PDO::FETCH_KEY_PAIR)); -var_dump($db->query("SELECT * FROM test034")->fetch(PDO::FETCH_KEY_PAIR)); -var_dump($db->query("SELECT a,a FROM test034")->fetchAll(PDO::FETCH_KEY_PAIR)); - -?> ---CLEAN-- - ---EXPECTF-- -array(1) { - ["test0"]=> - string(1) "0" -} -array(5) { - ["test0"]=> - string(1) "0" - ["test1"]=> - string(1) "1" - ["test2"]=> - string(1) "2" - ["test3"]=> - string(1) "3" - ["test4"]=> - string(1) "4" -} - -Warning: PDOStatement::fetch(): SQLSTATE[HY000]: General error: PDO::FETCH_KEY_PAIR fetch mode requires the result set to contain exactly 2 columns. in %spdo_034.php on line %d - -Warning: PDOStatement::fetch(): SQLSTATE[HY000]: General error%spdo_034.php on line %d -bool(false) -array(5) { - ["test0"]=> - string(5) "test0" - ["test1"]=> - string(5) "test1" - ["test2"]=> - string(5) "test2" - ["test3"]=> - string(5) "test3" - ["test4"]=> - string(5) "test4" -} diff --git a/ext/pdo/tests/pdo_fetch_default.phpt b/ext/pdo/tests/pdo_fetch_default.phpt new file mode 100644 index 0000000000000..12b52e64b35e4 --- /dev/null +++ b/ext/pdo/tests/pdo_fetch_default.phpt @@ -0,0 +1,81 @@ +--TEST-- +PDO Common: PDO::FETCH_DEFAULT +--EXTENSIONS-- +pdo +--SKIPIF-- + +--FILE-- +query("SELECT 'v1' AS c1, 'v2' AS c2"); +print_r($stmt->fetch()); + +/* +FIXME https://github.com/php/php-src/issues/20214 +print "\nPDOStatement::setFetchMode:\n"; +$stmt = $db->query("SELECT 'v1' AS c1, 'v2' AS c2"); +$stmt->setFetchMode(\PDO::FETCH_DEFAULT); +print_r($stmt->fetch()); +*/ + +print "\nPDOStatement::fetch:\n"; +$stmt = $db->query("SELECT 'v1' AS c1, 'v2' AS c2"); +print_r($stmt->fetch(\PDO::FETCH_DEFAULT)); + +print "\nPDOStatement::fetchAll:\n"; +$stmt = $db->query("SELECT 'v1' AS c1, 'v2' AS c2"); +print_r($stmt->fetchAll(\PDO::FETCH_DEFAULT)); + +print "\nPDO::setAttribute:\n"; +try { + $db->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_DEFAULT); +} catch (ValueError $e) { + print "Could not set fetch mode using PDO::setAttribute: ". $e->getMessage() ."\n"; +} + +if ($db->getAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE) !== $expectedFetchMode) { + print __LINE__ ." Fetch mode changed\n"; +} + +print "Done!\n"; + +--EXPECT-- +Original: +stdClass Object +( + [c1] => v1 + [c2] => v2 +) + +PDOStatement::fetch: +stdClass Object +( + [c1] => v1 + [c2] => v2 +) + +PDOStatement::fetchAll: +Array +( + [0] => stdClass Object + ( + [c1] => v1 + [c2] => v2 + ) + +) + +PDO::setAttribute: +Could not set fetch mode using PDO::setAttribute: Fetch mode must be a bitmask of PDO::FETCH_* constants +Done! diff --git a/ext/pdo/tests/pdo_fetch_keypair.phpt b/ext/pdo/tests/pdo_fetch_keypair.phpt new file mode 100644 index 0000000000000..5b54cabccdda7 --- /dev/null +++ b/ext/pdo/tests/pdo_fetch_keypair.phpt @@ -0,0 +1,119 @@ +--TEST-- +PDO Common: PDO::FETCH_KEY_PAIR +--EXTENSIONS-- +pdo +--SKIPIF-- + +--FILE-- +query($query); +while (false !== ($result = $stmt->fetch($fetchMode))) { + print_r($result); +} + +print "\nfetchAll:\n"; +$stmt = $db->query($query); +$result = $stmt->fetchAll($fetchMode); +print_r($result); + +print "\nToo many columns:\n"; +$query = "SELECT {$table}.country, {$table}.name, {$table}.userid + FROM {$table}"; +$stmt = $db->query($query); +$result = $stmt->fetchAll($fetchMode); +var_dump($result); + +print "\nToo few columns:\n"; +$query = "SELECT {$table}.country + FROM {$table}"; +$stmt = $db->query($query); +$result = $stmt->fetchAll($fetchMode); +var_dump($result); + +print "\nSame column:\n"; +$query = "SELECT {$table}.country, {$table}.country + FROM {$table}"; +$stmt = $db->query($query); +$result = $stmt->fetchAll($fetchMode); +print_r($result); + +--EXPECTF-- +fetch: +Array +( + [Ukraine] => Chris +) +Array +( + [England] => Jamie +) +Array +( + [Germany] => Robin +) +Array +( + [Ukraine] => Sean +) +Array +( + [Germany] => Toni +) +Array +( + [Germany] => Toni +) +Array +( + [France] => Sean +) + +fetchAll: +Array +( + [Ukraine] => Sean + [England] => Jamie + [Germany] => Toni + [France] => Sean +) + +Too many columns: + +Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: PDO::FETCH_KEY_PAIR fetch mode requires the result set to contain exactly 2 columns. in %s on line %d + +Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d +array(0) { +} + +Too few columns: + +Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: PDO::FETCH_KEY_PAIR fetch mode requires the result set to contain exactly 2 columns. in %s on line %s + +Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error in %s on line %d +array(0) { +} + +Same column: +Array +( + [Ukraine] => Ukraine + [England] => England + [Germany] => Germany + [France] => France +) From 05e3f9f8f2ff4497e75d9ccb368aef683c0f82c5 Mon Sep 17 00:00:00 2001 From: AllenJB Date: Sat, 18 Oct 2025 14:19:33 +0100 Subject: [PATCH 05/14] PDO: Tests for fetch modes uncovered behavior (KEY_PAIR and DEFAULT) --- ext/pdo/tests/pdo_fetch_default.phpt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ext/pdo/tests/pdo_fetch_default.phpt b/ext/pdo/tests/pdo_fetch_default.phpt index 12b52e64b35e4..ba69a16907f4c 100644 --- a/ext/pdo/tests/pdo_fetch_default.phpt +++ b/ext/pdo/tests/pdo_fetch_default.phpt @@ -17,6 +17,9 @@ if (getenv('REDIR_TEST_DIR') === false) { require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc'; $db = PDOTest::factory(); +$expectedFetchMode = \PDO::FETCH_OBJ; +$db->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, $expectedFetchMode); + print "Original:\n"; $stmt = $db->query("SELECT 'v1' AS c1, 'v2' AS c2"); print_r($stmt->fetch()); @@ -77,5 +80,5 @@ Array ) PDO::setAttribute: -Could not set fetch mode using PDO::setAttribute: Fetch mode must be a bitmask of PDO::FETCH_* constants +Could not set fetch mode using PDO::setAttribute: PDO::setAttribute(): Argument #2 ($value) Fetch mode must be a bitmask of PDO::FETCH_* constants Done! From 1622a8244176d875a64a9762daf94db4148d0435 Mon Sep 17 00:00:00 2001 From: AllenJB Date: Sat, 18 Oct 2025 14:51:48 +0100 Subject: [PATCH 06/14] PDO: Tests for fetch modes uncovered behavior (table schema fixes for DB compatibility) --- ext/pdo/tests/pdo_fetch_setup.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/pdo/tests/pdo_fetch_setup.php b/ext/pdo/tests/pdo_fetch_setup.php index e441a5dcd1f24..5aeb1c38b8062 100644 --- a/ext/pdo/tests/pdo_fetch_setup.php +++ b/ext/pdo/tests/pdo_fetch_setup.php @@ -23,9 +23,9 @@ $db->exec( "CREATE TABLE {$table} ( userid INT PRIMARY KEY, - name TEXT, - country TEXT, - referred_by_userid INT + name VARCHAR(20), + country VARCHAR(20), + referred_by_userid INT NULL )" ); From b599d12cf280c7ce962d5e1a9a6a55111de75c34 Mon Sep 17 00:00:00 2001 From: AllenJB Date: Sat, 18 Oct 2025 15:12:57 +0100 Subject: [PATCH 07/14] PDO: Tests for fetch modes uncovered behavior (table schema fixes for DB compatibility) --- ext/pdo/tests/pdo_fetch_setup.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ext/pdo/tests/pdo_fetch_setup.php b/ext/pdo/tests/pdo_fetch_setup.php index 5aeb1c38b8062..779b4eeac5bd5 100644 --- a/ext/pdo/tests/pdo_fetch_setup.php +++ b/ext/pdo/tests/pdo_fetch_setup.php @@ -20,12 +20,15 @@ } PDOTest::dropTableIfExists($db, $table); +# SQL Server requires this; Firebird error on it. +$nullable = (($db->getAttribute(PDO::ATTR_DRIVER_NAME) === 'dblib') ? 'NULL' : ''); + $db->exec( "CREATE TABLE {$table} ( userid INT PRIMARY KEY, name VARCHAR(20), country VARCHAR(20), - referred_by_userid INT NULL + referred_by_userid INT {$nullable} )" ); From 1341f51c7d266131859f95b4cfa6a8c69aa0dae0 Mon Sep 17 00:00:00 2001 From: AllenJB Date: Sat, 18 Oct 2025 15:22:58 +0100 Subject: [PATCH 08/14] PDO: Tests for fetch modes uncovered behavior (DB compatibility fix) --- ext/pdo/tests/pdo_fetch_default.phpt | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/ext/pdo/tests/pdo_fetch_default.phpt b/ext/pdo/tests/pdo_fetch_default.phpt index ba69a16907f4c..e197cf000c821 100644 --- a/ext/pdo/tests/pdo_fetch_default.phpt +++ b/ext/pdo/tests/pdo_fetch_default.phpt @@ -14,30 +14,31 @@ PDOTest::skip(); if (getenv('REDIR_TEST_DIR') === false) { putenv('REDIR_TEST_DIR=' . __DIR__ . '/../../pdo/tests/'); } -require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc'; -$db = PDOTest::factory(); +$table = str_replace('.php', '', basename(__FILE__)); +require_once getenv('REDIR_TEST_DIR') . "/pdo_fetch_setup.php"; +$query = "SELECT name, country FROM {$table} LIMIT 1"; $expectedFetchMode = \PDO::FETCH_OBJ; $db->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, $expectedFetchMode); print "Original:\n"; -$stmt = $db->query("SELECT 'v1' AS c1, 'v2' AS c2"); +$stmt = $db->query($query); print_r($stmt->fetch()); /* FIXME https://github.com/php/php-src/issues/20214 print "\nPDOStatement::setFetchMode:\n"; -$stmt = $db->query("SELECT 'v1' AS c1, 'v2' AS c2"); +$stmt = $db->query($query); $stmt->setFetchMode(\PDO::FETCH_DEFAULT); print_r($stmt->fetch()); */ print "\nPDOStatement::fetch:\n"; -$stmt = $db->query("SELECT 'v1' AS c1, 'v2' AS c2"); +$stmt = $db->query($query); print_r($stmt->fetch(\PDO::FETCH_DEFAULT)); print "\nPDOStatement::fetchAll:\n"; -$stmt = $db->query("SELECT 'v1' AS c1, 'v2' AS c2"); +$stmt = $db->query($query); print_r($stmt->fetchAll(\PDO::FETCH_DEFAULT)); print "\nPDO::setAttribute:\n"; @@ -57,15 +58,15 @@ print "Done!\n"; Original: stdClass Object ( - [c1] => v1 - [c2] => v2 + [name] => Chris + [country] => Ukraine ) PDOStatement::fetch: stdClass Object ( - [c1] => v1 - [c2] => v2 + [name] => Chris + [country] => Ukraine ) PDOStatement::fetchAll: @@ -73,8 +74,8 @@ Array ( [0] => stdClass Object ( - [c1] => v1 - [c2] => v2 + [name] => Chris + [country] => Ukraine ) ) From 326a25de0ae2a3be22f09e4f8f61594c9ed0b121 Mon Sep 17 00:00:00 2001 From: AllenJB Date: Sat, 18 Oct 2025 15:50:36 +0100 Subject: [PATCH 09/14] PDO: Tests for fetch modes uncovered behavior (DB compatibility fix) --- ext/pdo/tests/pdo_fetch_default.phpt | 2 +- ext/pdo/tests/pdo_fetch_setup.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/pdo/tests/pdo_fetch_default.phpt b/ext/pdo/tests/pdo_fetch_default.phpt index e197cf000c821..c7dcd9690ca39 100644 --- a/ext/pdo/tests/pdo_fetch_default.phpt +++ b/ext/pdo/tests/pdo_fetch_default.phpt @@ -17,7 +17,7 @@ if (getenv('REDIR_TEST_DIR') === false) { $table = str_replace('.php', '', basename(__FILE__)); require_once getenv('REDIR_TEST_DIR') . "/pdo_fetch_setup.php"; -$query = "SELECT name, country FROM {$table} LIMIT 1"; +$query = "SELECT name, country FROM {$table} WHERE userid = 104"; $expectedFetchMode = \PDO::FETCH_OBJ; $db->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, $expectedFetchMode); diff --git a/ext/pdo/tests/pdo_fetch_setup.php b/ext/pdo/tests/pdo_fetch_setup.php index 779b4eeac5bd5..ea93c46864ace 100644 --- a/ext/pdo/tests/pdo_fetch_setup.php +++ b/ext/pdo/tests/pdo_fetch_setup.php @@ -2,7 +2,6 @@ error_reporting(E_ALL); require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc'; $db = PDOTest::factory(); -$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); # Note: code below is written to be compatible in PHP 5.1+ (and sqlite of that era) (when PDO was introduced) @@ -18,7 +17,9 @@ if (! isset($table)) { die("Must set \$table before including pdo_fetch_setup.php"); } +$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); PDOTest::dropTableIfExists($db, $table); +$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); # SQL Server requires this; Firebird error on it. $nullable = (($db->getAttribute(PDO::ATTR_DRIVER_NAME) === 'dblib') ? 'NULL' : ''); From dba1ade90ffd779f67ba29ec155d8bbd8e18e034 Mon Sep 17 00:00:00 2001 From: AllenJB Date: Sat, 18 Oct 2025 16:07:34 +0100 Subject: [PATCH 10/14] PDO: Tests for fetch modes uncovered behavior (DB compatibility fix) --- ext/pdo/tests/pdo_fetch_setup.php | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/pdo/tests/pdo_fetch_setup.php b/ext/pdo/tests/pdo_fetch_setup.php index ea93c46864ace..782db11f0ceca 100644 --- a/ext/pdo/tests/pdo_fetch_setup.php +++ b/ext/pdo/tests/pdo_fetch_setup.php @@ -1,5 +1,4 @@ setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); From 84694c0f9d7dcf74734ac02b5ba59f83ed65132c Mon Sep 17 00:00:00 2001 From: AllenJB Date: Sat, 18 Oct 2025 16:15:44 +0100 Subject: [PATCH 11/14] PDO: Tests for fetch modes uncovered behavior (DB compatibility fix) --- ext/pdo/tests/pdo_fetch_default.phpt | 7 +++++++ ext/pdo/tests/pdo_fetch_keypair.phpt | 7 +++++++ ext/pdo/tests/pdo_fetch_named.phpt | 7 +++++++ ext/pdo/tests/pdo_fetch_named_group.phpt | 7 +++++++ ext/pdo/tests/pdo_fetch_named_unique.phpt | 7 +++++++ ext/pdo/tests/pdo_fetch_setup.php | 4 +--- 6 files changed, 36 insertions(+), 3 deletions(-) diff --git a/ext/pdo/tests/pdo_fetch_default.phpt b/ext/pdo/tests/pdo_fetch_default.phpt index c7dcd9690ca39..f94dce3a130eb 100644 --- a/ext/pdo/tests/pdo_fetch_default.phpt +++ b/ext/pdo/tests/pdo_fetch_default.phpt @@ -9,6 +9,13 @@ if (false == $dir) die('skip no driver'); require_once $dir . 'pdo_test.inc'; PDOTest::skip(); ?> +--CLEAN-- + --FILE-- +--CLEAN-- + --FILE-- +--CLEAN-- + --FILE-- +--CLEAN-- + --FILE-- +--CLEAN-- + --FILE-- setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); +$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); # Note: code below is written to be compatible in PHP 5.1+ (and sqlite of that era) (when PDO was introduced) # This allows it to easily be used with 3v4l (et al) for historical behavior checks @@ -16,9 +17,6 @@ if (! isset($table)) { die("Must set \$table before including pdo_fetch_setup.php"); } -$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); -PDOTest::dropTableIfExists($db, $table); -$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); # SQL Server requires this; Firebird error on it. $nullable = (($db->getAttribute(PDO::ATTR_DRIVER_NAME) === 'dblib') ? 'NULL' : ''); From 90d0eae9e7f8fbf91702df98700b2541464bfb7e Mon Sep 17 00:00:00 2001 From: AllenJB Date: Sat, 18 Oct 2025 17:03:20 +0100 Subject: [PATCH 12/14] PDO: Tests for fetch modes uncovered behavior (DB compatibility fix - stringify) --- ext/pdo/tests/pdo_fetch_named.phpt | 12 ++++++------ ext/pdo/tests/pdo_fetch_named_group.phpt | 22 +++++++++++----------- ext/pdo/tests/pdo_fetch_named_unique.phpt | 8 ++++---- ext/pdo/tests/pdo_fetch_setup.php | 1 - 4 files changed, 21 insertions(+), 22 deletions(-) diff --git a/ext/pdo/tests/pdo_fetch_named.phpt b/ext/pdo/tests/pdo_fetch_named.phpt index ee1011cebda23..7d30d92ab042a 100644 --- a/ext/pdo/tests/pdo_fetch_named.phpt +++ b/ext/pdo/tests/pdo_fetch_named.phpt @@ -45,7 +45,7 @@ var_dump($result); fetch: array(4) { ["userid"]=> - int(104) + string(3) "104" ["name"]=> array(2) { [0]=> @@ -60,7 +60,7 @@ array(4) { } array(4) { ["userid"]=> - int(107) + string(3) "107" ["name"]=> array(2) { [0]=> @@ -71,7 +71,7 @@ array(4) { ["country"]=> string(7) "Germany" ["referred_by_userid"]=> - int(104) + string(3) "104" } fetchAll: @@ -79,7 +79,7 @@ array(2) { [0]=> array(4) { ["userid"]=> - int(104) + string(3) "104" ["name"]=> array(2) { [0]=> @@ -95,7 +95,7 @@ array(2) { [1]=> array(4) { ["userid"]=> - int(107) + string(3) "107" ["name"]=> array(2) { [0]=> @@ -106,6 +106,6 @@ array(2) { ["country"]=> string(7) "Germany" ["referred_by_userid"]=> - int(104) + string(3) "104" } } diff --git a/ext/pdo/tests/pdo_fetch_named_group.phpt b/ext/pdo/tests/pdo_fetch_named_group.phpt index 1fc32d760c5d9..506e03866e7b6 100644 --- a/ext/pdo/tests/pdo_fetch_named_group.phpt +++ b/ext/pdo/tests/pdo_fetch_named_group.phpt @@ -51,7 +51,7 @@ array(4) { string(7) "Ukraine" } ["userid"]=> - int(104) + string(3) "104" ["name"]=> array(2) { [0]=> @@ -71,7 +71,7 @@ array(4) { string(7) "Germany" } ["userid"]=> - int(107) + string(3) "107" ["name"]=> array(2) { [0]=> @@ -80,7 +80,7 @@ array(4) { string(5) "Chris" } ["referred_by_userid"]=> - int(104) + string(3) "104" } fetchAll: @@ -90,7 +90,7 @@ array(4) { [0]=> array(4) { ["userid"]=> - int(104) + string(3) "104" ["name"]=> array(2) { [0]=> @@ -106,7 +106,7 @@ array(4) { [1]=> array(4) { ["userid"]=> - int(108) + string(3) "108" ["name"]=> array(2) { [0]=> @@ -125,7 +125,7 @@ array(4) { [0]=> array(4) { ["userid"]=> - int(105) + string(3) "105" ["name"]=> array(2) { [0]=> @@ -144,7 +144,7 @@ array(4) { [0]=> array(4) { ["userid"]=> - int(107) + string(3) "107" ["name"]=> array(2) { [0]=> @@ -155,12 +155,12 @@ array(4) { ["country"]=> string(7) "Germany" ["referred_by_userid"]=> - int(104) + string(3) "104" } [1]=> array(4) { ["userid"]=> - int(109) + string(3) "109" ["name"]=> array(2) { [0]=> @@ -176,7 +176,7 @@ array(4) { [2]=> array(4) { ["userid"]=> - int(110) + string(3) "110" ["name"]=> array(2) { [0]=> @@ -195,7 +195,7 @@ array(4) { [0]=> array(4) { ["userid"]=> - int(111) + string(3) "111" ["name"]=> array(2) { [0]=> diff --git a/ext/pdo/tests/pdo_fetch_named_unique.phpt b/ext/pdo/tests/pdo_fetch_named_unique.phpt index 123bb4cd214c7..dca90669a8e50 100644 --- a/ext/pdo/tests/pdo_fetch_named_unique.phpt +++ b/ext/pdo/tests/pdo_fetch_named_unique.phpt @@ -45,7 +45,7 @@ var_dump($result); fetch: array(4) { ["userid"]=> - int(104) + string(3) "104" ["name"]=> array(2) { [0]=> @@ -60,7 +60,7 @@ array(4) { } array(4) { ["userid"]=> - int(107) + string(3) "107" ["name"]=> array(2) { [0]=> @@ -71,7 +71,7 @@ array(4) { ["country"]=> string(7) "Germany" ["referred_by_userid"]=> - int(104) + string(3) "104" } fetchAll: @@ -102,6 +102,6 @@ array(2) { ["country"]=> string(7) "Germany" ["referred_by_userid"]=> - int(104) + string(3) "104" } } diff --git a/ext/pdo/tests/pdo_fetch_setup.php b/ext/pdo/tests/pdo_fetch_setup.php index 0370cbdee714a..f666de7ad959b 100644 --- a/ext/pdo/tests/pdo_fetch_setup.php +++ b/ext/pdo/tests/pdo_fetch_setup.php @@ -1,7 +1,6 @@ setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); # Note: code below is written to be compatible in PHP 5.1+ (and sqlite of that era) (when PDO was introduced) From d275e7be50da4cf6a97940bc716c2f235f62d154 Mon Sep 17 00:00:00 2001 From: AllenJB Date: Sat, 18 Oct 2025 17:36:33 +0100 Subject: [PATCH 13/14] PDO: Tests for fetch modes uncovered behavior (DB compatibility fix - ODBC unfetched results) --- ext/pdo/tests/pdo_fetch_keypair.phpt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/pdo/tests/pdo_fetch_keypair.phpt b/ext/pdo/tests/pdo_fetch_keypair.phpt index e16a9bbd54b5f..56e40c85d5441 100644 --- a/ext/pdo/tests/pdo_fetch_keypair.phpt +++ b/ext/pdo/tests/pdo_fetch_keypair.phpt @@ -45,6 +45,8 @@ $query = "SELECT {$table}.country, {$table}.name, {$table}.userid $stmt = $db->query($query); $result = $stmt->fetchAll($fetchMode); var_dump($result); +# ODBC does not like leaving the result unfetched +$ignore = $stmt->fetchAll(\PDO::FETCH_OBJ); print "\nToo few columns:\n"; $query = "SELECT {$table}.country @@ -52,6 +54,8 @@ $query = "SELECT {$table}.country $stmt = $db->query($query); $result = $stmt->fetchAll($fetchMode); var_dump($result); +# ODBC does not like leaving the result unfetched +$ignore = $stmt->fetchAll(\PDO::FETCH_OBJ); print "\nSame column:\n"; $query = "SELECT {$table}.country, {$table}.country From a70432fa3f6955a8ece70be3cb4e3473e7b20186 Mon Sep 17 00:00:00 2001 From: AllenJB Date: Sat, 18 Oct 2025 17:37:06 +0100 Subject: [PATCH 14/14] PDO: Tests for fetch modes uncovered behavior (simplify table name handling (cleanup)) --- ext/pdo/tests/pdo_fetch_default.phpt | 5 ++--- ext/pdo/tests/pdo_fetch_keypair.phpt | 5 ++--- ext/pdo/tests/pdo_fetch_named.phpt | 5 ++--- ext/pdo/tests/pdo_fetch_named_group.phpt | 5 ++--- ext/pdo/tests/pdo_fetch_named_unique.phpt | 5 ++--- 5 files changed, 10 insertions(+), 15 deletions(-) diff --git a/ext/pdo/tests/pdo_fetch_default.phpt b/ext/pdo/tests/pdo_fetch_default.phpt index f94dce3a130eb..4dd37c9b66186 100644 --- a/ext/pdo/tests/pdo_fetch_default.phpt +++ b/ext/pdo/tests/pdo_fetch_default.phpt @@ -13,15 +13,14 @@ PDOTest::skip(); --FILE-- --FILE-- --FILE-- --FILE-- --FILE--