Check attribute in trigger function (PostgreSQL) - sql

I have a task in school which requires me to create a table and a trigger.
I don't really know how to explain it but how can I check if cID is inside the select statement within the trigger function ?
Basically my goal is to only allow cID values which are not inside of "SELECT * from Example2 natural join Example3". Can anyone help me with that? Thank you.
CREATE TABLE Example(
cID INTEGER REFERENCES Example2(attr),
level INTEGER CHECK (level BETWEEN 1 AND 10));
CREATE FUNCTION exp() RETURNS TRIGGER AS
$$
BEGIN
IF EXISTS (select * from Example2 natural join Example3) THEN
RAISE EXCEPTION '...';
END IF;
return null;
END; $$ language plpgsql;
CREATE CONSTRAINT TRIGGER trg
AFTER INSERT OR UPDATE ON Example
FOR EACH ROW
EXECUTE PROCEDURE exp();

CREATE FUNCTION exp() RETURNS TRIGGER AS
$$
BEGIN
IF EXISTS (select 1 from Example2 a where a.cID = new.cID ) THEN
RAISE EXCEPTION '...';
END IF;
return RETURN NEW;
END; $$ language plpgsql;

Related

How to prevent table creation without primary key in Postgres?

I would like to enforce a rule such that when people are creating table without primary key, it throws an error. Is it possible to be done from within pgdb?
DROP EVENT TRIGGER trig_test_event_trigger_table_have_primary_key;
CREATE OR REPLACE FUNCTION test_event_trigger_table_have_primary_key ()
RETURNS event_trigger
LANGUAGE plpgsql
AS $$
DECLARE
obj record;
object_types text[];
table_name text;
BEGIN
FOR obj IN
SELECT
*
FROM
pg_event_trigger_ddl_commands ()
LOOP
RAISE NOTICE 'classid: % objid: %,object_type: %
object_identity: % schema_name: % command_tag: %' , obj.classid , obj.objid , obj.object_type , obj.object_identity , obj.schema_name , obj.command_tag;
IF obj.object_type ~ 'table' THEN
table_name := obj.object_identity;
END IF;
object_types := object_types || obj.object_type;
END LOOP;
RAISE NOTICE 'table name: %' , table_name;
IF EXISTS (
SELECT
FROM
pg_index i
JOIN pg_attribute a ON a.attrelid = i.indrelid
AND a.attnum = ANY (i.indkey)
WHERE
i.indisprimary
AND i.indrelid = table_name::regclass) IS FALSE THEN
RAISE EXCEPTION ' no primary key, this table not created';
END IF;
END;
$$;
CREATE EVENT TRIGGER trig_test_event_trigger_table_have_primary_key ON ddl_command_end
WHEN TAG IN ('CREATE TABLE')
EXECUTE FUNCTION test_event_trigger_table_have_primary_key ();
demo:
DROP TABLE a3;
DROP TABLE a4;
DROP TABLE a5;
CREATE TABLE a3 (
a int
);
CREATE TABLE a4 (
a int PRIMARY KEY
);
CREATE TABLE a5 (
a1 int UNIQUE
);
Only table a4 will be created.
related post: PL/pgSQL checking if a row exists
https://wiki.postgresql.org/wiki/Retrieve_primary_key_columns
EDIT: Someone else has answered regarding how to test the existence of primary keys, which completes Part 2 below. You will have to combine both answers for the full solution.
The logic fits inside several event triggers (also see documentation for the create command).
First point to note is the DDL commands this can apply to, all documented here.
Part 1: CREATE TABLE AS & SELECT INTO
If I am not wrong, CREATE TABLE AS and SELECT INTO never add constraints on the created table, they must be blocked with an event trigger that always raises an exception.
CREATE OR REPLACE FUNCTION block_ddl()
RETURNS event_trigger
LANGUAGE plpgsql AS
$$
BEGIN
RAISE EXCEPTION 'It is forbidden to create tables using command: %', tg_tag ;
END;
$$;
CREATE EVENT TRIGGER AdHocTables_forbidden
ON ddl_command_end
WHEN TAG IN ('CREATE TABLE AS', 'SELECT INTO')
EXECUTE FUNCTION block_ddl();
Note your could define the trigger to be ON ddl_command_start`. It makes it a little bit faster but does not go well with the full code I posted at the end.
See the next, less straightforward part for the rest of the explanations.
Part 2: Regular CREATE TABLE & ALTER TABLE
This case is more complex, as we want to block only some commands but not all.
The function and event trigger below do:
Output the whole command being passed.
Break the command into its subparts.
To do it, it uses the pg_event_trigger_ddl_commands() (documentation here), which BTW is the reason why this trigger had to be on ddl_command_end.
You will note that when adding a primary key, a CREATE INDEX is caught too.
In the case of the function below, raises an exception to block the creation in all cases (so you can test it without dropping the table you create every time).
Here is the code:
CREATE OR REPLACE FUNCTION pk_enforced()
RETURNS event_trigger
LANGUAGE plpgsql AS
$$
DECLARE r RECORD;
BEGIN
RAISE NOTICE 'Caught command %', (SELECT current_query());
FOR r IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP
RAISE NOTICE 'Caught inside command % (%)', r.command_tag, r.object_identity;
END LOOP;
RAISE EXCEPTION 'Blocking the Creation';
END;
$$;
CREATE EVENT TRIGGER pk_is_mandatory
ON ddl_command_end
WHEN TAG IN ('CREATE TABLE', 'ALTER TABLE')
EXECUTE FUNCTION pk_enforced();
Additional notes:
You can prevent these constraints from being enforced on a temporary table by tested the schema_name is not pg_temp. The full code, including this test and with credit to jian for the function he posted:
CREATE OR REPLACE FUNCTION public.pk_enforced()
RETURNS event_trigger
LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
obj RECORD;
table_name text;
BEGIN
FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands ()
LOOP
IF obj.schema_name = 'pg_temp' THEN
return;
END IF;
IF obj.object_type ~ 'table' THEN
table_name := obj.object_identity;
END IF;
END LOOP;
IF NOT EXISTS (
SELECT
FROM pg_index i
JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY (i.indkey)
WHERE i.indrelid = table_name::regclass
AND (i.indisprimary OR i.indisunique)) THEN
RAISE EXCEPTION 'A primary key or a unique constraint is mandatory to perform % on %.', tg_tag, obj.object_identity;
END IF;
END;
$BODY$;
CREATE OR REPLACE FUNCTION public.block_ddl()
RETURNS event_trigger
LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
obj RECORD;
BEGIN
FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands ()
LOOP
IF obj.schema_name = 'pg_temp' THEN
return;
END IF;
END LOOP;
RAISE EXCEPTION 'DDL command ''%'' is blocked.', tg_tag ;
END;
$BODY$;
CREATE EVENT TRIGGER pk_is_mandatory ON DDL_COMMAND_END
WHEN TAG IN ('CREATE TABLE', 'ALTER TABLE')
EXECUTE PROCEDURE public.pk_enforced();
CREATE EVENT TRIGGER adhoctables_forbidden ON DDL_COMMAND_END
WHEN TAG IN ('CREATE TABLE AS', 'SELECT INTO')
EXECUTE PROCEDURE public.block_ddl();

Update columns for the first user inserted

I'm trying to create a trigger and a function to update some columns (roles and is_verified) for the first user created. Here is my function :
CREATE OR REPLACE FUNCTION public.first_user()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
DECLARE
begin
if(select count(*) from public.user = 1) then
update new set new.is_verified = true and new.roles = ["ROLE_USER", "ROLE_ADMIN"]
end if;
return new;
end;
$function$
;
and my trigger :
create trigger first_user
before insert
on
public.user for each row execute function first_user()
I'm working on Dbeaver and Dbeaver won't persist my function because of a syntax error near the "=". Any idea ?
Quite a few things are wrong in your trigger function. Here it is revised w/o changing your business logic.
However this will affect the second user, not the first. Probably you shall compare the count to 0. Then the condition shall be if not exists (select from public.user) then
CREATE OR REPLACE FUNCTION public.first_user()
RETURNS trigger LANGUAGE plpgsql AS
$function$
begin
if ((select count(*) from public.user) = 1) then
-- probably if not exists (select from public.user) then
new.is_verified := true;
new.roles := array['ROLE_USER', 'ROLE_ADMIN'];
end if;
return new;
end;
$function$;

Create trigger in Postgres

I have two tables A and B. A has the following columns :
a_name, a_num, a_addr. B also has these columns.
Whenever I insert a record in A, I want that record to be inserted in B as well by the use of a trigger.
CREATE OR REPLACE FUNCTION trigger()
RETURNS trigger AS
$BODY$
begin
if tg_op='INSERT' then
insert into b values (new.a_name ,new.a_num ,new.a_addr);
return new;
end if;
return null;
end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
---------------------------
CREATE TRIGGER trigger_a
AFTER INSERT OR UPDATE
ON a
FOR EACH ROW
EXECUTE PROCEDURE trigger();

Execute a query for every row of a table inside a trigger function

I have made the following query which is working perfect by itself but when i call it inside trigger function i got problem.
select insert_new_grade('title0', return3_6(0), return3_6(1), return3_6(2), s.code)
FROM "student" as s
where find_st(s.grade)>=5;
insert_new_grade is a function that inserts a new row in a table every time it's being called.
Here is the trigger :
CREATE OR REPLACE FUNCTION insert_d()
RETURNS TRIGGER AS $$
BEGIN
select insert_new_grade('title0', return3_6(0), return3_6(1), return3_6(2), s.code)
FROM "student" as s
where find_st(s.grade)>=5;
return new;
END;
$$ LANGUAGE plpgsql;
and here is the insert function :
CREATE OR REPLACE FUNCTION insert_new_grade(title0 character(100), prof0 character(11), prof1 character(11))
RETURNS VOID AS $$
BEGIN
INSERT INTO "d_table"(thes0, title, grade, prof, secProf)
VALUES (null, title0, null, prof0, prof1);
END
$$
LANGUAGE 'plpgsql';
Is there a way to make the query work inside the trigger function ? If i use perform instead of select the insert function does not have result. I've read about cursors but I'm new in postgresql and I don't know how to do it. Any help ?
i modifited your trigger function:
check your trigger function in conlose pgAdmin, is visible raise info text
CREATE OR REPLACE FUNCTION insert_d()
RETURNS TRIGGER AS $$
declare
rec record;
BEGIN
for rec in (select * from student s where find_st(s.grade)>=5) loop
raise info 'LOOP code = %',rec.code;
PERFORM insert_new_grade('title0', return3_6(0), return3_6(1), return3_6(2), rec.code);
end loop;
return new;
END;
$$ LANGUAGE plpgsql;

Trigger Not Executing Yet It's Created

I have a trigger function I'm trying to have execute in Postgres.
It compiles and adds the trigger, however it does not insert the value into the table as I had hoped.
The function it uses looks like this:
CREATE OR REPLACE FUNCTION
calc_gnpDifference(n integer, o integer)
RETURNS NUMERIC AS $$
SELECT $1 ::numeric - $2::numeric AS gnpDifference;
$$ LANGUAGE SQL;
And the Trigger part:
CREATE OR REPLACE FUNCTION autoCalculate() RETURNS TRIGGER AS $$
BEGIN
IF NEW.gnp_old > NEW.gnp_old THEN
NEW.gnpDifference := calc_gnpDifference(NEW.gnp_old, NEW.gnp);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER insertDifference ON country;
CREATE TRIGGER insertDifference BEFORE INSERT ON country
FOR EACH ROW EXECUTE PROCEDURE autoCalculate();
However, when I insert data, the trigger does not update the gnpDifference field as I had hoped. Thoughts on why this might be happening?
Obviously this condition: IF NEW.gnp_old > NEW.gnp_old will never be true so the trigger will never have any effect.