-- array conversion hardening: NULL elements, escaping, unquoted text, -- numeric element types, empty arrays, bytea round-trips -- returning an array containing a PHP null produces a SQL NULL element create function arr_with_null() returns int[] language plphp as $$ return array(1, null, 3); $$; select arr_with_null(); -- text elements needing quoting/escaping survive the round trip out... create function arr_specials_out() returns text[] language plphp as $$ return array('plain', 'has"quote', 'back\\slash', 'com,ma', '{brace}', 'sp ace', ''); $$; select arr_specials_out(); -- ... and back in again (returns each element base64'd so the diff is unambiguous) create function arr_specials_in(text[]) returns text language plphp as $$ $out = array(); foreach ($args[0] as $el) $out[] = $el === null ? "NULL" : base64_encode($el); return implode(" ", $out); $$; select arr_specials_in(array['plain', 'has"quote', 'back\slash', 'com,ma', '{brace}', 'sp ace', '', null]); -- unquoted text elements (PG quotes only when needed) arrive as strings create function arr_unquoted(text[]) returns text language plphp as $$ return implode("|", $args[0]) . " (" . gettype($args[0][0]) . ")"; $$; select arr_unquoted(array['foo', 'bar', 'baz']); -- numeric arrays keep PHP numeric types create function arr_types(int[], float8[]) returns text language plphp as $$ return gettype($args[0][0]) . " " . gettype($args[1][0]); $$; select arr_types(array[1, 2], array[1.5, 2.5]); -- int array containing a NULL on input create function arr_null_in(int[]) returns int language plphp as $$ $sum = 0; $nulls = 0; foreach ($args[0] as $el) { if ($el === null) $nulls++; else $sum += $el; } return $sum * 10 + $nulls; $$; select arr_null_in(array[1, null, 3]); -- empty arrays, both directions create function arr_empty_out() returns int[] language plphp as $$ return array(); $$; select arr_empty_out(); create function arr_empty_in(int[]) returns int language plphp as $$ return count($args[0]); $$; select arr_empty_in('{}'); -- multi-dimensional array of strings needing quoting create function arr_ndim_text() returns text[] language plphp as $$ return array(array('a"b', 'c,d'), array('{e}', 'f\\g')); $$; select arr_ndim_text(); -- multi-dimensional input indexes correctly create function arr_ndim_in(text[]) returns text language plphp as $$ return $args[0][1][0]; $$; select arr_ndim_in(array[array['a', 'b'], array['see', 'd']]); -- bytea round-trip: PL/php sees the \x hex output form create function bytea_roundtrip(bytea) returns bytea language plphp as $$ return $args[0]; $$; select bytea_roundtrip('\x0001ff68656c6c6f'); create function bytea_make(text) returns bytea language plphp as $$ return "\\x" . bin2hex($args[0]); $$; select bytea_make('hi there'); 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; -- 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(); -- 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); -- 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]); -- 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(); select (arr_rec_make()).home.city, (arr_rec_make()).tags[2]; -- 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; 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); 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]); select arr_first_compat(array['x', 'y']);