Skip to content

Commit d4ff49b

Browse files
committed
pdo_pgsql: unbuffered fetching: test & NEWS
memory & non-regression test mention in the NEWS file
1 parent 260e87a commit d4ff49b

File tree

2 files changed

+166
-1
lines changed

2 files changed

+166
-1
lines changed

NEWS

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ PHP NEWS
4040
- PDO_Firebird:
4141
. Fixed GH-15604 (Always make input parameters nullable). (sim1984)
4242

43+
- PDO_PGSQL:
44+
. Implement GH-15387 Pdo\Pgsql::setAttribute(PDO::ATTR_PREFETCH, 0) or
45+
Pdo\PgSql::prepare(…, [ PDO::ATTR_PREFETCH => 0 ]) make fetch() lazy
46+
instead of storing the whole result set in memory (Guillaume Outters)
47+
4348
- Reflection:
4449
. Fixed bug GH-15718 (Segfault on ReflectionProperty::get{Hook,Hooks}() on
4550
dynamic properties). (DanielEScherzer)
@@ -577,7 +582,7 @@ PHP NEWS
577582
. Added class Pdo\Pgsql. (danack, kocsismate)
578583
. Retrieve the memory usage of the query result resource. (KentarouTakeda)
579584
. Added Pdo\Pgsql::setNoticeCallBack method to receive DB notices.
580-
(outtersg)
585+
(Guillaume Outters)
581586
. Added custom SQL parser. (Matteo Beccati)
582587

583588
- PDO_SQLITE:

