More on Triggers

Getting Internal Information

CREATE OR REPLACE FUNCTION fn_trigger_variables_display()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$

	BEGIN

		RAISE NOTICE 'TG_NAME: %', TG_NAME;
		RAISE NOTICE 'TG_RELNAME: %', TG_RELNAME;
		RAISE NOTICE 'TG_TABLE_SCHEMA: %', TG_TABLE_SCHEMA;
		RAISE NOTICE 'TG_WHEN: %', TG_WHEN;
		RAISE NOTICE 'TG_LEVEL: %', TG_LEVEL;
		RAISE NOTICE 'TG_OP: %', TG_OP;
		RAISE NOTICE 'TG_NARGS: %', TG_NARGS;
		RAISE NOTICE 'TG_ARGV: %', TG_NAME;

		RETURN NEW;
	END;
$$

CREATE TRIGGER trg_trigger_variables_display
	AFTER INSERT
	ON t_temperature_log
	FOR EACH ROW
	EXECUTE PROCEDURE fn_trigger_variables_display();

INSERT INTO t_temperature_log  ( add_date, temperature ) values ('2020-02-02', -40);

Disallow DELETE on table

-- create sample table

create table test_delete (
	id int
);

insert into test_delete (id ) values (1),(2),(3);

select * from test_delete;

-- creating function

create or replace function fn_generic_cancel_op()
returns trigger
language plpgsql
as
$$
	begin
	
	if TG_WHEN = 'AFTER' THEN
		raise exception 'you are not allowed to % rows in %.%', tg_op,tg_table_schema,tg_table_name;
	end if;
	
	raise notice '% on rows in %.% wont happen', tg_op, tg_table_schema, tg_table_name;
	return null; 
	
	end;
$$

-- creating trigger : AFTER

CREATE TRIGGER trg_disallow_delete
AFTER DELETE
ON test_delete
FOR EACH ROW
EXECUTE PROCEDURE fn_generic_cancel_op();

delete from test_delete where id = 1;

-- creating trigger : BEFORE

CREATE TRIGGER trg_disallow_delete_before
BEFORE DELETE
ON test_delete
FOR EACH ROW
EXECUTE PROCEDURE fn_generic_cancel_op();

delete from test_delete where id = 1;

-- checking

select * from test_delete;

Disallow truncating

CREATE TRIGGER trg_disallow_truncate_after
AFTER TRUNCATE
ON test_delete
FOR EACH STATEMENT
EXECUTE PROCEDURE fn_generic_cancel_op();


CREATE TRIGGER trg_disallow_truncate_beforE
BEFORE TRUNCATE
ON test_delete
FOR EACH STATEMENT
EXECUTE PROCEDURE fn_generic_cancel_op();

Creating Audit Trigger

  • To log data changes to tables in a consistent and transparent manner.

CREATE TABLE  audit (
    id INT
);

CREATE TABLE audit_log (
    username TEXT,
    add_time TIMESTAMP,
    table_name TEXT,
    operation TEXT,
    row_before JSON,
    row_after JSON
);
  • Please not that new OLD are not null for DELETE and INSERT triggers.

CREATE OR REPLACE FUNCTION fn_audit_trigger()
RETURNS TRIGGER 
LANGUAGE PLPGSQL
AS
$$
BEGIN

    DECLARE
        old_row json = NULL;
        new_row json = NULL;

    BEGIN

        IF TG_OP IN ('UPDATE','DELETE') THEN

            old_row = row_to_json(OLD);


        END IF;

        IF TG_OP IN ('INSERT','UPDATE') THEN

            new_row = row_to_json(NEW);

        END IF;
    
        INSERT INTO audit_log 
            ( username, add_time, table_name, operation, row_before, row_after )
        values
            (
                session_user,
                NOW(),
                TG_TABLE_SCHEMA || '.' || TG_TABLE_NAME,
                TG_OP,
                old_row,
                new_row
            );
        
        RETURN NEW;
    END;    

END;
$$

-- bind trigger

CREATE TRIGGER trg_audit_trigger
AFTER INSERT OR UPDATE OR DELETE
ON audit
FOR EACH ROW
EXECUTE PROCEDURE fn_audit_trigger();

-- Queries

insert into audit(id) values (1),(2),(3);

update audit
set id = '100'
where id = 1;

delete from audit
where id = 2;

select * from audit;
select * from audit_log;

Creating Conditional Triggers

  • Created by using generic WHEN clause.

  • With a WHEN clause, you can write some conditions except a subquery

