Skip to content

Releases: commandprompt/PL-php

2.6: Per-function state and native bytea

Choose a tag to compare

@jdatcmd jdatcmd released this 07 Jul 03:14
2e1432a

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's SD and GD.
    $_SD starts empty and resets when the function is redefined. The usual use
    is caching a prepared plan once per session without crowding the shared
    namespace.

  • bytea transform (bytea_plphp). A new CREATE EXTENSION bytea_plphp
    adds TRANSFORM FOR TYPE bytea, mapping a bytea to a raw, binary-safe PHP
    string (the bytes themselves, not the \x... text form) and back, mirroring
    PL/Python's bytea <-> bytes. Embedded NUL bytes survive the round trip,
    which the default text path truncates. It applies in nested contexts (a
    bytea column 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

Choose a tag to compare

@jdatcmd jdatcmd released this 06 Jul 16:44
6241f91

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

  • hstore transform (hstore_plphp). A new CREATE EXTENSION hstore_plphp
    adds TRANSFORM FOR TYPE hstore, mapping an hstore to a PHP associative
    array of string keys to string-or-null values and back (a PHP null value
    becomes an hstore NULL). Companion to the existing jsonb_plphp.
  • Transforms reach nested contexts. A declared jsonb/hstore transform
    now also applies to values of that type inside composite argument/result
    fields, RETURNS SETOF/return_next rows, and a trigger's $_TD rows
    (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 attaches DETAIL, HINT and (for ERROR) a custom
    SQLSTATE, mirroring PL/pgSQL's RAISE ... USING. The fields are readable
    on a caught PgError and survive an uncaught error out to the client.
  • TRUNCATE and INSTEAD OF triggers. Statement-level TRUNCATE triggers
    ($_TD['event'] = 'TRUNCATE') and view INSTEAD OF triggers
    ($_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's CHECK constraints are
    still enforced on results. Scalar domains were already transparent.
  • Project logo and brand assets. Light/dark PL/php logos and a square icon
    under doc/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
    (and SQLSTATE XX000); it now carries the original SQLSTATE, DETAIL and
    HINT through the zend_bailout reporting path.

2.4: Records, contexts, and receipts

Choose a tag to compare

@jdatcmd jdatcmd released this 06 Jul 05:20
19df7a9

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_next rows, OUT arguments, and trigger MODIFY fields 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.
  • anycompatible and anycompatiblearray accepted 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_try left Zend's bailout environment pointing into a dead stack frame; the next uncaught error jumped into garbage. Present since the zend_try blocks 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

Choose a tag to compare

@jdatcmd jdatcmd released this 06 Jul 01:22
189da12

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_plphp suites.

Changed

  • Array columns inside rows are PHP arrays. An array-typed column in $_TD['new']/['old'], in rows from spi_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. trigger MODIFY). 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

Choose a tag to compare

@jdatcmd jdatcmd released this 05 Jul 23:44

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 PgError exception with getSQLState(), getDetail(), and getHint() — PL/Perl's eval-trappable model with PL/Tcl's rich error info. pg_raise('error')/elog('ERROR') throw PgError with SQLSTATE P0001. Uncaught errors report exactly as before.
  • jsonb_plphp — a companion transform extension: with TRANSFORM FOR TYPE jsonb, jsonb arguments arrive as native PHP arrays/ints/floats/bools and PHP values convert straight back. PL/php core gained SQL-standard TRANSFORM FOR TYPE support 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's spi_exec -array a $query { body }.
  • Whole-array set-returning functionsreturn array($row1, $row2, ...) as an alternative to return_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 and plphp.start_proc (the counterpart of plperl.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, null elements map to SQL NULL, and array input is parsed with a real parser instead of zend_eval_string (unquoted text arrays used to crash; data no longer flows through eval()). 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.

⚠️ One behavioral note: code using broad 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

Choose a tag to compare

@jdatcmd jdatcmd released this 05 Jul 20:58
a45fbf3

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; returns false and 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 with spi_fetchrow, matching PL/Perl semantics. In 2.0 it was an alias of spi_exec_prepared; code relying on that should call spi_exec_prepared instead.

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

Choose a tag to compare

@cmdpreorg cmdpreorg released this 02 Jul 04:12
b8fd984

[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 DO blocksDO $$ ... $$ LANGUAGE plphp, via an inline
    handler.
  • Event trigger functionsRETURNS event_trigger, with $_TD['event']
    and $_TD['tag'].
  • Prepared statementsspi_prepare, spi_exec_prepared,
    spi_query_prepared, and spi_freeplan.
  • Transaction control in procedures — spi_commit and spi_rollback.
  • Explicit subtransactionssubtransaction(callable, ...).
  • Quoting helpersquote_literal, quote_nullable, quote_ident.
  • elog(level, message) supporting DEBUG/LOG/INFO/NOTICE/WARNING/ERROR.
  • Session initialization — module autoloading from a plphp_modules table
    and a plphp.start_proc configuration 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, uint64 SPI 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 returning NULL.
  • 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
    FunctionCallInfo argument shim before PG 12, the pre-CommandTag
    event-trigger tag before PG 13, EmitWarningsOnPlaceholders before PG 15, and
    an explicit SPI_start_transaction after commit/rollback before PG 15).

Removed

  • The obsolete pg_pltemplate install scripts (the catalog was removed in
    PostgreSQL 13).
  • The redundant plphpu variant.