diff --git a/CHANGELOG.md b/CHANGELOG.md index f53d6c0..9ab4552 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,13 @@ 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] +## [2.4.0] - 2026-07-06 ### 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 + 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. @@ -39,7 +39,7 @@ and the project aims to follow [Semantic Versioning](https://semver.org/). 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 + 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. @@ -55,16 +55,16 @@ and the project aims to follow [Semantic Versioning](https://semver.org/). (`doc/benchmarks.md`) comparing PL/php with PL/pgSQL and PL/Perl: within a few percent on scalar and string work, 1.75× PL/Perl on SPI row loops. -## [2.3.0] — 2026-07-06 +## [2.3.0] - 2026-07-06 ### Added -- **Continuous integration** — a GitHub Actions matrix building and running +- **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 a clear error). Previously any VARIADIC + unsupported and is rejected with an error). Previously any VARIADIC declaration failed. ### Changed @@ -86,7 +86,7 @@ and the project aims to follow [Semantic Versioning](https://semver.org/). several are installed (see INSTALL for a packaging caveat about shared SONAMEs). -## [2.2.0] — 2026-07-05 +## [2.2.0] - 2026-07-05 ### Added @@ -99,24 +99,24 @@ and the project aims to follow [Semantic Versioning](https://semver.org/). 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`. + 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 +- **`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 +- **`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_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`): practical recipes — `filter_var` +- **A tested cookbook** (`doc/cookbook.md`): `filter_var` CHECK constraints, bcrypt passwords, HMAC tokens, recursive JSON reshaping, - regex set-returning functions, a generic JSON-diff audit trigger, batch + 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. @@ -131,7 +131,7 @@ and the project aims to follow [Semantic Versioning](https://semver.org/). - **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 + 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 @@ -151,11 +151,11 @@ and the project aims to follow [Semantic Versioning](https://semver.org/). 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 +## [2.1.0] - 2026-07-05 ### Added -- **Cursor-streaming SPI** — `spi_query(query)` opens a cursor and returns its +- **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 @@ -170,7 +170,7 @@ and the project aims to follow [Semantic Versioning](https://semver.org/). result resource); code that relied on the alias should call `spi_exec_prepared` instead. -## [2.0.0] — 2026-07-01 +## [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** @@ -178,17 +178,17 @@ for current software. Tested on **PostgreSQL 11 through 18** with **PHP 8.3** ### Added -- **Anonymous `DO` blocks** — `DO $$ ... $$ LANGUAGE plphp`, via an inline +- **Anonymous `DO` blocks**: `DO $$ ... $$ LANGUAGE plphp`, via an inline handler. -- **Event trigger functions** — `RETURNS event_trigger`, with `$_TD['event']` +- **Event trigger functions**: `RETURNS event_trigger`, with `$_TD['event']` and `$_TD['tag']`. -- **Prepared statements** — `spi_prepare`, `spi_exec_prepared`, +- **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`. +- **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 +- **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. @@ -228,21 +228,21 @@ for current software. Tested on **PostgreSQL 11 through 18** with **PHP 8.3** the `TRUSTED` attribute; only superusers may install the extension or create PL/php functions. -## [1.4] — 2010-07-12 +## [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 +## [1.3.5-beta1] - 2007-10-15 ### Added - Support for parameter names. - Support for PostgreSQL 8.3. -## [1.3.3] — 2007-03-29 +## [1.3.3] - 2007-03-29 ### Added @@ -258,31 +258,31 @@ for current software. Tested on **PostgreSQL 11 through 18** with **PHP 8.3** - Several memory leaks. - Bugs in argument handling. -## [1.3.2] — 2007-03-01 +## [1.3.2] - 2007-03-01 ### Changed - Link against the PHP embed SAPI instead of Apache's `mod_php`, making the - build far more robust against internal PHP changes. + build robust against internal PHP changes. ### Added - `configure` support for detecting required utilities and libraries. -## [1.3.1] — 2006-12-01 +## [1.3.1] - 2006-12-01 ### Changed - Minor Makefile cleanups. -## [1.2] — 2005-12-13 +## [1.2] - 2005-12-13 ### Added - Set-returning functions. - Support for PostgreSQL 8.0. -## [1.1] — 2005-12-05 +## [1.1] - 2005-12-05 Supports PostgreSQL 8.0 and 8.1. diff --git a/Makefile b/Makefile index b932010..7350da8 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ MODULE_big = plphp OBJS = plphp.o plphp_io.o plphp_spi.o EXTENSION = plphp -DATA = plphp--2.3.sql plphp--2.0--2.1.sql plphp--2.1--2.2.sql plphp--2.2--2.3.sql +DATA = plphp--2.4.sql plphp--2.0--2.1.sql plphp--2.1--2.2.sql plphp--2.2--2.3.sql plphp--2.3--2.4.sql # PHP embed SAPI compile/link flags, discovered via php-config. PHP_CONFIG ?= php-config diff --git a/README.md b/README.md index 63a3e10..603e02d 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ PL/php is a procedural-language handler that lets you write database functions in **PHP**, stored and executed inside PostgreSQL. You get the convenience of PHP's -standard library with the full power of a native PostgreSQL function — plain +standard library as a native PostgreSQL function: plain functions, set-returning functions, triggers, event triggers, and procedures with transaction control. @@ -123,7 +123,7 @@ regression suite with `make installcheck`. > [!WARNING] > **PL/php is an untrusted language.** On modern PHP there is no sandbox > (`safe_mode` was removed in PHP 5.4), so a PL/php function can do anything the -> PostgreSQL server's operating-system user can — read and write files, open +> PostgreSQL server's operating-system user can: read and write files, open > network connections, run shell commands, and so on. The language is created **without** the `TRUSTED` attribute, so only superusers @@ -133,8 +133,8 @@ roles you would trust with the server's OS account. ## Documentation - [Language reference](doc/plphp.md) -- [Cookbook — tested recipes](doc/cookbook.md) -- [Benchmarks](doc/benchmarks.md) — within a few percent of PL/pgSQL on scalar work; 1.75× PL/Perl on row loops +- [Cookbook: tested recipes](doc/cookbook.md) +- [Benchmarks](doc/benchmarks.md): within a few percent of PL/pgSQL on scalar work; 1.75x PL/Perl on row loops - [Installation](INSTALL) - [Changelog](CHANGELOG.md) - [PL/php vs PL/Perl](doc/plperl-comparison.md) @@ -143,4 +143,4 @@ roles you would trust with the server's OS account. ## License PL/php is copyright © Command Prompt, Inc. and is distributed under a permissive, -PostgreSQL-style license — see the notice at the top of each source file. +PostgreSQL-style license; see the notice at the top of each source file. diff --git a/doc/benchmarks.md b/doc/benchmarks.md index 550bff9..0c6da39 100644 --- a/doc/benchmarks.md +++ b/doc/benchmarks.md @@ -30,7 +30,7 @@ warm session, Ubuntu 24.04 container on x86-64. Higher is better; treat - **Row iteration is PL/pgSQL's home turf.** Its `FOR ... IN SELECT` loop iterates natively without crossing a language boundary per row. PL/php pays a C-to-PHP conversion per row (one output-function call and one zval - per column) — and is still **1.75× faster than PL/Perl**, which + per column) and is still **1.75x faster than PL/Perl**, which materializes the entire result into Perl structures up front. - **Repeated SPI statements** carry a per-call subtransaction in both PL/php and PL/Perl (that is what makes their errors catchable); PL/pgSQL's @@ -43,12 +43,12 @@ per row) measured as a no-op: the row-loop cost lives in per-cell value conversion, not key handling. The optimization was dropped rather than carried as complexity without benefit. A future fast path worth exploring is converting common scalar types (int/float/bool/text) from their binary Datum -form instead of through the type output functions — that requires threading +form instead of through the type output functions; that requires threading type metadata into `spi_fetch_row`'s result handling. ## Guidance -- For pure computation, use whichever language reads best — the overhead +- For pure computation, use whichever language reads best; the overhead differences are negligible. - For tight loops over large results, prefer a set-based SQL statement (or PL/pgSQL) when the logic allows; when you need PHP's expressiveness per diff --git a/doc/cookbook.md b/doc/cookbook.md index 07e94e9..66f3333 100644 --- a/doc/cookbook.md +++ b/doc/cookbook.md @@ -2,7 +2,7 @@ Practical, self-contained recipes showing where PL/php earns its keep over `plpgsql`: PHP's standard library. Everything in the first section is run by -the `cookbook` regression test on every supported PostgreSQL version — if it's +the `cookbook` regression test on every supported PostgreSQL version: if it's on this page, it works. The embedded interpreter ships with `json`, `pcre`, `openssl`, `sodium`, @@ -54,7 +54,7 @@ is available too if libargon2 was compiled in. ### HMAC-signed tokens -Issue and verify tamper-proof tokens with a server-side secret — no extension +Issue and verify tamper-proof tokens with a server-side secret, no extension needed: ```sql @@ -80,7 +80,7 @@ otherwise, and uses constant-time `hash_equals`. ### Reshape JSON recursively -Deeply redact keys anywhere in a document — awkward with `jsonb` operators, +Deeply redact keys anywhere in a document. Awkward with `jsonb` operators, three lines of PHP: ```sql @@ -185,7 +185,7 @@ CALL process_queue(1000); ### Stream a big scan, stop early -A cursor (`spi_query`) reads rows one at a time in constant memory — close it +A cursor (`spi_query`) reads rows one at a time in constant memory. Close it as soon as you have your answer instead of materializing millions of rows: ```sql @@ -206,7 +206,7 @@ $$; ### Read a CSV file into rows -Server-side file ETL without `COPY`'s format restrictions — `fgetcsv` handles +Server-side file ETL without `COPY`'s format restrictions: `fgetcsv` handles quoting/escaping, and `RETURNS TABLE` + `return_next(array)` does the rest: ```sql @@ -257,10 +257,10 @@ SELECT gunz(gz(long_text)) = long_text FROM documents; -- true ``` (PostgreSQL TOAST already compresses transparently; this is for when you want -the compressed bytes explicitly — to hand to a client, or with `SET STORAGE +the compressed bytes explicitly, to hand to a client or use with `SET STORAGE EXTERNAL`.) -## Doc-only recipes (side effects — use with care) +## Doc-only recipes (side effects: use with care) These work but are deliberately not in the regression test: they touch the outside world, and they run **inside your transaction on the backend**. A slow @@ -270,7 +270,7 @@ write to a queue table instead and let a worker drain it. ### HTTP webhook from a trigger -No curl needed — PHP's HTTP stream wrapper does POSTs, and a short timeout is +No curl needed: PHP's HTTP stream wrapper does POSTs, and a short timeout is mandatory: ```sql @@ -300,7 +300,7 @@ Requires a working `sendmail` on the database server. ### Slugify with transliteration `iconv`'s `//TRANSLIT` is locale-dependent, so results can vary between -systems — good for display, not for keys you need to be stable everywhere: +systems: good for display, not for keys that must be stable everywhere: ```sql CREATE FUNCTION slugify(text) RETURNS text LANGUAGE plphp STRICT AS $$ diff --git a/doc/plperl-comparison.md b/doc/plperl-comparison.md index afaf321..d344d26 100644 --- a/doc/plperl-comparison.md +++ b/doc/plperl-comparison.md @@ -32,7 +32,7 @@ intentionally out of scope, with the rationale given. | `spi_cursor_close` | `spi_cursor_close` **added** | Abandon a cursor early | | errors trappable with `eval` | `try`/`catch (PgError $e)` **added** | PgError carries SQLSTATE, detail, and hint (richer than `$@`) | | SRF returns array reference | return an array from the SRF **added** | One element per row, as an alternative to `return_next` | -| — | `spi_each(query, callable)` **added** | Streaming per-row callback; PL/Perl has no equivalent | +| (no equivalent) | `spi_each(query, callable)` **added** | Streaming per-row callback; PL/Perl has no equivalent | | `elog(level, msg)` | `elog` **added** | DEBUG/LOG/INFO/NOTICE/WARNING/ERROR; `pg_raise` remains as the older spelling | | `spi_prepare` | `spi_prepare` **added** | Type names given as SQL type strings | | `spi_exec_prepared` | `spi_exec_prepared` **added** | | @@ -42,9 +42,9 @@ intentionally out of scope, with the rationale given. | `quote_nullable` | `quote_nullable` **added** | | | `quote_ident` | `quote_ident` **added** | | | `spi_commit` / `spi_rollback` | `spi_commit` / `spi_rollback` **added** | Transaction control in a procedure invoked by `CALL` (non-atomic) | -| `looks_like_number` | — | Use PHP's built-in `is_numeric()` | -| `encode_bytea` / `decode_bytea` | — | Use PHP's `bin2hex` / `hex2bin`, `base64_encode`, etc. | -| `encode_array_literal` / `encode_typed_literal` | — | Marginal; build literals with the quoting helpers | +| `looks_like_number` | (none) | Use PHP's built-in `is_numeric()` | +| `encode_bytea` / `decode_bytea` | (none) | Use PHP's `bin2hex` / `hex2bin`, `base64_encode`, etc. | +| `encode_array_literal` / `encode_typed_literal` | (none) | Marginal; build literals with the quoting helpers | ## Interpreter configuration @@ -52,14 +52,14 @@ intentionally out of scope, with the rationale given. |-----------------------|--------|-------| | `plperl.on_init` | `plphp.on_init` **added** | PHP source run at interpreter initialization | | (start proc, PL/Tcl-style) | `plphp.start_proc` | Runs a named PL/php function once per session | -| `plperl.use_strict` | — | No PHP equivalent; PHP is always "strict" about undefined functions | +| `plperl.use_strict` | (none) | No PHP equivalent; PHP is always "strict" about undefined functions | ## Transforms | PL/Perl | PL/php | Notes | |-----------------------|--------|-------| | `jsonb_plperl` | `jsonb_plphp` **added** | `TRANSFORM FOR TYPE jsonb`: native values in both directions | -| `bool_plperl` | — | PL/php already maps `t`/`f`; a transform would add little | +| `bool_plperl` | (none) | PL/php already maps `t`/`f`; a transform would add little | ## Intentionally not implemented diff --git a/doc/plphp.md b/doc/plphp.md index 17b8c7f..cedac4c 100644 --- a/doc/plphp.md +++ b/doc/plphp.md @@ -41,8 +41,8 @@ and only superusers can create PL/php functions. See [Security](#security). The body of a `LANGUAGE plphp` function is the body of a PHP function. Inside it you have: -- `$args` — a 0-indexed array of the call arguments. -- `$argc` — the number of declared arguments. +- `$args`: a 0-indexed array of the call arguments. +- `$argc`: the number of declared arguments. - Return a value with PHP's `return`. ```sql @@ -88,8 +88,8 @@ function. In practice: | arrays (e.g. `int[]`) | PHP array | PHP array | | composite / row / record | associative array | associative array | -Array- and composite-typed *columns* inside rows — in `$_TD['new']`/`['old']`, -rows from `spi_fetch_row`/`spi_fetchrow`, and composite arguments' fields — +Array- and composite-typed *columns* inside rows (in `$_TD['new']`/`['old']`, +rows from `spi_fetch_row`/`spi_fetchrow`, and composite arguments' fields) also convert structurally, all the way down: arrays become PHP arrays, composites become associative arrays, in both directions (so a trigger can `MODIFY` a nested composite field, and a function can return one built from @@ -109,7 +109,7 @@ SELECT php_an_array(); -- {{1,3,5},{2,4,6}} By default a `jsonb` value crosses the boundary as its text form. Install the companion extension and declare `TRANSFORM FOR TYPE jsonb` to work with -**native PHP values** instead — JSON objects/arrays become PHP arrays, numbers +**native PHP values** instead: JSON objects/arrays become PHP arrays, numbers become int/float, booleans and null map directly, in both directions: ```sql @@ -130,7 +130,7 @@ $$; Notes (shared with `jsonb_plperl`): returning PHP `null` yields SQL `NULL`, not jsonb `null`; an empty PHP array comes back as `[]` (PHP cannot -distinguish an empty list from an empty map — the same ambiguity +distinguish an empty list from an empty map, the same ambiguity `json_encode` has); JSON numbers arrive as PHP int when they fit, else float (precision beyond a double is lost). @@ -181,7 +181,7 @@ SELECT * FROM add_sub(10, 4); -- sum=14, diff=6 Named arguments must be valid PHP identifiers. -The same convention works for **INOUT parameters in procedures** — assign to +The same convention works for **INOUT parameters in procedures**: assign to the variable; `CALL` reports the resulting values (always as a row, even for a single INOUT parameter): @@ -215,7 +215,7 @@ array matching the result columns). `return_next()` with no argument emits a row built from the current OUT/TABLE variables. Alternatively, return the whole result set at once as an array with one -element per row (like PL/Perl's "return a reference to an array" form) — handy +element per row, like PL/Perl's "return a reference to an array" form. Use it when the rows are already collected in a variable: ```sql @@ -252,9 +252,9 @@ involved are available in the associative array `$_TD`: Return value of a **BEFORE ... FOR EACH ROW** trigger: -- `return;` (NULL) — proceed with the operation using the unmodified row. -- `return 'SKIP';` — silently skip the operation for this row. -- `return 'MODIFY';` — proceed using the (modified) `$_TD['new']` row. Modify +- `return;` (NULL): proceed with the operation using the unmodified row. +- `return 'SKIP';`: silently skip the operation for this row. +- `return 'MODIFY';`: proceed using the (modified) `$_TD['new']` row. Modify fields in place, e.g. `$_TD['new']['col'] = 'value';`. ```sql @@ -289,14 +289,14 @@ The return value of an event trigger function is ignored. Run queries against the current database from within a function: -- `spi_exec(query [, limit])` — execute `query` (optionally limiting rows) and +- `spi_exec(query [, limit])`: execute `query` (optionally limiting rows) and return a result resource. The call runs in a subtransaction that is rolled back automatically if the query raises an error. -- `spi_fetch_row(result)` — return the next row as an associative array, or +- `spi_fetch_row(result)`: return the next row as an associative array, or `false` when the rows are exhausted. -- `spi_processed(result)` — number of rows the query produced. -- `spi_status(result)` — the SPI status code as a string. -- `spi_rewind(result)` — restart iteration from the first row. +- `spi_processed(result)`: number of rows the query produced. +- `spi_status(result)`: the SPI status code as a string. +- `spi_rewind(result)`: restart iteration from the first row. ```sql CREATE FUNCTION sum_series(n integer) RETURNS integer LANGUAGE plphp AS $$ @@ -314,14 +314,14 @@ $$; first row. For result sets too large for that, open a cursor instead and fetch rows one at a time: -- `spi_query(query)` — open a cursor for `query` and return its name (a +- `spi_query(query)`: open a cursor for `query` and return its name (a string). -- `spi_fetchrow(cursor)` — return the next row as an associative array, or +- `spi_fetchrow(cursor)`: return the next row as an associative array, or `false` when the cursor is exhausted (the cursor is then closed automatically). -- `spi_cursor_close(cursor)` — close a cursor early, before exhausting it. +- `spi_cursor_close(cursor)`: close a cursor early, before exhausting it. Closing an unknown or already-closed cursor is harmless. -- `spi_each(query, callable)` — run `query` and invoke `callable($row)` once +- `spi_each(query, callable)`: run `query` and invoke `callable($row)` once per row, streaming over a cursor; return `false` from the callable to stop early. Returns the number of rows processed. The inline-loop equivalent of PL/Tcl's `spi_exec -array a $query { body }`. @@ -348,15 +348,15 @@ For queries you run repeatedly, prepare a plan once and execute it with parameters. `spi_prepare` takes the query text followed by the SQL type name of each `$1`, `$2`, ... placeholder and returns a plan resource: -- `spi_prepare(query, type1, type2, ...)` — returns a plan. -- `spi_exec_prepared(plan, arg1, arg2, ...)` — execute the plan; returns a +- `spi_prepare(query, type1, type2, ...)`: returns a plan. +- `spi_exec_prepared(plan, arg1, arg2, ...)`: execute the plan; returns a result resource just like `spi_exec` (use `spi_fetch_row`, `spi_processed`, etc.). -- `spi_query_prepared(plan, arg1, arg2, ...)` — open a *cursor* for the plan +- `spi_query_prepared(plan, arg1, arg2, ...)`: open a *cursor* for the plan and return its name; fetch rows with `spi_fetchrow` (see [Streaming large result sets](#streaming-large-result-sets-cursors)). Before PL/php 2.1 this was an alias of `spi_exec_prepared`. -- `spi_freeplan(plan)` — release the plan when you are done with it. +- `spi_freeplan(plan)`: release the plan when you are done with it. ```sql CREATE FUNCTION lookup(int) RETURNS text LANGUAGE plphp AS $$ @@ -376,8 +376,8 @@ better performance; free it with `spi_freeplan` when no longer needed. Inside a **procedure** invoked by `CALL` in a non-atomic context, you can commit or roll back the current transaction: -- `spi_commit()` — commit the current transaction and begin a new one. -- `spi_rollback()` — roll back the current transaction and begin a new one. +- `spi_commit()`: commit the current transaction and begin a new one. +- `spi_rollback()`: roll back the current transaction and begin a new one. ```sql CREATE PROCEDURE import_batch() LANGUAGE plphp AS $$ @@ -420,7 +420,7 @@ A *database* error inside the block (for example a constraint violation surfaced by `spi_exec`) likewise rolls the subtransaction back and propagates as a catchable [`PgError`](#errors-and-exceptions). Note that each SPI call already runs in its own subtransaction, so `try`/`catch` around a single call -does not need `subtransaction()` — use it to make a *group* of statements +does not need `subtransaction()`; use it to make a *group* of statements succeed or fail atomically. ## Quoting helpers @@ -428,10 +428,10 @@ succeed or fail atomically. When building SQL dynamically, quote values and identifiers so the result is safe and syntactically correct: -- `quote_literal(string)` — quote a value as an SQL string literal. -- `quote_nullable(value)` — like `quote_literal`, but a PHP `null` becomes the +- `quote_literal(string)`: quote a value as an SQL string literal. +- `quote_nullable(value)`: like `quote_literal`, but a PHP `null` becomes the SQL keyword `NULL`. -- `quote_ident(name)` — quote a string for use as an SQL identifier (only when +- `quote_ident(name)`: quote a string for use as an SQL identifier (only when needed). ```php @@ -481,7 +481,7 @@ session. **Modules.** If a table named `plphp_modules(modname text, modseq int, modsrc text)` exists, its rows are loaded (ordered by `modname`, `modseq`) when the interpreter initializes. Any functions or classes the code defines become -available to every PL/php function in the session — a convenient place for a +available to every PL/php function in the session, a place for a shared library of helpers: ```sql @@ -501,7 +501,7 @@ SET plphp.start_proc = 'my_setup'; ``` **on_init.** The `plphp.on_init` setting holds a snippet of *PHP source* to -execute at initialization — the counterpart of `plperl.on_init`. Use it for +execute at initialization, the counterpart of `plperl.on_init`. Use it for setup that doesn't warrant a modules table, such as defining a helper or setting an include path: @@ -519,10 +519,10 @@ an invalid body is rejected with the PHP parse error. **Database errors are catchable.** Every database error raised by an SPI call (`spi_exec`, `spi_fetchrow`, `spi_commit`, ...) is thrown as a **`PgError`** -exception — the counterpart of PL/Perl's `eval`-trappable errors and PL/Tcl's +exception, the counterpart of PL/Perl's `eval`-trappable errors and PL/Tcl's `catch`. The failed call's subtransaction has already been rolled back when you catch it, so the session is in a consistent state and the function can -simply continue: +continue: ```sql CREATE FUNCTION upsertish(int) RETURNS text LANGUAGE plphp AS $$ @@ -541,12 +541,12 @@ $$; `PgError extends Exception` and adds: -- `getSQLState()` — the five-character SQLSTATE code (e.g. `23505`). -- `getDetail()` / `getHint()` — the error's DETAIL and HINT, or `null`. +- `getSQLState()`: the five-character SQLSTATE code (e.g. `23505`). +- `getDetail()` / `getHint()`: the error's DETAIL and HINT, or `null`. `pg_raise('error', ...)` and `elog('ERROR', ...)` also throw a `PgError` -(SQLSTATE `P0001`, like PL/pgSQL's `RAISE`). An **uncaught** `PgError` — or -any other uncaught PHP exception, such as a `TypeError` — is reported as a +(SQLSTATE `P0001`, like PL/pgSQL's `RAISE`). An **uncaught** `PgError`, or +any other uncaught PHP exception such as a `TypeError`, is reported as a PostgreSQL `ERROR`, aborting the statement. PHP deprecation notices (such as the legacy `"${var}"` string interpolation) are surfaced as PostgreSQL `NOTICE`s and are not fatal. @@ -556,7 +556,7 @@ the legacy `"${var}"` string interpolation) are surfaced as PostgreSQL PL/php is an **untrusted** language. Historically the trusted variant restricted user code using PHP's `safe_mode`, but **`safe_mode` was removed in PHP 5.4**, so on modern PHP nothing sandboxes a PL/php function: it can do whatever the -PostgreSQL server's operating-system user can do — read and write files, open +PostgreSQL server's operating-system user can do: read and write files, open network connections, run shell commands, and so on. Accordingly, the language is created without the `TRUSTED` attribute: the diff --git a/doc/pltcl-comparison.md b/doc/pltcl-comparison.md index 15d7876..9eaf199 100644 --- a/doc/pltcl-comparison.md +++ b/doc/pltcl-comparison.md @@ -32,7 +32,7 @@ its rationale. | `elog level msg` | `elog(level, msg)` | DEBUG/LOG/INFO/NOTICE/WARNING/ERROR | | `subtransaction { body }` | `subtransaction(callable, …)` **added** | | | `commit` / `rollback` | `spi_commit` / `spi_rollback` | | -| — (no transform support) | `jsonb_plphp` **added** | `TRANSFORM FOR TYPE jsonb`; PL/Tcl has no transforms at all | +| (no transform support) | `jsonb_plphp` **added** | `TRANSFORM FOR TYPE jsonb`; PL/Tcl has no transforms at all | ## The one remaining gap diff --git a/expected/coverage.out b/expected/coverage.out index b459864..7718c9f 100644 --- a/expected/coverage.out +++ b/expected/coverage.out @@ -32,7 +32,7 @@ CREATE FUNCTION cov_dml() RETURNS text LANGUAGE plphp AS $$ $out[] = spi_status($r) . ":" . spi_processed($r); $r = spi_exec("delete from cov_t where a = 1"); $out[] = spi_status($r) . ":" . spi_processed($r); - // fetching from a non-SELECT result is refused politely + // fetching from a non-SELECT result is refused $row = spi_fetch_row($r); $out[] = $row === false ? "no-fetch" : "fetched?"; return implode(" ", $out); diff --git a/plphp--2.3--2.4.sql b/plphp--2.3--2.4.sql new file mode 100644 index 0000000..3fcc477 --- /dev/null +++ b/plphp--2.3--2.4.sql @@ -0,0 +1,11 @@ +/* Upgrade PL/php from 2.3 to 2.4. + * + * 2.4 adds per-function memory contexts, error CONTEXT lines, structural + * composite conversion, the anycompatible polymorphics, a fix for a crash + * in nested error propagation, an ASAN CI job, and a benchmark suite. All + * of it lives in the C module or the repository, so there is nothing to + * change at the SQL level. + */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION plphp UPDATE" to load this file. \quit diff --git a/plphp--2.3.sql b/plphp--2.4.sql similarity index 100% rename from plphp--2.3.sql rename to plphp--2.4.sql diff --git a/plphp.control b/plphp.control index 1467efc..c9e2c14 100644 --- a/plphp.control +++ b/plphp.control @@ -5,6 +5,6 @@ # the server's OS user. The extension is therefore superuser-only to install # and the language is created without the TRUSTED attribute. comment = 'PL/php procedural language (untrusted)' -default_version = '2.3' +default_version = '2.4' module_pathname = '$libdir/plphp' relocatable = false diff --git a/sql/coverage.sql b/sql/coverage.sql index a4c1946..955d501 100644 --- a/sql/coverage.sql +++ b/sql/coverage.sql @@ -23,7 +23,7 @@ CREATE FUNCTION cov_dml() RETURNS text LANGUAGE plphp AS $$ $out[] = spi_status($r) . ":" . spi_processed($r); $r = spi_exec("delete from cov_t where a = 1"); $out[] = spi_status($r) . ":" . spi_processed($r); - // fetching from a non-SELECT result is refused politely + // fetching from a non-SELECT result is refused $row = spi_fetch_row($r); $out[] = $row === false ? "no-fetch" : "fetched?"; return implode(" ", $out);