Execute query passed in argument in Postgresql - sql

I'm learning plpgsql and I have an exercise where I have to check if a query passed in function argument raises an exception or not and for that I need to run that query but I have no idea how to that.
For the moment I have this :
CREATE OR REPLACE FUNCTION exec(query varchar(100)) RETURNS BOOLEAN AS $$
BEGIN
BEGIN
raise notice 'query : %', query;
PERFORM query;
return 1;
EXCEPTION
when undefined_table then
raise notice 'undefined table';
WHEN undefined_column THEN
raise notice 'undefined column';
WHEN foreign_key_violation THEN
raise notice 'foreign key violation ';
WHEN syntax_error THEN
raise notice 'syntaxe error';
END;
return 0;
END
$$ LANGUAGE PLPGSQL;
Right now the problem I have is with the 5th line. It's where I'd want to perform the query to catch an error and then return 1 if no exception is caught or else return 0 after the "END" if there's an error.
Thank you for your help!

You cannot to execute dynamic query by statement PERFORM. You should to use statement EXECUTE.
CREATE OR REPLACE FUNCTION exec(query text)
RETURNS BOOLEAN
AS $$
BEGIN
RAISE NOTICE 'query : %', query;
EXECUTE query;
RETURN true;
EXCEPTION
WHEN when undefined_table THEN
RAISE NOTICE 'undefined table';
WHEN undefined_column THEN
RAISE NOTICE 'undefined column';
WHEN foreign_key_violation THEN
RAISE NOTICE 'foreign key violation ';
WHEN syntax_error THEN
RAISE NOTICE 'syntax error';
RETURN false;
END;
$$ LANGUAGE PLPGSQL;
Good start for programming in PL/pgSQL is reading related documentation. It has only few pages. The programming of stored procedures in PL/pgSQL is partially different from application's programming or from programming of stored procedures in other RDBMS.

Related

plpgsql in EDB: SPL procedure with SPL-style OUT parameter or a function cannot be invoked using CALL in PL/pgSQL

I'm trying to run natively correct plpgsql code in EDB environment. Unfortunately there is a problem. Interestingly, the first run does not generate an error. Restarts generate an error, but after a few repetitions, there is no error again.
In PosgreSQL, the syntax DOES NOT CAUSE any problems.
Below is an example:
CREATE OR REPLACE PROCEDURE test1()
AS $procedure$
DECLARE
BEGIN
null;
END;
$procedure$
LANGUAGE plpgsql
;
CREATE OR REPLACE PROCEDURE test2()
AS $procedure$
DECLARE
BEGIN
call test1();
END;
$procedure$
LANGUAGE plpgsql
;
CREATE OR REPLACE PROCEDURE test3()
AS $procedure$
DECLARE
BEGIN
call test2();
END;
$procedure$
LANGUAGE plpgsql
;
And now try to run it in postgresql way and EDB way:
--run it few times as edb - error occurs randomly
begin
test3();
end;
--once again as plpgs - no error occurs but... check last call
do
$$
begin
CALL test3();
end;
$$
--once again as plpgs with exception block. - now error occurs same as edb call
do
$$
declare
v_sqlstate text;
v_message text;
v_context text;
begin
CALL test3();
EXCEPTION
WHEN OTHERS THEN
GET STACKED DIAGNOSTICS v_sqlstate = returned_sqlstate,v_message = message_text,v_context = pg_exception_context;
RAISE NOTICE 'sqlstate: %,message: %,context: %', v_sqlstate,v_message,v_context;
end;
$$
Error is:
ERROR: SPL procedure with SPL-style OUT parameter or a function cannot be invoked using CALL in PL/pgSQL
HINT: You might want to use SELECT instead.
CONTEXT: PL/pgSQL function test3() line 4 at CALL
edb-spl function inline_code_block line 2 at procedure/function invocation statement
SQL state: 42809
What am I missing???
I find that this might be a bug fixed in EPAS 13.8 (DB-1818).
https://www.enterprisedb.com/docs/epas/13/epas_rel_notes/epas13_8_12_rel_notes/
I guess you may try your test in this or later new minor version.
Best Regards.

