Changelog

All notable changes to PL/php are documented in this file.

The format is based on Keep a Changelog, and the project aims to follow Semantic Versioning.

[Unreleased]

Added

  • PGXN packaging. A META.json conforming to version 1.0.0 of the PGXN distribution metadata spec, so PL/php can be released on PGXN and installed with pgxn install plphp. It lists the four extensions in the distribution (plphp plus the jsonb_plphp, hstore_plphp, and bytea_plphp transforms).

Fixed

  • Documentation: the README test-count badge and the INSTALL note on the optional transforms, which still described a single jsonb transform.

[2.6.0] - 2026-07-06

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. The pair mirrors PL/Python’s SD and GD. $_SD starts empty and is reset when the function is redefined. Typical use is caching a prepared plan once per session.
  • 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. Applies in nested contexts like the other transforms.

[2.5.0] - 2026-07-06

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.0] - 2026-07-06

Added

  • Composite values convert structurally. Composite-typed columns in rows, fields of composite arguments, and composites inside arrays now arrive as associative PHP arrays (recursively, arrays included), and PHP arrays convert back to composite values on return, in return_next rows, and through trigger MODIFY, completing the structural-conversion work begun with array columns in 2.3.0.
  • anycompatible and anycompatiblearray are accepted as polymorphic argument/return types on PostgreSQL 13 and newer.
  • ASAN in CI. A workflow job builds with AddressSanitizer and runs the suite against a libasan-preloaded server, catching memory-safety bugs mechanically (new ASAN_FLAGS hook in the Makefiles).
  • A reproducible benchmark suite (bench/) and published results (doc/benchmarks.md) comparing PL/php with PL/pgSQL and PL/Perl: within a few percent on scalar and string work, 1.75x PL/Perl on SPI row loops.
  • Error CONTEXT lines. Messages raised while PL/php code runs carry a CONTEXT: PL/php function "name" line (or the anonymous-block/compilation variants), like every other procedural language.

Fixed

  • Per-function memory contexts. Each compiled function’s descriptor and subsidiary data (fmgr info records included) now live in their own memory context, deleted wholesale on redefinition, implementing a FIXME as old as the file. This also closes a use-after-free window: the compiled- function cache used to briefly point at freed memory during recompilation, which an unluckily timed statement cancel could have hit.
  • 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 then jumped into garbage. Nested regression cases added.

[2.3.0] - 2026-07-06

Added

  • Continuous integration: a GitHub Actions matrix building and running the full regression suite (core + jsonb_plphp) on PostgreSQL 11-18 and PHP 8.1/8.2/8.3/8.4 for every pull request.
  • VARIADIC parameters. A variadic function’s collected arguments arrive as a single PHP array, matching every other PL (VARIADIC "any" remains unsupported and is rejected with an error). Previously any VARIADIC declaration failed.

Changed

  • Array columns inside rows are PHP arrays now. 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 now converts to a PHP array, and converts back when assigned (e.g. trigger MODIFY). Code that string-parsed those values should use the array directly.

Fixed

  • PHP 8.1, 8.2, and 8.4 compatibility. PL/php now builds and passes the full suite on PHP 8.1 through 8.4: a version guard for the php_module_startup signature change (8.2), explicit fgetcsv arguments in the cookbook (8.4 deprecation), and a clear compile error below 8.1. The Makefiles now prefer the libphp matching PHP_CONFIG’s version when several are installed (see INSTALL for a packaging caveat about shared SONAMEs).

[2.2.0] - 2026-07-05

Added

  • Catchable database errors. Every database error raised by an SPI call is now thrown as a PgError PHP exception (with getSQLState(), getDetail(), and getHint()), so try/catch works the way PL/Perl’s eval and PL/Tcl’s catch do. pg_raise('error', ...) and elog('ERROR', ...) throw a PgError with SQLSTATE P0001. Uncaught errors abort the statement with the same message format as before. Database errors inside subtransaction() callbacks are now catchable too.
  • Whole-array set-returning functions. An SRF may return the entire result set as one array with one element per row (PL/Perl’s “return a reference to an array” form), instead of or in addition to calling return_next. This had been PL/php 1.x behavior that 2.0 silently broke (such functions returned zero rows).
  • spi_each(query, callable): invoke a callback once per row, streaming over a cursor; returning false stops early. The inline-loop equivalent of PL/Tcl’s spi_exec -array a $query { body }.
  • plphp.on_init: a snippet of PHP source executed when the interpreter is first initialized in a session, before modules and plphp.start_proc; the counterpart of plperl.on_init.
  • jsonb_plphp transform extension: with TRANSFORM FOR TYPE jsonb, jsonb arguments arrive as native PHP values (arrays/int/float/bool/null) and PHP values convert straight back to jsonb, like jsonb_plperl (which PL/Tcl has no equivalent of). PL/php core gained the TRANSFORM FOR TYPE protocol support this builds on.

  • A tested cookbook (doc/cookbook.md): filter_var CHECK constraints, bcrypt passwords, HMAC tokens, recursive JSON reshaping, regex set-returning functions, a JSON-diff audit trigger, batch processing with periodic commits, streaming scans that stop early, CSV and XML shredding, and zlib compression. Every recipe in the “Tested” section runs in the new cookbook regression test.

  • New coverage regression test pinning previously-untested paths: DML and utility statements through spi_exec, trigger arguments ($_TD['args']), cursor behavior across spi_commit and rolled-back subtransactions, plphp.start_proc failure handling, multibyte and TOAST-sized values, PHP exception handling, and DO-block runtime errors.