ext/pdo_pgsql/tests/gh15287.phpt

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
--TEST--
2+
PDO PgSQL #15287 (Pdo\Pgsql has no real lazy fetch mode)
3+
--EXTENSIONS--
4+
pdo
5+
pdo_pgsql
6+
--SKIPIF--
7+
<?php
8+
require __DIR__ . '/config.inc';
9+
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
10+
PDOTest::skip();
11+
?>
12+
--FILE--
13+
<?php
14+
15+
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
16+
$pdo = PDOTest::test_factory(__DIR__ . '/common.phpt');
17+
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
18+
19+
// We need a dataset of several KB so that memory gain is significant.
20+
// See https://www.postgresql.org/message-id/1140652.1687950987%40sss.pgh.pa.us
21+
$pdo->exec("create temp table t (n int, t text)");
22+
$pdo->exec("insert into t values (0, 'original')");
23+
for ($i = -1; ++$i < 8;) {
24+
$pdo->exec("insert into t select n + 1, 'non '||t from t");
25+
}
26+
27+
$reqOf3 = 'select 79 n union all select 80 union all select 81';
28+
$reqOfBig = 'select * from t';
29+
30+
echo "=== mem test ===\n";
31+
32+
// First execute without lazy fetching, as a reference and non-regression;
33+
// execute twice: in case warmup reduces memory consumption, we want the stabilized consumption.
34+
for ($i = -1; ++$i < 5;) {
35+
$attrs = [];
36+
switch ($i) {
37+
case 0:
38+
case 3:
39+
echo "Without lazy fetching:\n";
40+
break;
41+
case 2:
42+
echo "With statement-scoped lazy fetching:\n";
43+
$attrs = [ PDO::ATTR_PREFETCH => 0 ];
44+
break;
45+
case 4:
46+
echo "With connection-scoped lazy fetching:\n";
47+
$pdo->setAttribute(PDO::ATTR_PREFETCH, 0);
48+
break;
49+
}
50+
$stmt = $pdo->prepare($reqOfBig, $attrs);
51+
$stmt->execute();
52+
$res = [];
53+
// No fetchAll because we want the memory of the result of the FORElast call (the last one is empty).
54+
while (($re = $stmt->fetch())) {
55+
$res[] = $re;
56+
// Memory introspection relies on an optionally-compiled constant.
57+
if (defined('PDO::PGSQL_ATTR_RESULT_MEMORY_SIZE')) {
58+
$mem = $stmt->getAttribute(PDO::PGSQL_ATTR_RESULT_MEMORY_SIZE);
59+
} else {
60+
// If not there emulate a return value which validates our test.
61+
$mem = $i < 2 ? 1 : 0;
62+
}
63+
}
64+
echo "ResultSet is $mem bytes long\n";
65+
if ($i >= 2) {
66+
echo "ResultSet is " . ($mem > $mem0 ? "longer" : ($mem == $mem0 ? "not shorter" : ($mem <= $mem0 / 2 ? "more than twice shorter" : "a bit shorter"))) . " than without lazy fetching\n";
67+
} else {
68+
$mem0 = $mem;
69+
}
70+
}
71+
72+
$pdo->setAttribute(PDO::ATTR_PREFETCH, 0);
73+
74+
function display($res)
75+
{
76+
echo implode("\n", array_map(fn($row) => implode("\t", $row), $res))."\n";
77+
}
78+
79+
foreach ([
80+
[ 'query', 'fetch' ],
81+
[ 'query', 'fetchAll' ],
82+
[ 'prepare', 'fetch' ],
83+
[ 'prepare', 'fetchAll' ],
84+
] as $mode) {
85+
echo "=== with " . implode(' / ', $mode). " ===\n";
86+
switch ($mode[0]) {
87+
case 'query':
88+
$stmt = $pdo->query($reqOf3);
89+
break;
90+
case 'prepare':
91+
$stmt = $pdo->prepare($reqOf3);
92+
$stmt->execute();
93+
break;
94+
}
95+
switch ($mode[1]) {
96+
case 'fetch':
97+
$res = [];
98+
while (($re = $stmt->fetch())) {
99+
$res[] = $re;
100+
}
101+
break;
102+
case 'fetchAll':
103+
$res = $stmt->fetchAll();
104+
break;
105+
}
106+
display($res);
107+
}
108+
echo "DML works too:\n";
109+
$pdo->exec("create temp table t2 as select 678 n, 'ok' status");
110+
echo "multiple calls to the same prepared statement, some interrupted before having read all results:\n";
111+
$stmt = $pdo->prepare("select :1 n union all select :1 + 1 union all select :1 + 2 union all select :1 + 3");
112+
$stmt->execute([ 32 ]);
113+
$res = []; for ($i = -1; ++$i < 2;) $res[] = $stmt->fetch(); display($res);
114+
$stmt->execute([ 15 ]);
115+
$res = []; while (($re = $stmt->fetch())) $res[] = $re; display($res);
116+
$stmt->execute([ 0 ]);
117+
$res = []; for ($i = -1; ++$i < 2;) $res[] = $stmt->fetch(); display($res);
118+
display($pdo->query("select * from t2")->fetchAll());
119+
?>
120+
--EXPECTF--
121+
=== mem test ===
122+
Without lazy fetching:
123+
ResultSet is %d bytes long
124+
ResultSet is %d bytes long
125+
With statement-scoped lazy fetching:
126+
ResultSet is %d bytes long
127+
ResultSet is more than twice shorter than without lazy fetching
128+
Without lazy fetching:
129+
ResultSet is %d bytes long
130+
ResultSet is not shorter than without lazy fetching
131+
With connection-scoped lazy fetching:
132+
ResultSet is %d bytes long
133+
ResultSet is more than twice shorter than without lazy fetching
134+
=== with query / fetch ===
135+
79
136+
80
137+
81
138+
=== with query / fetchAll ===
139+
79
140+
80
141+
81
142+
=== with prepare / fetch ===
143+
79
144+
80
145+
81
146+
=== with prepare / fetchAll ===
147+
79
148+
80
149+
81
150+
DML works too:
151+
multiple calls to the same prepared statement, some interrupted before having read all results:
152+
32
153+
33
154+
15
155+
16
156+
17
157+
18
158+
0
159+
1
160+
678 ok

0 commit comments

Comments
 (0)