How can I handle unique email violation exception in PostgreSQL?

CREATE OR REPLACE FUNCTION insert_new_user(u_name text, u_email text, role_id integer)
RETURNS void as
$body$
declare
u_id integer;
begin
-- perform check_email(u_email);
execute 'insert into users (USER_NAME,USER_EMAIL) values ('''|| u_name ||''','''|| u_email||''') RETURNING id;';
exception
when unique_violation then
raise notice "Email Is Not Unique" ;
perform insert_role_user(u_email,role_id);
END;
$body$
LANGUAGE plpgsql;
Above is the function to create a new user. The insertion is working good. But when I insert the duplicate email again then it errors.
I have tried using unique_violation exception... But could not properly defined.
Can someone help me to fix the error?
I used below code snippet to handle exception
exception
when unique_violation then
raise notice "Email Is Not Unique" ;
Error: SQL Error [42704]: ERROR: unrecognized exception condition "Email Is Not Unique"ΒΆ Where: compilation of PL/pgSQL function "insert_new_user" near line 11
In Postgres, strings are quoted with single quotes.
raise notice 'Email Is Not Unique';

NO_DATA_FOUND exception not thrown when used in SELECT INTO

I noticed strange behaviour of NO_DATA_FOUND exception when thrown from function used in PLSQL.
Long story short - it does propagate from function when using assignment, and does not propagate (or is handled silently somewhere in between) when used in SELECT INTO.
So, given function test_me throwing NO_DATA_FOUND exception, when invoked as:
v_x := test_me(p_pk);
It throws an exception, while when invoked as:
SELECT test_me(p_pk) INTO v_x FROM dual;
it does not throw exception. This does not occur with other exceptions. Below You can find my test examples.
Could somebody please explain to me this behaviour?
set serveroutput on;
CREATE OR REPLACE FUNCTION test_me(p_pk NUMBER) RETURN NVARCHAR2
IS
v_ret NVARCHAR2(50 CHAR);
BEGIN
BEGIN
SELECT 'PYK' INTO v_ret FROM dual WHERE 1 = 1/p_pk;
EXCEPTION WHEN NO_DATA_FOUND THEN
dbms_output.put_line(chr(9)||chr(9)||chr(9)||' (test_me NO_DATA_FOUND handled and rerised)');
RAISE;
END;
RETURN v_ret;
END;
/
DECLARE
v_x NVARCHAR2(500 CHAR);
v_pk NUMBER;
PROCEDURE test_example(p_pk NUMBER)
IS
BEGIN
BEGIN
dbms_output.put_line(chr(9)||chr(9)||'Test case 1: Select into.');
SELECT test_me(p_pk) INTO v_x FROM dual;
dbms_output.put_line(chr(9)||chr(9)||'Success: '||NVL(v_x,'NULL RETURNED'));
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line(chr(9)||chr(9)||'Failure: NO_DATA_FOUND detected');
WHEN OTHERS THEN
dbms_output.put_line(chr(9)||chr(9)||'Failure: '||SQLCODE||' detected');
END;
dbms_output.put_line(' ');
BEGIN
dbms_output.put_line(chr(9)||chr(9)||'Test case 2: Assignment.');
v_x := test_me(p_pk);
dbms_output.put_line(chr(9)||chr(9)||'Success: '||NVL(v_x,'NULL RETURNED'));
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line(chr(9)||chr(9)||'Failure: NO_DATA_FOUND detected');
WHEN OTHERS THEN
dbms_output.put_line(chr(9)||chr(9)||'Failure: '||SQLCODE||' detected');
END;
END;
BEGIN
dbms_output.put_line('START');
dbms_output.put_line(' ');
dbms_output.put_line(chr(9)||'Example 1: Function throws some exception, both cases throws exception, everything is working as expected.');
test_example(0);
dbms_output.put_line(' ');
dbms_output.put_line(chr(9)||'Example 2: Query returns row, there is no exceptions, everything is working as expected.');
test_example(1);
dbms_output.put_line(' ');
dbms_output.put_line(chr(9)||'Example 3: Query inside function throws NO_DATA_FOUND, strange things happen - one case is throwing exception, the other is not.');
test_example(2);
dbms_output.put_line(' ');
dbms_output.put_line('END');
END;
/
DROP FUNCTION test_me;
A minimal example is:
CREATE FUNCTION raise_exception RETURN INT
IS
BEGIN
RAISE NO_DATA_FOUND;
END;
/
If you do:
SELECT raise_exception
FROM DUAL;
You will get a single row containing a NULL value - Ask Tom states:
it has ALWAYS been that way
and then followed up with:
no_data_found is not an error - it is an "exceptional condition". You, the programmer, decide if something is an error by catching the exceptional condition and handling it (making it be "not an error") or ignoring it (making it be an error).
in sql, no data found quite simply means "no data found", stop.
Under the covers, SQL is raising back to the client application "hey buddy -- no_data_found". The
client in this case says "ah hah, no data found means 'end of data'" and stops.
So the exception is raised in the function and the SQL client sees this and interprets this as there is no data which is a NULL value and "handles" the exception.
So
DECLARE
variable_name VARCHAR2(50);
BEGIN
SELECT raise_exception
INTO variable_name
FROM DUAL
END;
/
Will succeed as the DUAL table has a single row and the exception from the function will be handled (silently) and the variable will end up containing a NULL value.
However,
BEGIN
DBMS_OUTPUT.PUT_LINE( raise_exception );
END;
/
The exception is this time being passed from the function to a PL/SQL scope - which does not handle the error and passes the exception to the exception handler block (which does not exist) so then gets passed up to the application scope and terminates execution of the program.
And Ask Tom states:
Under the covers, PLSQL is raising back to the client application "hey -- no_data_found. The client in this case says "uh-oh, wasn't expecting that from PLSQL -- sql sure, but not PLSQL. Lets print out the text that goes with this exceptional condition and continue on"
You see -- it is all in the way the CLIENT interprets the ORA-xxxxx message. That message, when raised by SQL, is interpreted by the client as "you are done". That message, when raised by PLSQL and not handled by the PLSQL programmer, is on the other hand interpreted as "a bad thing just happened"
Both PLSQL and SQL actually do the same thing here. It is the CLIENT that is deciding to do something different.
Now, if we change the function to raise a different exception:
CREATE OR REPLACE FUNCTION raise_exception RETURN INT
IS
BEGIN
RAISE ZERO_DIVIDE;
END;
/
Then both:
SELECT raise_exception
FROM DUAL;
and:
BEGIN
DBMS_OUTPUT.PUT_LINE( raise_exception );
END;
/
do not know how to handle the exception and terminate with ORA-01476 divisor is equal to zero.

Writing Errors to File in Postgresql

I want to write errors to a file in Postgresql, so whenever error occurs in a function it writes to a file, so I have captured the error and I pass on the error to another function and the return of that function is written to a file. Below is a sample function,
CREATE OR REPLACE FUNCTION test(TEXT)
RETURNS VOID
STRICT
LANGUAGE plpgsql
AS $$
declare STATEMENT TEXT;
declare sql_code TEXT;
BEGIN
BEGIN
EXECUTE 'DROP TABLE ' || $1;
EXCEPTION WHEN others THEN
sql_code := SQLERRM;
STATEMENT := 'COPY (select * from test1('''||sql_code||''')) to ''/tmp/errors.txt''';
EXECUTE STATEMENT;
RETURN;
END;
RAISE NOTICE 'Dropped table successfully %', $1;
RETURN;
END;
$$;
CREATE OR REPLACE FUNCTION test1(TEXT)
RETURNS TEXT
STRICT
LANGUAGE plpgsql
AS $$
declare error TEXT;
BEGIN
error := $1;
RETURN STATEMENT;
END;
$$;
This thing works fine but I want to know if there is any good way to capture the error and log it to a file ? Please suggest.
Changing logging levels would be preferable though there may be good reasons to prevent this.
Failing that you could write a secondary logging routine in, say, pl/perlu, or pl/python, which could write to a file. Just be careful that you don't overwrite your data files O.o.....