Fixed

  • INOUT parameters in procedures work. CALL on a PL/php procedure with INOUT parameters used to fail (“function returning record called in context that cannot accept type record”): a procedure’s result is always a record (even with a single INOUT parameter), which broke both the single-OUT scalar-return shortcut and the record descriptor lookup (which needed a ReturnSetInfo that CALL never supplies; it is now derived from the parameter declarations via get_call_result_type). The usual assignment convention now works: $param = ...;.
  • Array conversion rewritten in both directions, fixing three long-standing FIXMEs:
    • Returning a PHP array containing null now produces a SQL NULL element instead of raising an error.
    • String elements are now properly quoted and escaped on output; embedded quotes, backslashes, commas, braces, and spaces survive the round trip.
    • Array input is now parsed with a real parser instead of being rewritten into PHP source and passed through zend_eval_string. Unquoted text elements (e.g. {foo,bar}) previously crashed with an undefined-constant error and now arrive as strings; quoted/escaped elements are decoded correctly; data no longer flows through eval().
  • Array arguments are now detected from the argument’s declared type instead of the old “value starts with {” heuristic, so a text argument whose value happens to start with a brace is no longer misparsed as an array.

[2.1.0] - 2026-07-05

Added

  • Cursor-streaming SPI: spi_query(query) opens a cursor and returns its name, spi_fetchrow(cursor) fetches one row at a time (returning false and closing the cursor at exhaustion), and spi_cursor_close(cursor) abandons a cursor early. Large result sets can now be scanned in constant memory instead of being materialized by spi_exec. Matches PL/Perl’s spi_query/spi_fetchrow/spi_cursor_close.

Changed

  • Breaking: spi_query_prepared(plan, args...) now opens a cursor and returns its name for use with spi_fetchrow, matching PL/Perl semantics. In 2.0 it was an alias of spi_exec_prepared (returning a materialized result resource); code that relied on the alias should call spi_exec_prepared instead.

[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 blocks: 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, and spi_freeplan.
  • Transaction control in procedures: spi_commit and spi_rollback.
  • Explicit subtransactions: subtransaction(callable, ...).
  • Quoting helpers: quote_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.

Security

  • PHP’s safe_mode was removed in PHP 5.4, so PL/php can no longer be sandboxed. It is now an untrusted, superuser-only language, created without the TRUSTED attribute; only superusers may install the extension or create PL/php functions.

[1.4] - 2010-07-12

Added

  • Support for PostgreSQL 8.4 and 9.0.
  • Support for PHP 5.3.

[1.3.5-beta1] - 2007-10-15

Added

  • Support for parameter names.
  • Support for PostgreSQL 8.3.

[1.3.3] - 2007-03-29

Added

  • bool type in return values.
  • Column names resembling numbers.

Changed

  • Map PHP E_STRICT to WARNING instead of ERROR.

Fixed

  • Several memory leaks.
  • Bugs in argument handling.

[1.3.2] - 2007-03-01

Changed

  • Link against the PHP embed SAPI instead of Apache’s mod_php, making the build robust against internal PHP changes.

Added

  • configure support for detecting required utilities and libraries.

[1.3.1] - 2006-12-01

Changed

  • Minor Makefile cleanups.

[1.2] - 2005-12-13

Added

  • Set-returning functions.
  • Support for PostgreSQL 8.0.

[1.1] - 2005-12-05

Supports PostgreSQL 8.0 and 8.1.

Added

  • A PGXS-based build that no longer requires the PHP or PostgreSQL sources.
  • Rudimentary SPI support for running queries and processing results.
  • Trigger support, including aborting/skipping an operation and modifying the tuple before insert/update.
  • Function validation at creation time (syntax errors are reported immediately).
  • Propagation of PHP errors and warnings to PostgreSQL.

Changed

  • Overhauled memory handling; SPI results are now opaque PHP resources, and a private symbol table is created and cleared per call.