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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ and the project aims to follow [Semantic Versioning](https://semver.org/).
unsupported and is rejected with a clear 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
Expand Down
5 changes: 5 additions & 0 deletions doc/plphp.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ function. In practice:
| boolean | `"t"` / `"f"` string | `true` / `false`, or `"t"`/`"f"` |
| 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`).
| NULL | unset / null | `return;` or `null` |

Arrays map naturally, including multidimensional arrays:
Expand Down
59 changes: 59 additions & 0 deletions expected/arrays.out
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,62 @@ select bytea_read('\xdeadbeef');
DEADBEEF
(1 row)

-- array-typed columns inside rows arrive as PHP arrays (not "{...}" strings):
-- via SPI rows...
create table arrt (id int, tags text[], nums int[]);
insert into arrt values (1, array['red', 'b"lue'], array[10, 20, 30]);
create function arr_in_row() returns text language plphp as $$
$r = spi_exec("select * from arrt");
$row = spi_fetch_row($r);
return gettype($row['tags']) . " " . $row['tags'][1] . " " . array_sum($row['nums']);
$$;
select arr_in_row();
arr_in_row
----------------
array b"lue 60
(1 row)

-- ...via cursor rows...
create function arr_in_cursor() returns int language plphp as $$
$c = spi_query("select nums from arrt");
$row = spi_fetchrow($c);
return count($row['nums']);
$$;
select arr_in_cursor();
arr_in_cursor
---------------
3
(1 row)

-- ...in $_TD for triggers, and writable back through MODIFY
create function arr_trig() returns trigger language plphp as $$
pg_raise('notice', 'tags is ' . gettype($_TD['new']['tags'])
. ' with ' . count($_TD['new']['tags']) . ' elements');
$_TD['new']['tags'][] = 'added';
return 'MODIFY';
$$;
create trigger arrt_trg before insert on arrt
for each row execute procedure arr_trig();
insert into arrt values (2, array['green'], array[1]);
NOTICE: plphp: tags is array with 1 elements
select tags from arrt where id = 2;
tags
---------------
{green,added}
(1 row)

drop trigger arrt_trg on arrt;
-- ...and in composite-type arguments
create type with_arr as (label text, vals int[]);
create function arr_in_comp(with_arr) returns int language plphp as $$
return array_sum($args[0]['vals']);
$$;
select arr_in_comp(row('x', array[5, 6, 7])::with_arr);
arr_in_comp
-------------
18
(1 row)

drop function arr_in_row(), arr_in_cursor(), arr_in_comp(with_arr), arr_trig();
drop type with_arr;
drop table arrt;
25 changes: 22 additions & 3 deletions plphp_io.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,26 @@
#include "utils/syscache.h"
#include "utils/memutils.h"

/*
* plphp_add_row_value
* Add one column's text value to a row hash under attname, converting
* array-typed columns to PHP arrays (like the argument conversion
* does) rather than leaving them as literal "{...}" strings.
*/
static void
plphp_add_row_value(zval *row, char *attname, Oid atttypid, char *value)
{
if (OidIsValid(get_element_type(atttypid)))
{
zval *arr = plphp_convert_from_pg_array(value);

add_assoc_zval(row, attname, arr);
efree(arr);
}
else
add_assoc_string(row, attname, value);
}

/*
* plphp_zval_from_tuple
* Build a PHP hash from a tuple.
Expand All @@ -46,8 +66,7 @@ plphp_zval_from_tuple(HeapTuple tuple, TupleDesc tupdesc)
/* get its value */
if ((attdata = SPI_getvalue(tuple, tupdesc, i + 1)) != NULL)
{
/* add_assoc_string copies the string in PHP 7+ */
add_assoc_string(array, attname, attdata);
plphp_add_row_value(array, attname, att->atttypid, attdata);
pfree(attdata);
}
else
Expand Down Expand Up @@ -573,7 +592,7 @@ plphp_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc)

/* Append the attribute name and the value to the list. */
outputstr = OidOutputFunctionCall(typoutput, attr_datum);
add_assoc_string(output, attname, outputstr);
plphp_add_row_value(output, attname, att->atttypid, outputstr);
pfree(outputstr);
}

Expand Down
43 changes: 43 additions & 0 deletions sql/arrays.sql
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,46 @@ create function bytea_read(bytea) returns text language plphp as $$
return strtoupper(substr($args[0], 2));
$$;
select bytea_read('\xdeadbeef');

-- array-typed columns inside rows arrive as PHP arrays (not "{...}" strings):
-- via SPI rows...
create table arrt (id int, tags text[], nums int[]);
insert into arrt values (1, array['red', 'b"lue'], array[10, 20, 30]);
create function arr_in_row() returns text language plphp as $$
$r = spi_exec("select * from arrt");
$row = spi_fetch_row($r);
return gettype($row['tags']) . " " . $row['tags'][1] . " " . array_sum($row['nums']);
$$;
select arr_in_row();

-- ...via cursor rows...
create function arr_in_cursor() returns int language plphp as $$
$c = spi_query("select nums from arrt");
$row = spi_fetchrow($c);
return count($row['nums']);
$$;
select arr_in_cursor();

-- ...in $_TD for triggers, and writable back through MODIFY
create function arr_trig() returns trigger language plphp as $$
pg_raise('notice', 'tags is ' . gettype($_TD['new']['tags'])
. ' with ' . count($_TD['new']['tags']) . ' elements');
$_TD['new']['tags'][] = 'added';
return 'MODIFY';
$$;
create trigger arrt_trg before insert on arrt
for each row execute procedure arr_trig();
insert into arrt values (2, array['green'], array[1]);
select tags from arrt where id = 2;
drop trigger arrt_trg on arrt;

-- ...and in composite-type arguments
create type with_arr as (label text, vals int[]);
create function arr_in_comp(with_arr) returns int language plphp as $$
return array_sum($args[0]['vals']);
$$;
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;
Loading