Commit c0018beb118 for php

commit c0018beb1185031bcf40cffa2ffd00d66873f517
Author: Matthias Dötsch <matze@mdoetsch.de>
Date:   Wed Oct 14 20:05:27 2026 +0200

    ext/pdo_pgsql: Convert integer columns without strtoll()

    PostgreSQL sends int2/int4/int8 values in text format in canonical
    decimal form: an optional minus sign followed by digits without leading
    zeros. That is exactly what ZEND_HANDLE_NUMERIC_STR() accepts for
    numeric array keys, including the range check. Use it instead of
    calling strtoll() through ZEND_ATOL() for every value, and keep
    ZEND_ATOL() as fallback for anything else.

    fetchAll(PDO::FETCH_ASSOC) of 1M rows with 4 integer columns:
    -11% instructions, -9% cycles.

    Close GH-24385

diff --git a/NEWS b/NEWS
index 67896bb73a8..5155d8ede34 100644
--- a/NEWS
+++ b/NEWS
@@ -91,6 +91,7 @@ PHP                                                                        NEWS
 - PDO_PGSQL:
   . Improved performance of the SQL parser that handles placeholders.
     (brainexe)
+  . Improved performance of fetching integer columns. (brainexe)

 - PGSQL:
   . Fixed pg_lo_write() rejecting data containing null bytes. (Ilia Alshanetsky)
diff --git a/UPGRADING b/UPGRADING
index 60b7020cb1e..a232e35aa00 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -176,3 +176,4 @@ PHP 8.7 UPGRADE NOTES
 - PDO_PGSQL:
   . Improved performance of the SQL parser that handles placeholders in
     PDO::prepare() and, with emulated prepares, PDOStatement::execute().
+  . Improved performance of fetching integer columns.
diff --git a/ext/pdo_pgsql/pgsql_statement.c b/ext/pdo_pgsql/pgsql_statement.c
index de7d03bc726..15d9916a5c2 100644
--- a/ext/pdo_pgsql/pgsql_statement.c
+++ b/ext/pdo_pgsql/pgsql_statement.c
@@ -758,8 +758,17 @@ static int pgsql_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pd
 #if SIZEOF_ZEND_LONG >= 8
 			case INT8OID:
 #endif
-				ZVAL_LONG(result, ZEND_ATOL(ptr));
+			{
+				/* PostgreSQL sends integers in canonical decimal form, which the numeric array key
+				 * parser handles without the overhead of strtoll() */
+				zend_ulong lval;
+				if (EXPECTED(ZEND_HANDLE_NUMERIC_STR(ptr, len, lval))) {
+					ZVAL_LONG(result, (zend_long) lval);
+				} else {
+					ZVAL_LONG(result, ZEND_ATOL(ptr));
+				}
 				break;
+			}
 			case FLOAT4OID:
 			case FLOAT8OID:
                 if (strncmp(ptr, "Infinity", len) == 0) {
diff --git a/ext/pdo_pgsql/tests/fetch_integers.phpt b/ext/pdo_pgsql/tests/fetch_integers.phpt
new file mode 100644
index 00000000000..54287dc200e
--- /dev/null
+++ b/ext/pdo_pgsql/tests/fetch_integers.phpt
@@ -0,0 +1,56 @@
+--TEST--
+PDO PgSQL: integer columns at their boundaries
+--EXTENSIONS--
+pdo_pgsql
+--SKIPIF--
+<?php
+if (PHP_INT_SIZE < 8) die('skip 64-bit only');
+require __DIR__ . '/config.inc';
+require dirname(__DIR__, 2) . '/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_EXCEPTION);
+$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
+
+$stmt = $db->query(<<<'SQL'
+SELECT 0::int2, (-1)::int2, (-32768)::int2, 32767::int2,
+       (-2147483648)::int4, 2147483647::int4, 7::int4,
+       999999999999999999::int8, (-999999999999999999)::int8,
+       1000000000000000000::int8, (-1000000000000000000)::int8,
+       9223372036854775807::int8, '-9223372036854775808'::int8
+SQL);
+var_dump($stmt->fetch(PDO::FETCH_NUM));
+?>
+--EXPECT--
+array(13) {
+  [0]=>
+  int(0)
+  [1]=>
+  int(-1)
+  [2]=>
+  int(-32768)
+  [3]=>
+  int(32767)
+  [4]=>
+  int(-2147483648)
+  [5]=>
+  int(2147483647)
+  [6]=>
+  int(7)
+  [7]=>
+  int(999999999999999999)
+  [8]=>
+  int(-999999999999999999)
+  [9]=>
+  int(1000000000000000000)
+  [10]=>
+  int(-1000000000000000000)
+  [11]=>
+  int(9223372036854775807)
+  [12]=>
+  int(-9223372036854775808)
+}