Skip to content

Commit 5bdb515

Browse files
committed
pdo_pgsql: unbuffered fetching: test & NEWS
memory & non-regression test mention in the NEWS file
1 parent 53da36b commit 5bdb515

File tree

2 files changed

+187
-1
lines changed

2 files changed

+187
-1
lines changed

NEWS

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ 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+
/!\ In this mode statements cannot be run parallely
48+
4349
- Reflection:
4450
. Fixed bug GH-15718 (Segfault on ReflectionProperty::get{Hook,Hooks}() on
4551
dynamic properties). (DanielEScherzer)
@@ -577,7 +583,7 @@ PHP NEWS
577583
. Added class Pdo\Pgsql. (danack, kocsismate)
578584
. Retrieve the memory usage of the query result resource. (KentarouTakeda)
579585
. Added Pdo\Pgsql::setNoticeCallBack method to receive DB notices.
580-
(outtersg)
586+
(Guillaume Outters)
581587
. Added custom SQL parser. (Matteo Beccati)
582588

583589
- PDO_SQLITE:

ext/pdo_pgsql/tests/gh15287.phpt

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
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+
function display($res)
31+
{
32+
echo implode("\n", array_map(fn($row) => implode("\t", $row), $res))."\n";
33+
}
34+
35+
echo "=== non regression ===\n";
36+
37+
// libpq explicitely requires single-row-mode statements to run one at a time (one stmt must
38+
// be fully read, or aborted, before another one can be launched).
39+
// Ensure that integration does not break the ability of the traditional, prefetched mode,
40+
// to mix fetching of multiple statements' result.
41+
$stmt1 = $pdo->query($reqOf3);
42+
$stmt2 = $pdo->query("select * from ($reqOf3) t order by n desc");
43+
for ($i = -1; ++$i < 3;) {
44+
display([ $stmt1->fetch() ]);
45+
display([ $stmt2->fetch() ]);
46+
}
47+
48+
echo "=== mem test ===\n";
49+
50+
// First execute without lazy fetching, as a reference and non-regression;
51+
// execute twice: in case warmup reduces memory consumption, we want the stabilized consumption.
52+
for ($i = -1; ++$i < 5;) {
53+
$attrs = [];
54+
switch ($i) {
55+
case 0:
56+
case 3:
57+
echo "Without lazy fetching:\n";
58+
break;
59+
case 2:
60+
echo "With statement-scoped lazy fetching:\n";
61+
$attrs = [ PDO::ATTR_PREFETCH => 0 ];
62+
break;
63+
case 4:
64+
echo "With connection-scoped lazy fetching:\n";
65+
$pdo->setAttribute(PDO::ATTR_PREFETCH, 0);
66+
break;
67+
}
68+
$stmt = $pdo->prepare($reqOfBig, $attrs);
69+
$stmt->execute();
70+
$res = [];
71+
// No fetchAll because we want the memory of the result of the FORElast call (the last one is empty).
72+
while (($re = $stmt->fetch())) {
73+
$res[] = $re;
74+
// Memory introspection relies on an optionally-compiled constant.
75+
if (defined('PDO::PGSQL_ATTR_RESULT_MEMORY_SIZE')) {
76+
$mem = $stmt->getAttribute(PDO::PGSQL_ATTR_RESULT_MEMORY_SIZE);
77+
} else {
78+
// If not there emulate a return value which validates our test.
79+
$mem = $i < 2 ? 1 : 0;
80+
}
81+
}
82+
echo "ResultSet is $mem bytes long\n";
83+
if ($i >= 2) {
84+
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";
85+
} else {
86+
$mem0 = $mem;
87+
}
88+
}
89+
90+
$pdo->setAttribute(PDO::ATTR_PREFETCH, 0);
91+
92+
foreach ([
93+
[ 'query', 'fetch' ],
94+
[ 'query', 'fetchAll' ],
95+
[ 'prepare', 'fetch' ],
96+
[ 'prepare', 'fetchAll' ],
97+
] as $mode) {
98+
echo "=== with " . implode(' / ', $mode). " ===\n";
99+
switch ($mode[0]) {
100+
case 'query':
101+
$stmt = $pdo->query($reqOf3);
102+
break;
103+
case 'prepare':
104+
$stmt = $pdo->prepare($reqOf3);
105+
$stmt->execute();
106+
break;
107+
}
108+
switch ($mode[1]) {
109+
case 'fetch':
110+
$res = [];
111+
while (($re = $stmt->fetch())) {
112+
$res[] = $re;
113+
}
114+
break;
115+
case 'fetchAll':
116+
$res = $stmt->fetchAll();
117+
break;
118+
}
119+
display($res);
120+
}
121+
echo "DML works too:\n";
122+
$pdo->exec("create temp table t2 as select 678 n, 'ok' status");
123+
echo "multiple calls to the same prepared statement, some interrupted before having read all results:\n";
124+
$stmt = $pdo->prepare("select :1 n union all select :1 + 1 union all select :1 + 2 union all select :1 + 3");
125+
$stmt->execute([ 32 ]);
126+
$res = []; for ($i = -1; ++$i < 2;) $res[] = $stmt->fetch(); display($res);
127+
$stmt->execute([ 15 ]);
128+
$res = []; while (($re = $stmt->fetch())) $res[] = $re; display($res);
129+
$stmt->execute([ 0 ]);
130+
$res = []; for ($i = -1; ++$i < 2;) $res[] = $stmt->fetch(); display($res);
131+
display($pdo->query("select * from t2")->fetchAll());
132+
?>
133+
--EXPECTF--
134+
=== non regression ===
135+
79
136+
81
137+
80
138+
80
139+
81
140+
79
141+
=== mem test ===
142+
Without lazy fetching:
143+
ResultSet is %d bytes long
144+
ResultSet is %d bytes long
145+
With statement-scoped lazy fetching:
146+
ResultSet is %d bytes long
147+
ResultSet is more than twice shorter than without lazy fetching
148+
Without lazy fetching:
149+
ResultSet is %d bytes long
150+
ResultSet is not shorter than without lazy fetching
151+
With connection-scoped lazy fetching:
152+
ResultSet is %d bytes long
153+
ResultSet is more than twice shorter than without lazy fetching
154+
=== with query / fetch ===
155+
79
156+
80
157+
81
158+
=== with query / fetchAll ===
159+
79
160+
80
161+
81
162+
=== with prepare / fetch ===
163+
79
164+
80
165+
81
166+
=== with prepare / fetchAll ===
167+
79
168+
80
169+
81
170+
DML works too:
171+
multiple calls to the same prepared statement, some interrupted before having read all results:
172+
32
173+
33
174+
15
175+
16
176+
17
177+
18
178+
0
179+
1
180+
678 ok

0 commit comments

Comments
 (0)