How to execute PostgreSQL RAISE command dynamically - postgresql

How to raise error from PostgreSQL SQL statement if some condition is met?
I tried code below but got error.
CREATE OR REPLACE FUNCTION "exec"(text)
RETURNS text AS
$BODY$
BEGIN
EXECUTE $1;
RETURN $1;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
-- ERROR: syntax error at or near "raise"
-- LINE 1: raise 'test'
SELECT exec('raise ''test'' ') WHERE TRUE
In real application TRUE is replaced by some condition.
Update
I tried to extend answer to pass exception message parameters.
Tried code below but got syntax error.
How to pass message parameters ?
CREATE OR REPLACE FUNCTION exec(text, variadic )
RETURNS void LANGUAGE plpgsql AS
$BODY$
BEGIN
RAISE EXCEPTION $1, $2;
END;
$BODY$;
SELECT exec('Exception Param1=% Param2=%', 'param1', 2 );

You cannot call RAISE dynamically (with EXECUTE) in PL/pgSQL - that only works for SQL statements, and RAISE is a PL/pgSQL command.
Use this simple function instead:
CREATE OR REPLACE FUNCTION f_raise(text)
RETURNS void
LANGUAGE plpgsql AS
$func$
BEGIN
RAISE EXCEPTION '%', $1;
END
$func$;
Call:
SELECT f_raise('My message is empty!');
Related:
Generate an exception with a Context
Additional answer to comment
CREATE OR REPLACE FUNCTION f_raise1(VARIADIC text[])
RETURNS void
LANGUAGE plpgsql AS
$func$
BEGIN
RAISE EXCEPTION 'Reading % % %!', $1[1], $1[2], $1[3];
END
$func$;
Call:
SELECT f_raise1('the','manual','educates');
VARIADIC is not a data type, but an argument mode.
Elements have to be handled like any other array element.
To use multiple variables in a RAISE statement, put multiple % into the message text.
The above example will fail if no $3 is passed. You'd have to assemble a string from the variable number of input elements. Example:
CREATE OR REPLACE FUNCTION f_raise2(VARIADIC _arr text[])
RETURNS void
LANGUAGE plpgsql AS
$func$
DECLARE
_msg text := array_to_string(_arr, ' and '); -- simple string construction
BEGIN
RAISE EXCEPTION 'Reading %!', _msg;
END
$func$;
Call:
SELECT f_raise2('the','manual','educates');
I doubt you need a VARIADIC parameter for this at all. Read the manual here.
Instead, define all parameters, maybe add defaults:
CREATE OR REPLACE FUNCTION f_raise3(_param1 text = ''
, _param2 text = ''
, _param3 text = 'educates')
RETURNS void
LANGUAGE plpgsql AS
$func$
BEGIN
RAISE EXCEPTION 'Reading % % %!', $1, $2, $3;
END
$func$;
Call:
SELECT f_raise3('the','manual','educates');
Or:
SELECT f_raise3(); -- defaults kick in

Related

How to return Select * INTO STRICT variable from function

