# Changelog All notable changes to PL/php are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project aims to follow [Semantic Versioning](https://semver.org/). ## [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](https://pgxn.org/) 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.