Rollback Transaction on Trigger ERROR

I'm trying to check if the room that is going to be inserted in the system is already rented at that date or not. I've though about counting the rows that match both the room number and the date, and then rolling back the transaction. But I'm getting the following error, even though I have changed the code to raise user-defined exceptions:
ERROR: cannot begin/end transactions in PL/pgSQL
HINT: Use a BEGIN block with an EXCEPTION clause instead.
CONTEXT: PL/pgSQL function "checkRoom"() line 17 at SQL statement
CREATE OR REPLACE FUNCTION "checkRoom"() RETURNS TRIGGER AS
$BODY$
DECLARE
counter integer;
BEGIN
SELECT COUNT("num_sesion")
FROM "Sesion"
INTO counter
WHERE "Room_Name"=NEW."Room_Name" AND "Date"=NEW."Date";
IF (counter> 0) THEN -- Probably counter>1 as it's triggered after the transaction..
raise notice 'THERE'S A ROOM ALREADY!!';
raise exception 'The room is rented at that date';
END IF;
RETURN new;
EXCEPTION
WHEN raise_exception THEN
ROLLBACK TRANSACTION;
RETURN new;
END;$BODY$
LANGUAGE plpgsql VOLATILE NOT LEAKPROOF;
Then I create the trigger:
CREATE TRIGGER "roomOcupied" AFTER INSERT OR UPDATE OF "Room_Name", "Date"
ON "Sesion" FOR EACH ROW
EXECUTE PROCEDURE "checkRoom"();
It's been 2 years from my last approach to SQL and the changes between plsql and plpgsql are getting me crazy.
A couple of issues with your trigger function:
Use IF EXISTS (...) THEN instead of counting all occurrences. Faster, simpler. See:
PL/pgSQL checking if a row exists
A trigger function AFTER INSERT OR UPDATE can just return NULL. RETURN NEW is only relevant for triggers called BEFORE. The manual:
The return value is ignored for row-level triggers fired after an operation, and so they can return NULL.
Unbalanced single quote.
As #Pavel explained, you cannot control transactions from within a plpgsql function. Any unhandled exception forces your entire transaction to be rolled back automatically. So, just remove the EXCEPTION block.
Your hypothetical trigger rewritten:
CREATE OR REPLACE FUNCTION check_room()
RETURNS TRIGGER AS
$func$
BEGIN
IF EXISTS (
SELECT FROM "Sesion" -- are you sure it's not "Session"?
WHERE "Room_Name" = NEW."Room_Name"
AND "Date" = NEW."Date") THEN
RAISE EXCEPTION 'The room is rented at that date';
END IF;
RETURN NULL;
END
$func$ LANGUAGE plpgsql;
A BEFORE trigger makes more sense.
But a UNIQUE INDEX ON ("Room_Name", "Date") would do the same, more efficiently. Then, any row in violation raises a duplicate key exception and rolls back the transaction (unless caught and handled). In modern Postgres you can alternatively skip or divert such INSERT attempts with INSERT ... ON CONFLICT .... See:
How to UPSERT (MERGE, INSERT ... ON DUPLICATE UPDATE) in PostgreSQL?
Advanced usage:
How to use RETURNING with ON CONFLICT in PostgreSQL?
PostgreSQL processes errors significantly differently from other databases. Any unhandled errors are raised to the user. Inside PL/pgSQL you can trap any exception or you can raise any exception, but you cannot explicitly control transactions. Any PostgreSQL statement is executed inside of a transaction (functions too). And the most outer transaction is automatically broken when any unhandled exception goes to the top.
What you can:
raise exception (often in triggers)
BEGIN
IF CURRENT_USER <> 'Admin' THEN
RAISE EXCEPTION 'missing admin rights';
END IF;
RETURN NEW;
END;
trapping exception
BEGIN
BEGIN -- start of protected section
-- do some, what can be stopped by exception
EXCEPTION WHEN divide_by_zero THEN
-- exception handler;
RAISE WARNING 'I was here';
-- should ignore
EXCEPTION WHEN others THEN
-- any unexpected exception
RAISE WARNING 'some unexpected issue';
RAISE; -- forward exception'
END;
There is no other possibility - so writing application in PL/pgSQL is very simple, but different than PL/SQL or TSQL.