Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@ and the project aims to follow [Semantic Versioning](https://semver.org/).
function cache used to briefly point at freed memory during recompilation,
which an unluckily timed statement cancel could have hit.

## [Unreleased]

### Added

- **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

- **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
Expand Down
8 changes: 8 additions & 0 deletions expected/base.out
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,20 @@ LANGUAGE plphp AS $$
$$;
SELECT test_a_bogus_array();
ERROR: function declared to return array must return an array
CONTEXT: PL/php function "test_a_bogus_array"
SELECT * FROM test_a_bogus_array();
ERROR: function declared to return array must return an array
CONTEXT: PL/php function "test_a_bogus_array"
CREATE FUNCTION test_a_bogus_int() RETURNS integer
LANGUAGE plphp AS $$
return array(1);
$$;
SELECT test_a_bogus_int();
ERROR: this plphp function cannot return arrays
CONTEXT: PL/php function "test_a_bogus_int"
SELECT * FROM test_a_bogus_int();
ERROR: this plphp function cannot return arrays
CONTEXT: PL/php function "test_a_bogus_int"
CREATE FUNCTION test_ndim_array(int, int) RETURNS int[]
LANGUAGE plphp AS $$
if (!function_exists('bar')) {
Expand Down Expand Up @@ -112,6 +116,7 @@ SELECT test_ndim_array(6, 1);

SELECT test_ndim_array(7, 1);
ERROR: number of array dimensions exceeds the maximum allowed (6)
CONTEXT: PL/php function "test_ndim_array"
CREATE FUNCTION php_max (integer, integer) RETURNS integer
STRICT LANGUAGE plphp AS $$
if ($args[0] > $args[1]) {
Expand Down Expand Up @@ -290,14 +295,17 @@ create function foo() returns record language plphp as $$
$$;
select foo();
ERROR: function declared to return tuple must return an array
CONTEXT: PL/php function "foo"
select * FROM foo() as (a int);
ERROR: function declared to return tuple must return an array
CONTEXT: PL/php function "foo"
create or replace function foo(anyelement) returns anyarray
language plphp as $$
return 1;
$$;
select foo(1);
ERROR: function declared to return array must return an array
CONTEXT: PL/php function "foo"
-- test recursive functions
create function php_fib(int) returns int language plphp as $$
if ($args[0] <= 1) { return 1; }
Expand Down
8 changes: 8 additions & 0 deletions expected/base_1.out
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,20 @@ LANGUAGE plphp AS $$
$$;
SELECT test_a_bogus_array();
ERROR: function declared to return array must return an array
CONTEXT: PL/php function "test_a_bogus_array"
SELECT * FROM test_a_bogus_array();
ERROR: function declared to return array must return an array
CONTEXT: PL/php function "test_a_bogus_array"
CREATE FUNCTION test_a_bogus_int() RETURNS integer
LANGUAGE plphp AS $$
return array(1);
$$;
SELECT test_a_bogus_int();
ERROR: this plphp function cannot return arrays
CONTEXT: PL/php function "test_a_bogus_int"
SELECT * FROM test_a_bogus_int();
ERROR: this plphp function cannot return arrays
CONTEXT: PL/php function "test_a_bogus_int"
CREATE FUNCTION test_ndim_array(int, int) RETURNS int[]
LANGUAGE plphp AS $$
if (!function_exists('bar')) {
Expand Down Expand Up @@ -112,6 +116,7 @@ SELECT test_ndim_array(6, 1);

SELECT test_ndim_array(7, 1);
ERROR: number of array dimensions (7) exceeds the maximum allowed (6)
CONTEXT: PL/php function "test_ndim_array"
CREATE FUNCTION php_max (integer, integer) RETURNS integer
STRICT LANGUAGE plphp AS $$
if ($args[0] > $args[1]) {
Expand Down Expand Up @@ -290,14 +295,17 @@ create function foo() returns record language plphp as $$
$$;
select foo();
ERROR: function declared to return tuple must return an array
CONTEXT: PL/php function "foo"
select * FROM foo() as (a int);
ERROR: function declared to return tuple must return an array
CONTEXT: PL/php function "foo"
create or replace function foo(anyelement) returns anyarray
language plphp as $$
return 1;
$$;
select foo(1);
ERROR: function declared to return array must return an array
CONTEXT: PL/php function "foo"
-- test recursive functions
create function php_fib(int) returns int language plphp as $$
if ($args[0] <= 1) { return 1; }
Expand Down
3 changes: 3 additions & 0 deletions expected/compat.out
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ NOTICE: plphp: DO via SPI: 42
-- A syntactically invalid DO block is rejected
DO $$ this is not php $$ LANGUAGE plphp;
ERROR: unable to compile inline code block: syntax error, unexpected identifier "is"
CONTEXT: PL/php anonymous code block
-- Prepared statements: prepare / exec_prepared / freeplan
CREATE FUNCTION prep_test(int) RETURNS int LANGUAGE plphp AS $$
$plan = spi_prepare('select $1 * 10 as v', 'int4');
Expand Down Expand Up @@ -111,6 +112,7 @@ CREATE FUNCTION elog_error() RETURNS void LANGUAGE plphp AS $$
$$;
SELECT elog_error();
ERROR: boom from elog at line 2
CONTEXT: PL/php function "elog_error"
-- A prepared statement with a NULL argument
CREATE FUNCTION prep_null() RETURNS text LANGUAGE plphp AS $$
$plan = spi_prepare('select $1::text as v', 'text');
Expand Down Expand Up @@ -155,3 +157,4 @@ CREATE FUNCTION elog_bad() RETURNS void LANGUAGE plphp AS $$
$$;
SELECT elog_bad();
ERROR: elog: unrecognized level "BOGUS" at line 2
CONTEXT: PL/php function "elog_bad"
3 changes: 3 additions & 0 deletions expected/coverage.out
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ LINE 1: SELECT cov_no_such_fn()
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
QUERY: SELECT cov_no_such_fn()
CONTEXT: PL/php function "cov_ident"
RESET plphp.start_proc;
SELECT cov_ident(1);
cov_ident
Expand Down Expand Up @@ -144,6 +145,7 @@ SELECT cov_catch();
-- A DO block can fail at runtime (not just at parse time)
DO $$ pg_raise('error', 'do block runtime error'); $$ LANGUAGE plphp;
ERROR: do block runtime error at line 1
CONTEXT: PL/php anonymous code block
DROP TABLE cov_t;
-- Redefinition churn: each CREATE OR REPLACE (and ALTER) discards the old
-- compiled function and its memory context and recompiles on next use
Expand Down Expand Up @@ -176,6 +178,7 @@ SELECT churn();
-- a body that fails to compile poisons the cache entry; fixing it recovers
CREATE OR REPLACE FUNCTION churn() RETURNS int LANGUAGE plphp AS $$ return $$;
ERROR: function "churn" does not validate: syntax error, unexpected token "}", expecting ";"
CONTEXT: compilation of PL/php function "churn"
SELECT churn();
churn
-------
Expand Down
1 change: 1 addition & 0 deletions expected/cursor.out
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ select test_cursor_error(1);

select test_cursor_error(0);
ERROR: division by zero at line 2
CONTEXT: PL/php function "test_cursor_error"
select test_cursor_error(1);
test_cursor_error
-------------------
Expand Down
1 change: 1 addition & 0 deletions expected/evttrig.out
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ CREATE EVENT TRIGGER guard ON ddl_command_start WHEN TAG IN ('CREATE TABLE')
EXECUTE FUNCTION no_create();
CREATE TABLE blocked (x int);
ERROR: no new tables allowed at line 2
CONTEXT: PL/php function "no_create"
DROP EVENT TRIGGER guard;
DROP FUNCTION no_create();
Empty file modified expected/modules.out
100755 → 100644
Empty file.
1 change: 1 addition & 0 deletions expected/oninit.out
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ $$;
SET plphp.on_init = 'throw new Exception("on_init boom");';
SELECT oninit_victim();
ERROR: plphp: on_init failed: on_init boom
CONTEXT: PL/php function "oninit_victim"
RESET plphp.on_init;
SELECT oninit_victim();
oninit_victim
Expand Down
1 change: 1 addition & 0 deletions expected/out.out
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ $$
$$ LANGUAGE plphp;
SELECT * FROM plphp.out_invalid();
ERROR: "long identifier" can not be used as a PHP variable name
CONTEXT: PL/php function "out_invalid"
-- INOUT parameters in procedures: a procedure's result is always a record
-- (even with one INOUT), returned via the same assignment convention
CREATE PROCEDURE proc_one(INOUT x int) LANGUAGE plphp AS $$
Expand Down
35 changes: 35 additions & 0 deletions expected/pgerror.out
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ CREATE FUNCTION err_uncaught() RETURNS void LANGUAGE plphp AS $$
$$;
SELECT err_uncaught();
ERROR: division by zero at line 2
CONTEXT: PL/php function "err_uncaught"
-- Errors surfacing mid-iteration from a cursor are catchable too
CREATE FUNCTION err_cursor() RETURNS text LANGUAGE plphp AS $$
$c = spi_query("select 1/(g-2) as x from generate_series(1,3) g");
Expand All @@ -108,4 +109,38 @@ SELECT err_cursor();
-1 then division by zero
(1 row)

-- An error crossing nested PL/php calls: propagates cleanly (this used to
-- crash the backend via a stale Zend bailout environment), is catchable in
-- the outer function, and the session stays healthy
CREATE FUNCTION err_inner() RETURNS int LANGUAGE plphp AS $$
spi_exec("select 1/0");
$$;
CREATE FUNCTION err_outer() RETURNS int LANGUAGE plphp AS $$
$r = spi_exec("select err_inner() as v");
return 1;
$$;
SELECT err_outer();
ERROR: division by zero at line 2
CONTEXT: PL/php function "err_outer"
SELECT 1 AS session_alive;
session_alive
---------------
1
(1 row)

CREATE FUNCTION err_outer_catch() RETURNS text LANGUAGE plphp AS $$
try {
spi_exec("select err_inner()");
} catch (PgError $e) {
return "caught nested: " . $e->getMessage();
}
return "not reached";
$$;
SELECT err_outer_catch();
err_outer_catch
-------------------------------------------
caught nested: division by zero at line 2
(1 row)

DROP FUNCTION err_inner(), err_outer(), err_outer_catch();
DROP TABLE errt;
4 changes: 4 additions & 0 deletions expected/raise.out
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@ insert into b values (1);
insert into b values (2);
insert into b values (3);
ERROR: value a=3 is not allowed at line 13
CONTEXT: PL/php function "trigfunc_ins"
insert into b values (4);
insert into b values (5);
ERROR: expected trigger function to return NULL, 'SKIP' or 'MODIFY'
CONTEXT: PL/php function "trigfunc_ins"
insert into b values (6);
ERROR: value a=6 is not allowed at line 13
CONTEXT: PL/php function "trigfunc_ins"
insert into b values (1);
insert into b values (3);
ERROR: value a=3 is not allowed at line 13
CONTEXT: PL/php function "trigfunc_ins"
insert into b values (2);
select * from b;
a
Expand Down
1 change: 1 addition & 0 deletions expected/shared.out
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ SELECT php_get_shared('fourth');

SELECT php_get_shared_ary('fourth');
ERROR: function declared to return array must return an array
CONTEXT: PL/php function "php_get_shared_ary"
-- A real array shared between calls
CREATE OR REPLACE FUNCTION php_set_shared_ary (text, text[]) RETURNS text AS $$
global $_SHARED;
Expand Down
1 change: 1 addition & 0 deletions expected/spi.out
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ NOTICE: plphp: got value 3.000000
-- oops, division by zero
select div_by_zero('{1,2,3}', 0);
ERROR: division by zero at line 7
CONTEXT: PL/php function "div_by_zero"
-- 1/2 + 2/2 + 3/2 = 3
select div_by_zero('{1,2,3}', 2);
NOTICE: plphp: got value 0.500000
Expand Down
1 change: 1 addition & 0 deletions expected/srf.out
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ language plphp as $$
$$;
select * from retset5(5, 2);
ERROR: cannot use return_next in functions not declared to return a set
CONTEXT: PL/php function "retset5"
-- Try to return setof array
create or replace function retset6(int, int) returns setof int[]
language plphp as $$
Expand Down
Empty file modified expected/subxact.out
100755 → 100644
Empty file.
8 changes: 8 additions & 0 deletions expected/trigger.out
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ $$;
-- Invalid attribute "a" in NEW
INSERT INTO trigfunc_test1 VALUES (1);
ERROR: $_TD['new'] does not contain attribute "b"
CONTEXT: PL/php function "trigfunc_test1"
-- Create NEW as the correct array, but with a NULL value
CREATE OR REPLACE FUNCTION trigfunc_test1() RETURNS trigger LANGUAGE plphp AS $$
$_TD['new'] = array('b' => NULL);
Expand All @@ -113,20 +114,23 @@ $$;
INSERT INTO trigfunc_test1 VALUES (1);
ERROR: insufficient number of keys in $_TD['new']
DETAIL: At least 1 expected, 0 found.
CONTEXT: PL/php function "trigfunc_test1"
-- Make "b" a literal of an incorrect type
CREATE OR REPLACE FUNCTION trigfunc_test1() RETURNS trigger LANGUAGE plphp AS $$
$_TD['new'] = array('b' => 'foobar');
return 'MODIFY';
$$;
INSERT INTO trigfunc_test1 VALUES (1);
ERROR: invalid input syntax for type integer: "foobar"
CONTEXT: PL/php function "trigfunc_test1"
-- Make "b" an array
CREATE OR REPLACE FUNCTION trigfunc_test1() RETURNS trigger LANGUAGE plphp AS $$
$_TD['new'] = array('b' => array(1, 2, 3));
return 'MODIFY';
$$;
INSERT INTO trigfunc_test1 VALUES (1);
ERROR: invalid input syntax for type integer: "{1,2,3}"
CONTEXT: PL/php function "trigfunc_test1"
-- What happens if we create a table whose columns are numbers?
-- Does PHP regular array handling work?
CREATE TABLE numbers ("0" int, "1" text, "2" float, "3" numeric[]);
Expand Down Expand Up @@ -186,20 +190,23 @@ create trigger foo before insert on foo
for each row execute procedure foo_trig();
insert into foo values (1, 'one');
ERROR: $_TD is not an array
CONTEXT: PL/php function "foo_trig"
create or replace function foo_trig() returns trigger
language plphp as $$
$_TD = array();
return 'MODIFY';
$$;
insert into foo values (1, 'one');
ERROR: $_TD['new'] not found
CONTEXT: PL/php function "foo_trig"
create or replace function foo_trig() returns trigger
language plphp as $$
$_TD = array('new' => 1);
return 'MODIFY';
$$;
insert into foo values (1, 'one');
ERROR: $_TD['new'] must be an array
CONTEXT: PL/php function "foo_trig"
create or replace function foo_trig() returns trigger
language plphp as $$
$_TD = array('new' => array());
Expand All @@ -208,3 +215,4 @@ $$;
insert into foo values (1, 'one');
ERROR: insufficient number of keys in $_TD['new']
DETAIL: At least 2 expected, 0 found.
CONTEXT: PL/php function "foo_trig"
Loading
Loading