I am trying to return a single row of a table in a function and it is important that if there isn't any row or more then one there is an exception raised. I am using SELECT * INTO STRICT for that reason. However, I can't seem to find the correct way to return. Was wondering if someone knew the correct way of returning the single row?
I know I can separate the check and get it work but was curious if there was a way to get this to work.
CREATE OR REPLACE FUNCTION GameInfo.getAllPlayerInfo(
playerID GameInfo.Player.PID%Type)
RETURNS TABLE (PID VARCHAR,Name VARCHAR, Email VARCHAR,Password VARCHAR) AS
$$
DECLARE
found_player GameInfo.Player%ROWTYPE;
BEGIN
SELECT * INTO STRICT found_player FROM GameInfo.Player WHERE Player.PID =
$1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE EXCEPTION 'PID % not found';
WHEN TOO_MANY_ROWS THEN
RAISE EXCEPTION 'PID % not unique';
RETURN found_player;
END;
$$ LANGUAGE plpgsql
STABLE
SECURITY DEFINER;
Your function is declared as returns table, so in PL/pgSQL you need to use return next... to return a row.
That return statements needs to be moved before the exception handling. Currently your function does not return anything, if a row is found.
You can also simplify the function declaration by using returns setof so you don't need to specify all columns.
CREATE OR REPLACE FUNCTION GameInfo.getAllPlayerInfo(playerID GameInfo.Player.PID%Type)
RETURNS setof gameinfo.player
AS $$
DECLARE
found_player GameInfo.Player%ROWTYPE;
BEGIN
SELECT *
INTO STRICT found_player
FROM GameInfo.Player
WHERE Player.PID = playerid;
RETURN NEXT found_player; --<< here
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE EXCEPTION 'PID % not found';
WHEN TOO_MANY_ROWS THEN
RAISE EXCEPTION 'PID % not unique';
END;
$$
LANGUAGE plpgsql
STABLE
SECURITY DEFINER;
Note that set returning functions need to be used in the FROM clause:
select *
from gameinfo.getallplayerinfo(1);

How to access outer scope variables from a function in PostgreSQL?

