Handling error with exception clause in postgreSQL - postgresql

I wrote this piece of code in PL/PGSQL.
WHILE nbStatus1 < nbStatus0 AND StatusExec = 1 LOOP
BEGIN
SELECT Id, query INTO idQuery, Selectquery FROM temp_table_test WHERE Status = 0 LIMIT 1;
EXECUTE Selectquery;
UPDATE temp_table_test
SET Status = 1
WHERE Id = idQuery;
nbStatus1 := nbStatus1 + 1;
EXCEPTION
WHEN others THEN
UPDATE LOGS_TEST_DETAILS
SET ENDDATE = NOW(),
ERRORMESSAGE = SQLERRM,
ERRORCODE = SQLSTATE
WHERE TRAITEMENTID = lastTraitementId AND QUERYID = idQuery;
StatusExec := 0;
ROLLBACK;
END;
COMMIT;
END LOOP;
I use EXECUTE to CALL a list of procedure, one after the other in the while loop.
Each CALL is stored in the variable Selectquery.
It works fine but...
I'm trying to implement a kind of try catch in case i made a mistake in one of the procedure i call in the loop.
So i did a mistake (on purpose) inside one of the procedure i call. But the ROLLBACK, remove all the changes i did, even before the while loop. it kinda propagates to all my code
Is there a way to ROLLBACK only the changes that occured within the EXECUTE command and then exit the loop ?
There is something to deal with savepoint i guess, but i can't sort it out
Thanks for your help

Related

UPDATE/SET/OUTPUT/FROM with output into variable

