I have a procedure that checks for a condition (if a session is fully booked). I want to make a trigger that checks if the session is fully booked before inserting a new booking.
What I imagine it would be like is something like this.
CREATE OR REPLACE FUNCTION check_if_session_is_full_trigger()
RETURNS trigger AS
$BODY$
BEGIN
IF (EXECUTE session_is_full(NEW.session_id)) THEN
RAISE EXCEPTION 'SESSION IS FULLY BOOKED';
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
But I get this
ERROR: syntax error at or near "session_is_full"
LINE 5: IF (EXECUTE session_is_full(NEW.session_id) = TRUE) THE...
So obviously I'm not doing something right.
session_is_full(int) returns boolean.
Am I on the right track, and how to fix this?
IF (session_is_full(NEW.session_id)) should work – #a_horse_with_no_name
Related
I'm new to Postgres, but with experience from Oracle. Trying to create a stored procedure which is going to:
Insert a row
Handle exceptions and in case of an exception insert a row into a log table by calling dedicated procedure
Emit an audit log record into a log table in case the whole procedure ran successfully
By pseudo code:
CREATE OR REPLACE PROCEDURE test.p_insert(IN p_test_param character varying)
LANGUAGE 'plpgsql'
SECURITY DEFINER
AS $BODY$
DECLARE
-- some declarations
BEGIN
BEGIN
INSERT INTO test.a(a) VALUES (p_test_param);
EXCEPTION
WHEN OTHERS THEN
-- GET STACKED DIAGNOSTICS
CALL test.p_insert_log(...); -- Inserts a row into a log table, another COMMIT may be required?
RAISE;
END;
COMMIT; -- CAN'T DO
BEGIN
IF (SELECT test.f_debug()) THEN
CALL test.p_insert_log(...); -- Audit the execution
END IF;
END;
COMMIT; -- CAN'T DO EITHER
END;
$$BODY$$;
However when I try to test the procedure out from an anonymous block in PgAdmin such as:
BEGIN;
DO
LANGUAGE plpgsql
$$
BEGIN
CALL test.p_insert(
p_test_param => 'test'
);
END;
$$
I'm getting an error ERROR: invalid transaction termination. How can I get rid of it? My objective is to let the procedure carry out the transaction control, I don't want the caller to COMMIT or ROLLBACK anything. If I remove both COMMIT commands from the code of the procedure, it executes well, however the invoker must explicitly COMMIT or REVOKE the transaction afterwards, which is not desired. In Oracle the pseudo code with COMMIT statements would work, in Postgres it doesn't seem to work as I would like to. Could you please help me out? Thanks
Your code will work as intended. Perhaps you made some mistake in calling the code:
you cannot call the procedure from a function
you cannot call the procedure in an explicitly started transaction:
BEGIN;
CALL p_insert('something); -- will fail
COMMIT;
I've gotten the essence of the function below from: How to re-check an SQL function created with check_function_bodies=false?
The context is: I'm migrating some functions from Oracle to PostgreSQL. While migrating them, I used the option which establishes a non-verification of their bodies, so that all function could be created without being compiled/verified, what would "speed" the process. Therefore, now - using the function bellow as a mean - I am trying to make an analysis of each function created in this X schema.
My problem is that the function doesn't continue when it finds an error. My central thought is to run the recompile_function() once and get all the messages fired when an error is found for each function. I have tried to enclosure the statement that verifies the function within a sub-block (BEGIN, EXCEPTION, END). It didn't work though.
What am I missing here?
CREATE OR REPLACE FUNCTION public.recompile_functions()
RETURNS void
LANGUAGE plpgsql
AS
$function$
DECLARE
l_func regprocedure;
BEGIN
--test plpgsql functions
FOR l_func IN (
SELECT oid
FROM pg_proc
WHERE pronamespace='<<schema>>'::regnamespace
AND prolang=(SELECT oid FROM pg_language WHERE lanname='plpgsql')
AND pg_proc.oid NOT IN (select tg.tgfoid FROM pg_trigger tg)
AND pg_proc.prokind = 'f'
)
LOOP
BEGIN
PERFORM plpgsql_validator(l_func);
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION 'Function % failed validation checks: %', l_func::text, SQLERRM;
END;
END LOOP;
END;
$function$
This might be a stupid question but pardon me, I'm trying to convert one of my MariaDB database into a PostgreSQL database. Here I'm getting an error while executing this function.
I cannot find what's wrong here,
create function tg_prodcut_insert()
returns trigger as '
BEGIN
SET NEW.id = CONCAT(1, LPAD(INSERT INTO product_seq VALUES (NULL) returning id, 6, 0));
END;
' LANGUAGE 'plpgsql';
Error is pointing to the 1 in CONCAT method, The type of id I'm trying to SET is char(7)
EDIT
I also tried this, this won't work either,
create function tg_orders_insert()
returns trigger as '
BEGIN
INSERT INTO order_seq VALUES (NULL);
SET NEW.id = CONCAT('1', LPAD(LAST_INSERT_ID(), 6, 0));
END;
' LANGUAGE 'plpgsql';
Thanks in advance.
It seems you are trying to simulate some kind of sequence with that code by inserting into a table and then getting the auto_increment value from that.
This can be done much more efficiently using a sequence in Postgres.
The error you get also isn't caused by the concat() function but because you are using the wrong syntax.
Value assignment is done using := in PL/pgSQL.
And there is also no last_insert_id() function in Postgres. To get the next value from a sequence use nextval(), to get the most recently generated value, you can use lastval() but that's not necessary here.
create sequence product_id_seq;
create function tg_product_insert()
returns trigger as
$$
BEGIN
NEW.id := concat('ORD', to_char(nextval('product_id_seq'), 'FM00000000'));
return new;
END;
$$
LANGUAGE plpgsql;
you will need to create a before trigger for that to work:
create trigger product_seq_trigger
before insert on product
for each row
execute procedure tg_product_insert();
Online example
But it would be a lot more efficient to switch to a proper identity column instead and get rid of the trigger.
is there anyway that I can get the last SQLSTATE generated by any exception/command execute, something like:
CREATE FUNCTION SQL_STATE()
RETURNS CHAR(5)
AS $$
BEGIN
RETURN (SELECT pg_last_error.SQLSTATE);
END;
$$ LANGUAGE PLPGSQL;
No, this information is transient.
PostgreSQL sends the SQLSTATE in the server's response to a query. If you need to keep that, you have to do so in the client code.
Since your question contains PL/pgSQL code, I'll describe how that works there:
The SQLSTATE is only accessible in the EXCEPTION handler of a PL/pgSQL block, where it can be accessed through the variable SQLSTATE. Again, you'd have to retain this information yourself if you need it later.
Is it possible for a trigger to be defined in such a way that the row that was to be inserted is not inserted, without raising an exception? My use case is that I want to simplify the exception handling for the client library: the client library will just execute a statement to insert a row in a table and I was hoping that the trigger could be defined, more or less using the below syntax:
CREATE TRIGGER control_tr AFTER INSERT ON tableFoo
FOR EACH ROW
EXECUTE PROCEDURE control_tr_fun();
CREATE OR REPLACE FUNCTION control_tr_fun() RETURNS TRIGGER AS $$
BEGIN
IF (NOT condition_is_met(NEW.a, NEW.b, NEW.c)) THEN
DO NOTHING INSTEAD OF INSERT // syntax I am hoping for instead of RAISE EXCEPTION
ELSE
RETURN NEW;
END IF;
END
$$ LANGUAGE plpgsql;
I appreciate that I can ask the client library to call a PL/pgSQL function or make a trigger that RAISEs an exception and ask the client library to catch the exception (if raised) and just ignore it, but I am looking for a way to implement this as transparently for the client as possible.
If you RETURN NULL then nothing will happen, the INSERT will fail silently. But you need to define the trigger as BEFORE INSERT, and not AFTER INSERT as it is in your example.
If you RAISE EXCEPTION, then the entire transaction will fail.
You can also RAISE NOTICE without failing the transaction and catch it in the client library, but only if it was the last notice produced. You cannot stack notices.