I have this code:
DO $$
DECLARE
NODE_ID bigint := 46;
BEGIN
CREATE OR REPLACE FUNCTION funk(VAL bigint)
RETURNS bigint AS $f$
BEGIN
RETURN VAL;
END; $f$ LANGUAGE plpgsql;
RAISE NOTICE '%', funk(NODE_ID);
END $$;
I works as expected and prints 46 to the console.
I want to get rid of the parameters, because the variable is global. But I am getting errors:
DO $$
DECLARE
NODE_ID bigint := 46;
BEGIN
CREATE OR REPLACE FUNCTION funk()
RETURNS bigint AS $f$
BEGIN
RETURN NODE_ID;
END; $f$ LANGUAGE plpgsql;
RAISE NOTICE '%', funk();
END $$;
I'm getting "NODE_ID not exist". Is there a way to access the outer variable in the function?
No, that won't work, because the function has no connection to your DO block whatsoever. It is a persistent database object that will continue to exist in the database after the DO block has finished.
In essence, a function is just a string with the function body (and some metadata, see pg_proc); in this case, the function body consists of the text between the opening and the closing $f$. It is interpreted by the language handler when the function is run.
The only database data you can reference in a function are other persistent database objects, and a variable in a DO block isn't one of those.
There are no global variables in PostgreSQL except for – in a way – the configuration parameters. You can access these with the SET and SHOW SQL commands and, more conveniently in code, with the set_config and current_setting functions.
Or use dynamic SQL:
DO $$
DECLARE
NODE_ID bigint := 46;
src text := format('
CREATE OR REPLACE FUNCTION funk()
RETURNS bigint AS $f$
BEGIN
RETURN %s;
END;
$f$ LANGUAGE plpgsql;
', NODE_ID::text);
BEGIN
execute src;
RAISE NOTICE '%', funk();
END $$;
(works for me, landing on your question searching for solution of same problem)

Where the NOTICE or error messages?

No NOTICE neither error messages at
CREATE or replace FUNCTION copy_to_csv(
fname text,
query text,
header boolean DEFAULT true,
quotedfields text[] DEFAULT NULL,
usedate boolean DEFAULT true
) RETURNS text AS $f$
DECLARE
aux text :='';
BEGIN
RAISE NOTICE 'HELLO!!!!!';
IF p_quotedfields IS NOT NULL THEN
aux := ', FORCE_QUOTE('|| array_to_string(quote_ident(quotedfields),',') ||')';
END IF;
aux := format(
'COPY (%L) TO (%L) WITH (FORMAT CSV, HEADER %L%s)',
query,
CASE WHEN usedate THEN fname|| now()::date::text ELSE fname END ||'.csv',
header,
aux
);
RAISE NOTICE 'HELLO2';
EXECUTE aux;
RAISE NOTICE 'HELLO3';
RETURN aux;
END;
$f$ LANGUAGE plpgsql STRICT;
... Calling with select copy_to_csv(E'select * from t', '/tmp/t');. Using PostgreSQL v10 at UBUNTU 16 LTS.
But this function is working fine:
CREATE or replace FUNCTION test1() RETURNS void AS $f$
BEGIN
RAISE NOTICE 'HELLO!!!';
END;
$f$ LANGUAGE plpgsql STRICT;
PS: the quote_ident() overload also working fine, was implemented with
CREATE FUNCTION quote_ident(text[]) RETURNS text[] AS $f$
SELECT array_agg(quote_ident(x)) FROM unnest($1) t(x)
$f$ LANGUAGE SQL IMMUTABLE;
When the function is STRICT and one of the arguments is NULL, the function body is not executed and the result is NULL. Remove STRICT from the function definition.
Btw, you've mistaken the order of arguments.

'ERROR: syntax error at or near "AS"' for a PL/pgSQL function with FOR LOOP

I am getting an error for the below FOR loop with cursor in a function:
ERROR: syntax error at or near "AS"
CREATE OR REPLACE FUNCTION functionName(custom varchar(15)) RETURNS INTEGER AS $$
DECLARE
...
BEGIN
...
FOR loop AS cursor CURSOR FOR
SELECT column FROM table
DO
...
END FOR;
RETURN someValue;
END;
$$
LANGUAGE plpgsql;
This is wrong syntax - Postgres doesn't support declaration of CURSOR inside FOR statement. See documentation:
CREATE OR REPLACE FUNCTION foo()
RETURNS void AS $$
DECLARE r record;
BEGIN
FOR r IN SELECT xx,yy FROM some_tab
LOOP
RAISE NOTICE 'row data: %', r;
END LOOP;
END;
$$ LANGUAGE plpgsql;
It looks so you are using ANSI SQL PSM syntax. PL/pgSQL is based on PL/SQL syntax (Oracle/ADA).

Syntax error at or near "FOR" while using "execute format" in a function

This function compiled successfully:
CREATE OR REPLACE FUNCTION FieldValidations1(tbl_name varchar(35),col_name varchar(25), error_flag varchar(3))
RETURNS void AS $$
declare
cust_rec RECORD;
BEGIN
execute format($sel$
FOR cust_rec IN SELECT %I FROM %s
LOOP
RAISE NOTICE 'inside loop';
END LOOP;
$sel$,
col_name,tbl_name);
END;
$$ LANGUAGE plpgsql;
But while calling the function,
select FieldValidations1('raw_tbl1','col1','gg');
the error appears like this
ERROR: syntax error at or near "FOR"
LINE 3: FOR cust_rec IN SELECT col1 FROM raw_tbl1
^
QUERY:
FOR cust_rec IN SELECT col1 FROM raw_tbl1
LOOP
RAISE NOTICE 'inside loop';
END LOOP;
CONTEXT: PL/pgSQL function "fieldvalidations1" line 6 at EXECUTE statement
Can anyone explain what's wrong?
You must be aware that plpgsql functions are only tested on a superficial syntactical level in CREATE FUNCTION. It does not find all possible errors, it does not try to evaluate functions.
Would work like this:
CREATE OR REPLACE FUNCTION field_validations2(tbl_name text
, col_name text, error_flag text)
RETURNS void AS
$func$
DECLARE
cust_rec RECORD;
BEGIN
FOR cust_rec IN
EXECUTE format('SELECT %I FROM %I', col_name, tbl_name)
LOOP
RAISE NOTICE 'inside loop';
END LOOP;
END
$func$ LANGUAGE plpgsql;
The proper syntax for a FOR-IN-EXECUTE statement can be found in the manual.
Also fixed a couple of other things. text instead of varchar(n) is just a friendly advice.
It is not possible to execute plpgsql. It must be plain SQL.