Commit f0236f11ba7 for php.net

commit f0236f11ba77cf372f1709493988bf37f10c09cd
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Sat Jun 20 22:00:28 2026 -0400

    pdo_pgsql: preserve the pending exception when a COPY row fails to convert

    pgsqlCopyFromArray() feeds each row through try_convert_to_string(). A
    non-stringable row throws a TypeError, but both the array and iterator
    branches then called pdo_pgsql_error() and recorded a fabricated
    PGRES_FATAL_ERROR, leaving PDO::errorInfo() reporting "HY000" for what is
    a client-side type error. Return through the pending exception instead of
    overwriting the driver error state.

    Closes GH-22384

diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c
index db640f2a1b5..54b2e25f72f 100644
--- a/ext/pdo_pgsql/pgsql_driver.c
+++ b/ext/pdo_pgsql/pgsql_driver.c
@@ -720,6 +720,9 @@ void pgsqlCopyFromArray_internal(INTERNAL_FUNCTION_PARAMETERS)
 		if (Z_TYPE_P(pg_rows) == IS_ARRAY) {
 			ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pg_rows), tmp) {
 				if (!_pdo_pgsql_send_copy_data(H, tmp)) {
+					if (EG(exception)) {
+						RETURN_THROWS();
+					}
 					pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL);
 					PDO_HANDLE_DBH_ERR();
 					RETURN_FALSE;
@@ -742,6 +745,9 @@ void pgsqlCopyFromArray_internal(INTERNAL_FUNCTION_PARAMETERS)
 				tmp = iter->funcs->get_current_data(iter);
 				if (!_pdo_pgsql_send_copy_data(H, tmp)) {
 					zend_iterator_dtor(iter);
+					if (EG(exception)) {
+						RETURN_THROWS();
+					}
 					pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL);
 					PDO_HANDLE_DBH_ERR();
 					RETURN_FALSE;
diff --git a/ext/pdo_pgsql/tests/copy_from_array_non_stringable.phpt b/ext/pdo_pgsql/tests/copy_from_array_non_stringable.phpt
new file mode 100644
index 00000000000..65edb30d667
--- /dev/null
+++ b/ext/pdo_pgsql/tests/copy_from_array_non_stringable.phpt
@@ -0,0 +1,49 @@
+--TEST--
+PDO PgSQL pgsqlCopyFromArray()/copyFromArray() with a non-stringable row throws without polluting errorInfo
+--EXTENSIONS--
+pdo_pgsql
+--SKIPIF--
+<?php
+require __DIR__ . '/config.inc';
+require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
+$db = PDOTest::test_factory(__DIR__ . '/common.phpt');
+$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
+
+$db->exec('CREATE TABLE test_copy_non_stringable (v text)');
+
+try {
+    $db->pgsqlCopyFromArray('test_copy_non_stringable', [new stdClass()]);
+} catch (\Error $e) {
+    echo $e->getMessage(), PHP_EOL;
+}
+
+var_dump($db->errorInfo()[0]);
+
+$pgsql = PDOTest::test_factory(__DIR__ . '/common.phpt', Pdo\Pgsql::class, true);
+$pgsql->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT);
+
+try {
+    $pgsql->copyFromArray('test_copy_non_stringable', [new stdClass()]);
+} catch (\Error $e) {
+    echo $e->getMessage(), PHP_EOL;
+}
+
+var_dump($pgsql->errorInfo()[0]);
+?>
+--CLEAN--
+<?php
+require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
+$db = PDOTest::test_factory(__DIR__ . '/common.phpt');
+$db->query('DROP TABLE IF EXISTS test_copy_non_stringable CASCADE');
+?>
+--EXPECTF--
+Deprecated: Method PDO::pgsqlCopyFromArray() is deprecated since 8.5, use Pdo\Pgsql::copyFromArray() instead in %s on line %d
+Object of class stdClass could not be converted to string
+string(5) "00000"
+Object of class stdClass could not be converted to string
+string(5) "00000"