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)
+}