Consider the following stored procedure
CREATE PROCEDURE AssignCodeToCustomer (#customerId int)
AS
BEGIN
DECLARE #code NVARCHAR(255)
BEGIN TRY
BEGIN TRANSACTION
SELECT #code = (
UPDATE
Codes
SET
CustomerId = #customerId
OUTPUT
INSERTED.Code
FROM (
SELECT TOP 1
Code
FROM
Codes
) AS c
WHERE
c.Code = Codes.Code
-- Other stuff
COMMIT TRANSACTION
END TRY
BEGIN CATCH
BEGIN
ROLLBACK TRANSACTION
EXEC spLogSQLError
END
END CATCH
END
GO
I get an error 'Incorrect syntax near the keyword UPDATE' on line 10 (which holds the keyword UPDATE). I could also first select a code and then assign it, but with concurrency in mind I want only one query. The query works if I don't try to set the output value into the variable. How can I fix this error or should I use another approach?

PostgreSQL trigger function update error

i cant seem to logically place this error being produced unlike most gives little to no information as to why (usually seems theirs very good pre-processors or you can dig into the logic, yet here i get no error.
also i have another function working well, that adds those keys, so its not that..
other then the transaction seems to fail and i get in the terminal when i enter
update guest_list set coatcheck = true where ticket_number = 3;
"PL/pgSQL function coatcheck_gen() line 8 at SQL statement
SQL statement "update guest_list set coatcheck_num = coat_num where ticket_number = old.ticket_number"
PL/pgSQL function coatcheck_gen() line 8 at SQL statement
SQL statement "update guest_list set coatcheck_num = coat_num where ticket_number = old.ticket_number"
this goes on for pages, then ends.
i've tried uses new. old. or just numbers to see. and nothing. same error.
all of the tables are fine. all updates work when just done on command.
it appears in examples elsewhere seemly correct...
the function is
create or replace function coatcheck_gen() returns trigger as $gencoatcheck$
declare
coat_num bigint;
begin
IF (TG_OP = 'UPDATE') then
if ( new.coatcheck = true ) then
coat_num := (old.frkey_id_event + old.frkey_id_guest);
update guest_list set coatcheck_num = coat_num where ticket_number = old.ticket_number;
return new;
END IF;
return new;
end if;
return new;
end;
$gencoatcheck$ LANGUAGE plpgsql;
trigger
create trigger trg_coatchek_gen after update on guest_list for each row when (new.coatcheck = true) execute Procedure coatcheck_gen();
You are making an infinite loop by updating the table inside the trigger.
You call it first and set the coatcheck = true, then the trigger update the table again but since coatcheck = true it will be again processed by the trigger (and this loop will never end).
You sould replace the entire line
update guest_list set coatcheck_num = coat_num where ticket_number = old.ticket_number;
by
new.coatcheck_num = coat_num;
and make the trigger before update

Issue with nested loops in postgresql

I am wondering if there is something related to variables and nested loops in postgresql that works differently than in other languages.
Example:
CREATE OR REPLACE FUNCTION public.generate_syllables()
RETURNS integer AS
$BODY$
DECLARE
w RECORD;
s RECORD;
current_syllable integer := 1;
vowel_trigger integer := 0;
syllable_count integer := 1;
BEGIN
FOR w IN SELECT id FROM words LOOP
FOR s IN SELECT sound, id FROM sounds WHERE id = w.id ORDER BY ordering LOOP
IF (SELECT sr.vowel FROM sound_reference sr WHERE sr.sound = s.sound) = 1 AND vowel_trigger = 1 THEN
syllable_count := syllable_count + 1;
UPDATE sounds SET syllable = syllable_count WHERE id = s.id;
vowel_trigger := 0;
ELSIF (SELECT sr.vowel FROM sound_reference sr WHERE sr.sound = s.sound) = 1 THEN
vowel_trigger := 1;
UPDATE sounds SET syllable = syllable_count WHERE id = s.id;
ELSE
UPDATE sounds SET syllable = syllable_count WHERE id = s.id;
END IF;
END LOOP;
UPDATE words SET syllables = syllable_count WHERE id = w.id;
syllable_count := 1;
vowel_trigger := 0;
END LOOP;
RETURN 1;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
When I run this function as is, the function never enters the first condition in the if statement. I tested this by adding a return statement within that first condition. At first I thought this must be a logic error, but I have gone through it by hand with examples that are generated from my dataset, and it should work as desired. What is even stranger, is when I comment out the line in the outer loop, for vowel_trigger := 0, then it DOES enter the first if statement. Of course then, the logic does not work correctly either, and from that I have gathered that the syllable_count is being set back to 0 BEFORE the nested loop finishes looping, which would also explain why the first condition is never entered, because vowel_trigger is set back to 0 before the loop makes it back up to the first condition.
In other words, it seems to me that my nested loop is not acting like a nested loop, but rather the nested loop extends into the outer loop, before the nested loop restarts. I imagine I must just not understand how to properly create a nested loop, or perhaps they just can't work this way in POSTGRESQL... any advice would be greatly appreciated.
You haven't provided table structures and - even more important - data. While your function's behavior really depends on data in tables words, sounds, and sound_reference. For example, if sound_reference is empty, vowel_trigger will never be 1, so the first IF becomes non-achievable.
This will help to debug your function:
RAISE NOTICE 'printlining helps to debug! vowel_trigger=%, syllable_count=%',
vowel_trigger, syllable_count;
As a side note, I've noticed that UPDATE sounds SET syllable = syllable_count WHERE id = s.id; is repeated in all if/else cases, so it may be worth to move it outside them and place right before the inner END LOOP;.
Addition:
...when I comment out the line in the outer loop, for vowel_trigger := 0, then it DOES enter the first if statement.
It tells us that one of the inner loop's executions ends with vowel_trigger being 1, and it would allow the first IF to trigger, but right outside the inner loop you turn it 0, so the first IF doesn't work then.

How to return an integer from a plpgsql function AND rollback every modifications?

My application uses pl/pgsql functions that return integers.
The returned integer is used as a return code, to distinguish between different errors.
For example, if a function that insert datas returns -1, it means that some datas exist already and this is forbidden to try to insert the same data again. But if it returns -2, it means something else. That way the application knows the error and can display a useful error message to the user.
My problem now is that i want, at some points in the function, to return immediately when i detect an error, and rollback everything done so far in the function. If i use "raise exception", it will rollback, but not return an integer. If i use "return -1;", it will return an integer, but not rollback modifications. So i'm stuck, because obviously i can't do both
Here's a phony example function:
CREATE OR REPLACE FUNCTION create_flight_and_passengers(
_date timetamp,
_name text,
_passengers integer[]
)
RETURNS integer
LANGUAGE plpgsql
AS $$
DECLARE
return_code integer;
BEGIN
INSERT INTO flights
VALUES (_name);
SELECT function_1(_date, _passengers) into return_code;
if (return_code = -1) then
-- [1] rollback everything done since beginning of function
-- create_flight_and_passengers() and return -1
end if;
SELECT function_2(_date, _passengers) into return_code;
if (return_code = -1) then
-- [2] rollback everything done since beginning of function
-- create_flight_and_passengers() and return -2
end if;
return 0;
END;
$$;
In [1] and [2] i could use raise exception to rollback, but when i do this i don't have a returned integer.
I could set a local variable in [1] and [2], then raise exception, and in EXCEPTION do tests on this variable to know where the exception come from, but this is bloated, there must be something better !
I'm not even sure you can rollback effects of a function you have called and that has terminated (function_1() and function_2() in my example)
Any ideas ?
This is a pretty bizarre thing to want to do. If you really need to, you could do it like this:
DECLARE
retval integer;
BEGIN
retval := 0;
BEGIN
... make my changes ...
IF (... is something wrong? ...) THEN
RAISE EXCEPTION SQLSTATE '0U001';
END IF;
EXCEPTION
WHEN '0U001' THEN
retval := -1;
END;
END;
The concept here is that a BEGIN ... EXCEPTION block defines a subtransaction. A RAISE EXCEPTION within the block rolls back the subtransaction. We catch it at the outer level, preventing the exception from propagating outside the function and aborting the whole transaction.
See the PL/PgSQL documentation.

count number of rows to be affected before update in trigger

I want to know number of rows that will be affected by UPDATE query in BEFORE per statement trigger . Is that possible?
The problem is that i want to allow only queries that will update up to 4 rows. If affected rows count is 5 or more i want to raise error.
I don't want to do this in code because i need this check on db level.
Is this at all possible?
Thanks in advance for any clues on that
Write a function that updates the rows for you or performs a rollback. Sorry for poor style formatting.
create function update_max(varchar, int)
RETURNS void AS
$BODY$
DECLARE
sql ALIAS FOR $1;
max ALIAS FOR $2;
rcount INT;
BEGIN
EXECUTE sql;
GET DIAGNOSTICS rcount = ROW_COUNT;
IF rcount > max THEN
--ROLLBACK;
RAISE EXCEPTION 'Too much rows affected (%).', rcount;
END IF;
--COMMIT;
END;
$BODY$ LANGUAGE plpgsql
Then call it like
select update_max('update t1 set id=id+10 where id < 4', 3);
where the first param ist your sql-Statement and the 2nd your max rows.
Simon had a good idea but his implementation is unnecessarily complicated. This is my proposition:
create or replace function trg_check_max_4()
returns trigger as $$
begin
perform true from pg_class
where relname='check_max_4' and relnamespace=pg_my_temp_schema();
if not FOUND then
create temporary table check_max_4
(value int check (value<=4))
on commit drop;
insert into check_max_4 values (0);
end if;
update check_max_4 set value=value+1;
return new;
end; $$ language plpgsql;
I've created something like this:
begin;
create table test (
id integer
);
insert into test(id) select generate_series(1,100);
create or replace function trg_check_max_4_updated_records()
returns trigger as $$
declare
counter_ integer := 0;
tablename_ text := 'temptable';
begin
raise notice 'trigger fired';
select count(42) into counter_
from pg_catalog.pg_tables where tablename = tablename_;
if counter_ = 0 then
raise notice 'Creating table %', tablename_;
execute 'create temporary table ' || tablename_ || ' (counter integer) on commit drop';
execute 'insert into ' || tablename_ || ' (counter) values(1)';
execute 'select counter from ' || tablename_ into counter_;
raise notice 'Actual value for counter= [%]', counter_;
else
execute 'select counter from ' || tablename_ into counter_;
execute 'update ' || tablename_ || ' set counter = counter + 1';
raise notice 'updating';
execute 'select counter from ' || tablename_ into counter_;
raise notice 'Actual value for counter= [%]', counter_;
if counter_ > 4 then
raise exception 'Cannot change more than 4 rows in one trancation';
end if;
end if;
return new;
end; $$ language plpgsql;
create trigger trg_bu_test before
update on test
for each row
execute procedure trg_check_max_4_updated_records();
update test set id = 10 where id <= 1;
update test set id = 10 where id <= 2;
update test set id = 10 where id <= 3;
update test set id = 10 where id <= 4;
update test set id = 10 where id <= 5;
rollback;
The main idea is to have a trigger on 'before update for each row' that creates (if necessary) a temporary table (that is dropped at the end of transaction). In this table there is just one row with one value, that is the number of updated rows in current transaction. For each update the value is incremented. If the value is bigger than 4, the transaction is stopped.
But I think that this is a wrong solution for your problem. What's a problem to run such wrong query that you've written about, twice, so you'll have 8 rows changed. What about deletion rows or truncating them?
PostgreSQL has two types of triggers: row and statement triggers. Row triggers only work within the context of a row so you can't use those. Unfortunately, "before" statement triggers don't see what kind of change is about to take place so I don't believe you can use those, either.
Based on that, I would say it's unlikely you'll be able to build that kind of protection into the database using triggers, not unless you don't mind using an "after" trigger and rolling back the transaction if the condition isn't satisfied. Wouldn't mind being proved wrong. :)
Have a look at using Serializable Isolation Level. I believe this will give you a consistent view of the database data within your transaction. Then you can use option #1 that MusiGenesis mentioned, without the timing vulnerability. Test it of course to validate.
I've never worked with postgresql, so my answer may not apply. In SQL Server, your trigger can call a stored procedure which would do one of two things:
Perform a SELECT COUNT(*) to determine the number of records that will be affected by the UPDATE, and then only execute the UPDATE if the count is 4 or less
Perform the UPDATE within a transaction, and only commit the transaction if the returned number of rows affected is 4 or less
No. 1 is timing vulnerable (the number of records affected by the UPDATE may change between the COUNT(*) check and the actual UPDATE. No. 2 is pretty inefficient, if there are many cases where the number of rows updated is greater than 4.