ext/pdo_pgsql: Add Pdo\Pgsql::ATTR_CHUNK_SIZE for chunked result fetching - #23210
ext/pdo_pgsql: Add Pdo\Pgsql::ATTR_CHUNK_SIZE for chunked result fetching#23210KentarouTakeda wants to merge 5 commits into
Pdo\Pgsql::ATTR_CHUNK_SIZE for chunked result fetching#23210Conversation
| try { | ||
| $pdo->setAttribute(Pdo\Pgsql::ATTR_CHUNK_SIZE, -1); | ||
| } catch (ValueError $e) { | ||
| echo $e->getMessage(), "\n"; |
There was a problem hiding this comment.
| echo $e->getMessage(), "\n"; | |
| echo $e::class, ': ', $e->getMessage(), "\n"; |
| try { | ||
| $pdo->prepare("SELECT 1", [Pdo\Pgsql::ATTR_CHUNK_SIZE => -42]); | ||
| } catch (ValueError $e) { | ||
| echo $e->getMessage(), "\n"; |
There was a problem hiding this comment.
| echo $e->getMessage(), "\n"; | |
| echo $e::class, ': ', $e->getMessage(), "\n"; |
| new PDO(getenv('PDOTEST_DSN'), $user === false ? null : $user, | ||
| $pass === false ? null : $pass, [Pdo\Pgsql::ATTR_CHUNK_SIZE => -1]); | ||
| } catch (ValueError $e) { | ||
| echo $e->getMessage(), "\n"; |
There was a problem hiding this comment.
| echo $e->getMessage(), "\n"; | |
| echo $e::class, ': ', $e->getMessage(), "\n"; |
| printf("%-8s accepted as %d\n", get_debug_type($value), | ||
| $pdo->getAttribute(Pdo\Pgsql::ATTR_CHUNK_SIZE)); | ||
| } catch (TypeError $e) { | ||
| printf("%-8s %s\n", get_debug_type($value), $e->getMessage()); |
There was a problem hiding this comment.
| printf("%-8s %s\n", get_debug_type($value), $e->getMessage()); | |
| printf("%-8s %s: %s\n", get_debug_type($value), $e::class, $e->getMessage()); |
| Pdo\Pgsql::ATTR_CHUNK_SIZE => 4, | ||
| ]); | ||
| } catch (ValueError $e) { | ||
| echo $e->getMessage(), "\n"; |
There was a problem hiding this comment.
| echo $e->getMessage(), "\n"; | |
| echo $e::class, ': ', $e->getMessage(), "\n"; |
| ?> | ||
| --EXPECT-- | ||
| === a negative value is refused by setAttribute() === | ||
| Pdo\Pgsql::ATTR_CHUNK_SIZE must be between 0 and 2147483647 |
There was a problem hiding this comment.
| Pdo\Pgsql::ATTR_CHUNK_SIZE must be between 0 and 2147483647 | |
| ValueError: Pdo\Pgsql::ATTR_CHUNK_SIZE must be between 0 and 2147483647 |
| Pdo\Pgsql::ATTR_CHUNK_SIZE must be between 0 and 2147483647 | ||
| the attribute kept its previous value: int(0) | ||
| === and by the prepare() driver options === | ||
| Pdo\Pgsql::ATTR_CHUNK_SIZE must be between 0 and 2147483647 |
There was a problem hiding this comment.
| Pdo\Pgsql::ATTR_CHUNK_SIZE must be between 0 and 2147483647 | |
| ValueError: Pdo\Pgsql::ATTR_CHUNK_SIZE must be between 0 and 2147483647 |
| === and by the prepare() driver options === | ||
| Pdo\Pgsql::ATTR_CHUNK_SIZE must be between 0 and 2147483647 | ||
| === and by the constructor options === | ||
| Pdo\Pgsql::ATTR_CHUNK_SIZE must be between 0 and 2147483647 |
There was a problem hiding this comment.
| Pdo\Pgsql::ATTR_CHUNK_SIZE must be between 0 and 2147483647 | |
| ValueError: Pdo\Pgsql::ATTR_CHUNK_SIZE must be between 0 and 2147483647 |
| float Attribute value must be of type int for selected attribute, float given | ||
| string accepted as 8 | ||
| array Attribute value must be of type int for selected attribute, array given | ||
| null Attribute value must be of type int for selected attribute, null given | ||
| stdClass Attribute value must be of type int for selected attribute, stdClass given |
There was a problem hiding this comment.
| float Attribute value must be of type int for selected attribute, float given | |
| string accepted as 8 | |
| array Attribute value must be of type int for selected attribute, array given | |
| null Attribute value must be of type int for selected attribute, null given | |
| stdClass Attribute value must be of type int for selected attribute, stdClass given | |
| float TypeError: Attribute value must be of type int for selected attribute, float given | |
| string accepted as 8 | |
| array TypeError: Attribute value must be of type int for selected attribute, array given | |
| null TypeError: Attribute value must be of type int for selected attribute, null given | |
| stdClass TypeError: Attribute value must be of type int for selected attribute, stdClass given |
There was a problem hiding this comment.
string is accepted, so this line comes from the try branch, not the catch. Prefixing it with TypeError: fails the test.
| null Attribute value must be of type int for selected attribute, null given | ||
| stdClass Attribute value must be of type int for selected attribute, stdClass given | ||
| === scrollable cursors have nothing to stream === | ||
| Pdo\Pgsql::ATTR_CHUNK_SIZE cannot be combined with PDO::ATTR_CURSOR set to PDO::CURSOR_SCROLL |
There was a problem hiding this comment.
| Pdo\Pgsql::ATTR_CHUNK_SIZE cannot be combined with PDO::ATTR_CURSOR set to PDO::CURSOR_SCROLL | |
| ValueError: Pdo\Pgsql::ATTR_CHUNK_SIZE cannot be combined with PDO::ATTR_CURSOR set to PDO::CURSOR_SCROLL |
| try { | ||
| $pdo->setAttribute(Pdo\Pgsql::ATTR_CHUNK_SIZE, 2147483648); | ||
| } catch (ValueError $e) { | ||
| echo "setAttribute(): ", $e->getMessage(), "\n"; |
There was a problem hiding this comment.
| echo "setAttribute(): ", $e->getMessage(), "\n"; | |
| echo 'setAttribute(): ', $e::class, ': ', $e->getMessage(), "\n"; |
| try { | ||
| $pdo->prepare("SELECT 1", [Pdo\Pgsql::ATTR_CHUNK_SIZE => PHP_INT_MAX]); | ||
| } catch (ValueError $e) { | ||
| echo "prepare(): ", $e->getMessage(), "\n"; |
There was a problem hiding this comment.
| echo "prepare(): ", $e->getMessage(), "\n"; | |
| echo 'prepare(): ', $e::class, ': ', $e->getMessage(), "\n"; |
| === INT_MAX is the largest chunk size === | ||
| accepted as: int(2147483647) | ||
| === one more is refused === | ||
| setAttribute(): Pdo\Pgsql::ATTR_CHUNK_SIZE must be between 0 and 2147483647 |
There was a problem hiding this comment.
| setAttribute(): Pdo\Pgsql::ATTR_CHUNK_SIZE must be between 0 and 2147483647 | |
| setAttribute(): ValueError: Pdo\Pgsql::ATTR_CHUNK_SIZE must be between 0 and 2147483647 |
| accepted as: int(2147483647) | ||
| === one more is refused === | ||
| setAttribute(): Pdo\Pgsql::ATTR_CHUNK_SIZE must be between 0 and 2147483647 | ||
| prepare(): Pdo\Pgsql::ATTR_CHUNK_SIZE must be between 0 and 2147483647 |
There was a problem hiding this comment.
| prepare(): Pdo\Pgsql::ATTR_CHUNK_SIZE must be between 0 and 2147483647 | |
| prepare(): ValueError: Pdo\Pgsql::ATTR_CHUNK_SIZE must be between 0 and 2147483647 |
|
Left one correction inline.
|
A good chunk of #22799 is merged in extracted PRs. The changes I proposed here are not part of the still "under discussion" questions -- what I propose here gets regularly merged, last one here today: #23184. Sorry about the one I missed! |
ff41006 to
62266ca
Compare
|
Applied. |
Note: this PR currently also contains the bug fix commits from #23065. The feature is independent of them, but the existing lazy fetch defects break this feature's tests too. Once #23065 is merged, rebasing will leave only the last commit here.
Summary
Adds chunked fetching through
Pdo\Pgsql::ATTR_CHUNK_SIZE. It generalizes the lazy fetch from #15287, and the chunk size lets you choose the balance between memory and speed. ext/pgsql got this aspg_set_chunked_rows_sizein #14571, but there was no way to use it from ext/pdo_pgsql.Benchmark
Benchmark code
Run on a build without
--enable-debug(PostgreSQL 18.4 / libpq 18.4):bufferedATTR_PREFETCH => 0ATTR_CHUNK_SIZE => 1ATTR_CHUNK_SIZE => 10ATTR_CHUNK_SIZE => 100ATTR_CHUNK_SIZE => 1000Usage
Set on the connection:
Set on the statement, overriding the connection level:
Design note
The part in bold is where I am not confident that the behavior is right. I would appreciate feedback.
ATTR_CHUNK_SIZEoverridesATTR_PREFETCH.ATTR_PREFETCHis left unset and onlyATTR_CHUNK_SIZEis given, this is treated as a request for chunked fetching.ATTR_PREFETCH => 0. The behavior changes from lazy to chunk.ATTR_CHUNK_SIZE => 1behaves the same as lazy, and once a chunk size is given, I think honoring it is the natural behavior.ATTR_CHUNK_SIZEandATTR_CURSOR => CURSOR_SCROLLare mutually exclusive.CURSOR_SCROLLdisables a connection-levelATTR_CHUNK_SIZE.ValueErroris thrown.