-- Coverage for previously-untested paths: start_proc failure, DML/utility -- SPI results, trigger arguments, cursor/transaction interactions, UTF-8, -- TOASTed values, PHP exception handling, DO runtime errors. -- start_proc failure: the first call of the session fails, but the session -- is already marked initialized, so after clearing the GUC things work. CREATE FUNCTION cov_ident(int) RETURNS int LANGUAGE plphp AS $$ return $args[0]; $$; SET plphp.start_proc = 'cov_no_such_fn'; SELECT cov_ident(1); ERROR: function cov_no_such_fn() does not exist 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 ----------- 1 (1 row) -- DML and utility statements through spi_exec: status and row counts CREATE FUNCTION cov_dml() RETURNS text LANGUAGE plphp AS $$ $out = array(); $r = spi_exec("create table cov_t (a int, b text)"); $out[] = spi_status($r); $r = spi_exec("insert into cov_t select g, 'row ' || g from generate_series(1, 3) g"); $out[] = spi_status($r) . ":" . spi_processed($r); $r = spi_exec("update cov_t set b = b || '!' where a < 3"); $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 $row = spi_fetch_row($r); $out[] = $row === false ? "no-fetch" : "fetched?"; return implode(" ", $out); $$; SELECT cov_dml(); WARNING: plphp: SPI status is not good cov_dml ------------------------------------------------------------------------- SPI_OK_UTILITY SPI_OK_INSERT:3 SPI_OK_UPDATE:2 SPI_OK_DELETE:1 no-fetch (1 row) SELECT * FROM cov_t ORDER BY a; a | b ---+-------- 2 | row 2! 3 | row 3 (2 rows) -- spi_rewind on a non-SELECT result is harmless CREATE FUNCTION cov_rewind_dml() RETURNS text LANGUAGE plphp AS $$ $r = spi_exec("insert into cov_t values (9, 'nine')"); spi_rewind($r); return spi_status($r) . ":" . spi_processed($r); $$; SELECT cov_rewind_dml(); cov_rewind_dml ----------------- SPI_OK_INSERT:1 (1 row) -- Trigger arguments: $_TD['args'] carries CREATE TRIGGER's literal arguments CREATE FUNCTION cov_trigargs() RETURNS trigger LANGUAGE plphp AS $$ pg_raise('notice', "trigger args: argc=" . $_TD['argc'] . " [" . implode(",", $_TD['args']) . "]"); return; $$; CREATE TRIGGER cov_trg BEFORE INSERT ON cov_t FOR EACH ROW EXECUTE PROCEDURE cov_trigargs('alpha', '42'); INSERT INTO cov_t VALUES (10, 'ten'); NOTICE: plphp: trigger args: argc=2 [alpha,42] DROP TRIGGER cov_trg ON cov_t; -- A cursor does not survive spi_commit: the portal dies with the -- transaction and further fetches just return false. CREATE PROCEDURE cov_cursor_commit() LANGUAGE plphp AS $$ $c = spi_query("select a from cov_t order by a"); $row = spi_fetchrow($c); pg_raise('notice', "before commit: a=" . $row['a']); spi_commit(); $row = spi_fetchrow($c); pg_raise('notice', "after commit: " . ($row === false ? "false" : "a row")); $$; CALL cov_cursor_commit(); NOTICE: plphp: before commit: a=2 NOTICE: plphp: after commit: false -- A cursor opened inside a rolled-back subtransaction() disappears with it CREATE FUNCTION cov_cursor_subxact() RETURNS text LANGUAGE plphp AS $$ $c = null; try { subtransaction(function() use (&$c) { $c = spi_query("select 1 as x"); throw new Exception("abort the subxact"); }); } catch (Exception $e) { pg_raise('notice', "caught: " . $e->getMessage()); } $row = spi_fetchrow($c); return $row === false ? "cursor gone" : "cursor survived"; $$; SELECT cov_cursor_subxact(); NOTICE: plphp: caught: abort the subxact cov_cursor_subxact -------------------- cursor gone (1 row) -- Multibyte UTF-8 round trip (strlen counts bytes) CREATE FUNCTION cov_utf8(text) RETURNS text LANGUAGE plphp AS $$ return $args[0] . " (" . strlen($args[0]) . " bytes)"; $$; SELECT cov_utf8('héllo wörld 日本語'); cov_utf8 ------------------------------- héllo wörld 日本語 (23 bytes) (1 row) -- A TOAST-sized value passes through intact CREATE FUNCTION cov_toast(text) RETURNS text LANGUAGE plphp AS $$ return strlen($args[0]) . ":" . md5($args[0]); $$; SELECT cov_toast(repeat('abcdefghij', 1000)); cov_toast ---------------------------------------- 10000:e2d23706a012bf2db2ff77c988a69178 (1 row) -- PHP exceptions are catchable in plain function code CREATE FUNCTION cov_catch() RETURNS text LANGUAGE plphp AS $$ try { throw new RuntimeException("boom"); } catch (RuntimeException $e) { return "caught " . $e->getMessage(); } $$; SELECT cov_catch(); cov_catch ------------- caught boom (1 row) -- 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 CREATE FUNCTION churn() RETURNS int LANGUAGE plphp AS $$ return 0; $$; SELECT churn(); churn ------- 0 (1 row) DO $$ for ($i = 1; $i <= 50; $i++) { spi_exec("create or replace function churn() returns int language plphp as 'return $i;'"); $r = spi_exec("select churn() as v"); $row = spi_fetch_row($r); if ($row['v'] != $i) pg_raise('error', "recompile returned {$row['v']}, expected $i"); } pg_raise('notice', 'redefined 50 times, all correct'); $$ LANGUAGE plphp; NOTICE: plphp: redefined 50 times, all correct -- ALTER also invalidates the cached compilation ALTER FUNCTION churn() COST 42; SELECT churn(); churn ------- 50 (1 row) -- 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 ------- 50 (1 row) CREATE OR REPLACE FUNCTION churn() RETURNS int LANGUAGE plphp AS $$ return 99; $$; SELECT churn(); churn ------- 99 (1 row) DROP FUNCTION churn();