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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,22 @@ and the project aims to follow [Semantic Versioning](https://semver.org/).
environment pointing into a dead stack frame; the next uncaught error then
jumped into garbage. Nested regression cases added.

## [Unreleased]

### 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).

## [2.3.0] — 2026-07-06

### Added
Expand Down
14 changes: 10 additions & 4 deletions doc/plphp.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,12 @@ function. In practice:
| arrays (e.g. `int[]`) | PHP array | PHP array |
| composite / row / record | associative array | associative array |

Array-typed *columns* inside rows — in `$_TD['new']`/`['old']`, rows from
`spi_fetch_row`/`spi_fetchrow`, and composite arguments' fields — also arrive
as PHP arrays, and can be assigned back as arrays (e.g. before a trigger
`MODIFY`).
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
plain PHP arrays).
| NULL | unset / null | `return;` or `null` |

Arrays map naturally, including multidimensional arrays:
Expand Down Expand Up @@ -150,6 +152,10 @@ SELECT * FROM make_tuple('answer', 42); -- answer | 42
Functions declared `RETURNS record` must be called with a column definition
list, e.g. `SELECT * FROM f() AS (a int, b text)`.

Nesting converts recursively in both directions: a composite containing
arrays or other composites arrives as nested PHP arrays, and can be returned
the same way.

## Arguments

PL/php supports the full range of argument modes.
Expand Down
111 changes: 111 additions & 0 deletions expected/arrays.out
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,114 @@ select arr_in_comp(row('x', array[5, 6, 7])::with_arr);
drop function arr_in_row(), arr_in_cursor(), arr_in_comp(with_arr), arr_trig();
drop type with_arr;
drop table arrt;
-- composite conversion: composite columns/fields/elements are associative
-- arrays in both directions
create type arr_addr as (street text, city text);
create type arr_person as (name text, home arr_addr, tags text[]);
create table arr_folks (id int, who arr_person);
insert into arr_folks values
(1, row('J''s "Place"', row('1 Main, Apt (2)', 'Olympia')::arr_addr,
array['a', 'b,c'])::arr_person);
-- composite column in a row: nested composite and array fields convert
create function arr_rec_peek() returns text language plphp as $$
$r = spi_exec("select * from arr_folks");
$row = spi_fetch_row($r);
$w = $row['who'];
return gettype($w) . " | " . $w['name'] . " | " . $w['home']['city']
. " | " . $w['tags'][1] . " | " . gettype($w['tags']);
$$;
select arr_rec_peek();
arr_rec_peek
---------------------------------------------
array | J's "Place" | Olympia | b,c | array
(1 row)

-- composite argument (with nesting)
create function arr_rec_arg(p arr_person) returns text language plphp as $$
return $args[0]['home']['street'] . " / " . count($args[0]['tags']);
$$;
select arr_rec_arg(row('X', row('5 Oak', 'Tacoma')::arr_addr, array['q'])::arr_person);
arr_rec_arg
-------------
5 Oak / 1
(1 row)

-- composites inside an array argument
create function arr_rec_elems(addrs arr_addr[]) returns text language plphp as $$
return $args[0][1]['city'];
$$;
select arr_rec_elems(array[row('a', 'CityA')::arr_addr, row('b', 'City,B()')::arr_addr]);
arr_rec_elems
---------------
City,B()
(1 row)

-- the reverse: build a nested composite from associative arrays
create function arr_rec_make() returns arr_person language plphp as $$
return array(
'name' => 'He said "hi"',
'home' => array('street' => '9 Elm, S(2)', 'city' => 'B\\C'),
'tags' => array('x', 'y z')
);
$$;
select * from arr_rec_make();
name | home | tags
--------------+------------------------+-----------
He said "hi" | ("9 Elm, S(2)","B\\C") | {x,"y z"}
(1 row)

select (arr_rec_make()).home.city, (arr_rec_make()).tags[2];
city | tags
------+------
B\C | y z
(1 row)

-- trigger MODIFY through a composite column
create function arr_rec_trig() returns trigger language plphp as $$
$_TD['new']['who']['home']['city'] = 'Changed';
return 'MODIFY';
$$;
create trigger arr_folks_trg before insert on arr_folks
for each row execute procedure arr_rec_trig();
insert into arr_folks values
(2, row('K', row('7 Pine', 'Old')::arr_addr, array['z'])::arr_person);
select (who).home.city from arr_folks where id = 2;
city
---------
Changed
(1 row)

drop trigger arr_folks_trg on arr_folks;
-- NULL fields inside composites survive both directions
create function arr_rec_nulls(p arr_addr) returns arr_addr language plphp as $$
pg_raise('notice', 'street is ' . ($args[0]['street'] === null ? 'null' : 'set'));
return array('street' => null, 'city' => $args[0]['city']);
$$;
select * from arr_rec_nulls(row(NULL, 'Salem')::arr_addr);
NOTICE: plphp: street is null
street | city
--------+-------
| Salem
(1 row)

drop function arr_rec_peek(), arr_rec_arg(arr_person), arr_rec_elems(arr_addr[]),
arr_rec_make(), arr_rec_trig(), arr_rec_nulls(arr_addr);
drop table arr_folks;
drop type arr_person, arr_addr;
-- anycompatible polymorphics (PostgreSQL 13+)
create function arr_first_compat(anycompatiblearray) returns anycompatible
language plphp as $$
return $args[0][0];
$$;
select arr_first_compat(array[7, 8, 9]);
arr_first_compat
------------------
7
(1 row)

select arr_first_compat(array['x', 'y']);
arr_first_compat
------------------
x
(1 row)

Loading
Loading