-- sample table

CREATE TABLE mytask (
    task_id SERIAL PRIMARY KEY,
    task text
);

-- trigger function

CREATE OR REPLACE FUNCTION fn_cancel_with_message()
RETURNS TRIGGER 
LANGUAGE PLPGSQL
AS
$$
BEGIN

    RAISE EXCEPTION '%', TG_ARGV[0];
    
    RETURN NULL;

END;
$$

-- function binding to trigger

CREATE TRIGGER trg_no_update 
BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE
ON mytask
FOR EACH STATEMENT
WHEN 
(
	EXTRACT ('DOW' FROM CURRENT_TIMESTAMP) = 5
	-- 5 means friday
	AND CURRENT_TIME > '12:00'
)
EXECUTE PROCEDURE fn_cancel_with_message('NO UPDATE ARE ALLOWED');

-- Queries

INSERT INTO mytask (task) values ('task 1'), ('task 2'), ('task 3');

Disallow updating Primary Key of table

create table pg_table(
	id serial primary key,
	t text
);

insert into pg_table(t) values ('t1'),('t2');

create trigger disallow_pk_change
after update of id
on pg_table
for each row
execute procedure fn_generic_cancel_op();

update pg_table 
set id = 10
where id = 1;

Event Triggers

  • Event triggers are data-specific and not bind or attached to a table

  • Unlike regular triggers they capture system level DLL events

  • Event triggers can be BEFORE or AFTER triggers

  • Trigger function can be written in any language except SQL

  • Event triggers are disabled in the single user mode and can only be created by a superuser

  • Syntax : CREATE EVENT TRIGGER trg_name

  • Before creating an event trigger, we must have a function that the trigger will execute

  • The function must return a specifi type called EVENT_TRIGGER

  • This function need not (and may not) return a valuel the return type serivces merely as s signal that the function is to be invoked as an event trigger.

  • Can we create conditional event trigger ? Yes, using the when clause

  • Event trigger cannot be executed in an aborted transaction

Event trigger events

when
explaination

ddl_command_start

This event occurs jsut BEFORE a CREATE, ALTER, or DROP DLL command is executed

ddl_command_end

This event occurs just AFTER a create, alter, or drop command has finished executing

table_rewrite

This event occurs just before a table is re written by some action of the commands ALTER TABLE and ALTER TYPE.

sql_drop

This evetn occurs just before the ddl_command_end eevent for the commands that frop database objects

Event trigger variables

  • TG_TAG : this variable contains the 'TAG' or the command for which the trigger is executed.

  • TG_EVENT : This variable contains the event name, which can be ddl_command_start, ddl_comman_end, and sql_drop.

Creating an auditing event trigger

CREATE TABLE audit_dll (
	audit_ddl_id SERIAL PRIMARY KEY,
	username TEXT,
	ddl_event TEXT,
	ddl_command TEXT,
	ddl_add_time TIMESTAMPTZ
);

CREATE OR REPLACE FUNCTION fn_event_audit_ddl()
RETURNS EVENT_TRIGGER
LANGUAGE PLPGSQL
SECURITY DEFINER 
AS
$$
	BEGIN
		
		INSERT INTO public.audit_dll
		(username, ddl_event, ddl_command, ddl_add_time)
		VALUES 
		(session_user, TG_EVENT, TG_TAG, NOW());		
		
		RAISE NOTICE 'DDL activity is created';
		
	END;
$$

-- without condition
create event trigger trg_event_audit_ddl_no_cond
on ddl_command_start
execute procedure fn_event_audit_ddl();

-- with condition

create event trigger trg_event_audit_ddl
on ddl_command_start
when
 TAG IN ('CREATE TABLE')
execute procedure fn_event_audit_ddl();

Dont allow anyone to create table between time

CREATE OR REPLACE FUNCTION fn_event_abort_create_table_func()
RETURNS EVENT_TRIGGER
LANGUAGE PLPGSQL
SECURITY DEFINER
AS
$$
	DECLARE
		current_hour int = EXTRACT ('HOUR' FROM NOW());
	BEGIN
		IF current_hour between 4 and 16 then
			RAISE EXCEPTION 'tables are not allowed to be created during 9-4';
		END IF;
	END;
$$

CREATE EVENT TRIGGER trg_event_create_table_function
ON ddl_command_start 
WHEN
	TAG IN ('CREATE TABLE')
EXECUTE PROCEDURE fn_event_abort_create_table_func();

Dropping event trigger

drop event trigger trg_name;

Last updated