Releases: commandprompt/PL-php
Release list
2.6: Per-function state and native bytea
PL/php 2.6.0 brings the two remaining PL/Python-parity features.
Added
-
Private per-function data:
$_SD. Each function now has an$_SD
associative array that persists across calls to that function within a
session and is private to it, the per-function counterpart to the
session-global$_SHARED. Together they mirror PL/Python'sSDandGD.
$_SDstarts empty and resets when the function is redefined. The usual use
is caching a prepared plan once per session without crowding the shared
namespace. -
byteatransform (bytea_plphp). A newCREATE EXTENSION bytea_plphp
addsTRANSFORM FOR TYPE bytea, mapping abyteato a raw, binary-safe PHP
string (the bytes themselves, not the\x...text form) and back, mirroring
PL/Python'sbytea<->bytes. Embedded NUL bytes survive the round trip,
which the default text path truncates. It applies in nested contexts (a
byteacolumn in a composite result, etc.) like the other transforms.
Upgrading
ALTER EXTENSION plphp UPDATE; moves an existing install to 2.6. $_SD lives
entirely in the C module, so there is no SQL-level change. Install
bytea_plphp separately with CREATE EXTENSION bytea_plphp CASCADE.
Verified across the PG 11-18 x PHP 8.1-8.4 CI matrix plus an AddressSanitizer
build; hstore_plphp and bytea_plphp are now covered by CI alongside
jsonb_plphp.
2.5: Transforms, triggers, and structured errors
PL/php 2.5.0 closes the remaining gaps against PL/Ruby: transforms now reach nested contexts, triggers cover TRUNCATE and INSTEAD OF, pg_raise carries structured error fields, and domains over arrays and composites work. Upgrade with ALTER EXTENSION plphp UPDATE.
Added
hstoretransform (hstore_plphp). A newCREATE EXTENSION hstore_plphp
addsTRANSFORM FOR TYPE hstore, mapping anhstoreto a PHP associative
array of string keys to string-or-null values and back (a PHPnullvalue
becomes an hstoreNULL). Companion to the existingjsonb_plphp.- Transforms reach nested contexts. A declared
jsonb/hstoretransform
now also applies to values of that type inside composite argument/result
fields,RETURNS SETOF/return_nextrows, and a trigger's$_TDrows
(including the'MODIFY'return), not only as top-level arguments and
results. Rows read back through SPI are still left in text form. - Structured
pg_raise.pg_raise(level, message [, detail [, hint [, sqlstate]]])now attachesDETAIL,HINTand (forERROR) a custom
SQLSTATE, mirroring PL/pgSQL'sRAISE ... USING. The fields are readable
on a caughtPgErrorand survive an uncaught error out to the client. TRUNCATEandINSTEAD OFtriggers. Statement-levelTRUNCATEtriggers
($_TD['event'] = 'TRUNCATE') and viewINSTEAD OFtriggers
($_TD['when'] = 'INSTEAD OF') are now supported; previously the handler
errored on the unrecognized event/timing.- Domains over arrays and composites. A function argument or result typed
as a domain is now handled as its base type, so a domain over an array
arrives as a PHP array (and can be returned as one) and a domain over a
composite as an associative array. The domain'sCHECKconstraints are
still enforced on results. Scalar domains were already transparent. - Project logo and brand assets. Light/dark PL/php logos and a square icon
underdoc/assets/, wired into the README and language reference.
Fixed
- Uncaught database errors lost their SQLSTATE, DETAIL and HINT. An error
that unwound to the top of a PL/php call was reported with only its message
(andSQLSTATE XX000); it now carries the originalSQLSTATE,DETAILand
HINTthrough thezend_bailoutreporting path.
2.4: Records, contexts, and receipts
PL/php 2.4.0 finishes the structural-conversion work, improves diagnostics, fixes a crash, and publishes performance numbers.
Added
- Structural composite conversion. Composite columns in rows, fields of composite arguments, and composites inside arrays arrive as associative PHP arrays, recursively. The reverse works the same way: return values,
return_nextrows, OUT arguments, and triggerMODIFYfields accept nested PHP arrays for composite slots. - Error CONTEXT lines. Messages raised while PL/php code runs carry
CONTEXT: PL/php function "name"(with anonymous-block and compilation variants), matching the other procedural languages. - Per-function memory contexts. A compiled function's descriptor and its subsidiary data are freed as a unit on redefinition.
anycompatibleandanycompatiblearrayaccepted on PostgreSQL 13 and newer.- ASAN in CI. A workflow job builds with AddressSanitizer and runs the suite against a
libasan-preloaded server. - A benchmark suite (
bench/run.sh) with published results (doc/benchmarks.md): within a few percent of PL/pgSQL on scalar and string work, 1.75x PL/Perl on SPI row loops.
Fixed
- Backend crash when an error crossed nested PL/php calls. A PostgreSQL error unwinding out of a handler's
zend_tryleft Zend's bailout environment pointing into a dead stack frame; the next uncaught error jumped into garbage. Present since thezend_tryblocks were introduced. - The use-after-free window in function recompilation (the cache briefly pointed at freed memory).
Upgrading
ALTER EXTENSION plphp UPDATE (no SQL-level changes; install the new plphp.so).
Tested on PostgreSQL 11 through 18 with PHP 8.1 through 8.4: all 23 regression tests plus the jsonb_plphp suite pass on every version, validated by the 12-job CI matrix.
See CHANGELOG.md for details.
2.3: Arrays all the way down
PL/php 2.3.0 — now with continuous integration: every change in this release (and every future PR) is validated by an 11-job matrix covering PostgreSQL 11–18 and PHP 8.1–8.4.
Added
- VARIADIC parameters — the collected arguments arrive as a single PHP array, like every other PL (
VARIADIC "any"is rejected with a clear error). - PHP 8.1 through 8.4 support — previously only 8.3 was tested; the documented range is now 8.1–8.4, with version guards where the Zend API changed.
- CI — GitHub Actions matrix running the full core +
jsonb_plphpsuites.
Changed
- Array columns inside rows are PHP arrays. An array-typed column in
$_TD['new']/['old'], in rows fromspi_fetch_row/spi_fetchrow, or in a composite argument's fields used to arrive as its literal text form ({a,b}); it is now a real PHP array, and converts back when assigned (e.g. triggerMODIFY). Code that string-parsed those values should use the array directly. - Hardened against host php.ini. PL/php now forces the error-reporting settings it depends on, disables opcache/JIT (useless for one-shot eval'd functions), neutralizes xdebug, and re-asserts its error callback on every call — messages and notices no longer depend on how the host's PHP happens to be configured.
Upgrading
ALTER EXTENSION plphp UPDATE (no SQL-level changes; install the new plphp.so).
See CHANGELOG.md for details.
2.2: Catch me if you can
PL/php 2.2.0 goes beyond PL/Perl and PL/Tcl parity — and fixes three long-standing bugs along the way.
Added
- Catchable database errors. Every database error from an SPI call is thrown as a
PgErrorexception withgetSQLState(),getDetail(), andgetHint()— PL/Perl'seval-trappable model with PL/Tcl's rich error info.pg_raise('error')/elog('ERROR')throwPgErrorwith SQLSTATEP0001. Uncaught errors report exactly as before. jsonb_plphp— a companion transform extension: withTRANSFORM FOR TYPE jsonb, jsonb arguments arrive as native PHP arrays/ints/floats/bools and PHP values convert straight back. PL/php core gained SQL-standardTRANSFORM FOR TYPEsupport to power it. (PL/Tcl has no transforms at all.)spi_each(query, callable)— streaming per-row callback over a cursor; the inline-loop equivalent of PL/Tcl'sspi_exec -array a $query { body }.- Whole-array set-returning functions —
return array($row1, $row2, ...)as an alternative toreturn_next(PL/Perl's array-reference form; restores 1.x behavior that 2.0 silently broke). plphp.on_init— PHP source executed at interpreter initialization, before modules andplphp.start_proc(the counterpart ofplperl.on_init).- A tested cookbook (
doc/cookbook.md) — validation constraints, bcrypt, HMAC tokens, JSON reshaping, regex SRFs, an audit trigger, batch commits, streaming scans, CSV/XML shredding, compression — every recipe runs in the regression suite.
Fixed
- Array conversion rewritten in both directions: string elements are properly escaped,
nullelements map to SQLNULL, and array input is parsed with a real parser instead ofzend_eval_string(unquoted text arrays used to crash; data no longer flows througheval()). Array arguments are detected from the declared type, not a "starts with{" guess. - INOUT parameters in procedures work —
CALL p(...)used to fail with "function returning record called in context that cannot accept type record".
Upgrading
ALTER EXTENSION plphp UPDATE (no SQL-level changes; install the new plphp.so). The jsonb transform is a separate CREATE EXTENSION jsonb_plphp CASCADE.
Tested on PostgreSQL 11 through 18 with PHP 8.3 (embed SAPI, NTS): all 23 regression tests plus the jsonb_plphp suite pass on every version.
See CHANGELOG.md for full details.
catch (\Throwable) blocks will now also catch database errors (PgError); rethrow if you don't intend to handle them.
2.1: Stream, don't materialize
PL/php 2.1.0 closes the last PL/Perl parity gap: cursor-streaming SPI.
Added
spi_query(query)— opens a cursor and returns its name.spi_fetchrow(cursor)— fetches one row at a time as an associative array; returnsfalseand closes the cursor at exhaustion.spi_cursor_close(cursor)— abandon a cursor early; harmless on unknown/closed cursors.
Large result sets now scan in constant memory: a 1M-row scan holds the backend at ~34 MB RSS where spi_exec needs ~110 MB.
Changed
- Breaking:
spi_query_prepared(plan, args...)now opens a cursor consumed withspi_fetchrow, matching PL/Perl semantics. In 2.0 it was an alias ofspi_exec_prepared; code relying on that should callspi_exec_preparedinstead.
Upgrading
ALTER EXTENSION plphp UPDATE (a no-op at the SQL level — the new functions live in the C module; install the new plphp.so).
Tested on PostgreSQL 11 through 18 with PHP 8.3 (embed SAPI, NTS): all 18 regression tests pass on every version.
See CHANGELOG.md for details.
2.0: It is about time
[2.0.0] — 2026-07-01
A ground-up modernization of PL/php (the previous release, 1.4, dates from 2010)
for current software. Tested on PostgreSQL 11 through 18 with PHP 8.3
(embed SAPI, non-thread-safe).
Added
- Anonymous
DOblocks —DO $$ ... $$ LANGUAGE plphp, via an inline
handler. - Event trigger functions —
RETURNS event_trigger, with$_TD['event']
and$_TD['tag']. - Prepared statements —
spi_prepare,spi_exec_prepared,
spi_query_prepared, andspi_freeplan. - Transaction control in procedures —
spi_commitandspi_rollback. - Explicit subtransactions —
subtransaction(callable, ...). - Quoting helpers —
quote_literal,quote_nullable,quote_ident. elog(level, message)supportingDEBUG/LOG/INFO/NOTICE/WARNING/ERROR.- Session initialization — module autoloading from a
plphp_modulestable
and aplphp.start_procconfiguration setting. - Packaging as a first-class extension (
CREATE EXTENSION plphp) and a
regression test for every new feature. - Documentation: a language reference (
doc/plphp.md) and PL/Perl and PL/Tcl
feature comparisons.
Changed
- Ported the C code to the PostgreSQL 18 API:
FunctionCallInfo.args[](PG 12),
TupleDescAttr(PG 10),SearchSysCache1,strlcpy,TextDatumGetCString,
ALLOCSET_DEFAULT_SIZES,uint64SPI row counts (PG 11), and the removal of
SPI_restore_connection(PG 10). - Ported the interpreter glue to the PHP 8 Zend API: the new zval/refcount
model, hash-table and resource APIs,call_user_function,
zend_rebuild_symbol_table, embed startup, and the PHP 8.1+zend_error_cb
signature. - Uncaught PHP exceptions (for example calling an undefined function) are now
reported as PostgreSQL errors instead of silently returningNULL. - PHP deprecations (such as the legacy
"${var}"string interpolation) are now
non-fatal notices. - Replaced the autoconf build with a PGXS
Makefile. - Version guards keep releases back to PostgreSQL 11 working (a
FunctionCallInfoargument shim before PG 12, the pre-CommandTag
event-trigger tag before PG 13,EmitWarningsOnPlaceholdersbefore PG 15, and
an explicitSPI_start_transactionafter commit/rollback before PG 15).
Removed
- The obsolete
pg_pltemplateinstall scripts (the catalog was removed in
PostgreSQL 13). - The redundant
plphpuvariant.