In Postgresql, trigger can be created by using trigger procedure. This is handy way of creating trigger. Using the same trigger procedure, it is possible to create several triggers and apply it even for several different tables. I am wondering if there is any MySQL equivalent for it. I am inspired by this blog post which creates a generic trigger for database auditing. My plan is to implement the similar approach by using MySQL. But, is it really possible create that kind of generic trigger by MySQL?
1 Answer
After doing some research, I have understood that there is no direct way to create generic trigger in MySQL. Even dynamic SQL like prepare statement, execute statement are not allowed inside trigger in MySQL.
I have found a workaround to generate trigger dynamically. Suppose we have a customer table:
CREATE TABLE customer (
id bigint(20) NOT NULL AUTO_INCREMENT,
created_on datetime DEFAULT NULL,
first_name varchar(100) NOT NULL,
last_name varchar(100) NOT NULL,
PRIMARY KEY (id)
)
A revision info table:
CREATE TABLE REVINFO (
REV int(11) NOT NULL AUTO_INCREMENT,
REVTSTMP bigint(20) DEFAULT NULL,
PRIMARY KEY (REV)
)
An audit table:
CREATE TABLE customer_AUD (
id bigint(20) NOT NULL,
REV int(11) NOT NULL,
REVTYPE tinyint(4) DEFAULT NULL,
created_on datetime DEFAULT NULL,
first_name varchar(100) DEFAULT NULL,
last_name varchar(100) DEFAULT NULL,
PRIMARY KEY (id, REV),
KEY FK_REV (REV)
)
Now we will crate a procedure that will take a table name and generate SQL for create audit related trigger for table.
DROP PROCEDURE IF EXISTS `proc_trigger_generator`;
DELIMITER $$
CREATE PROCEDURE `proc_trigger_generator` (IN tableName VARCHAR(255))
BEGIN
DECLARE triggerSQL TEXT DEFAULT "";
DECLARE cols TEXT DEFAULT "";
DECLARE col_values TEXT DEFAULT "";
DECLARE insert_query TEXT DEFAULT "";
DECLARE colName TEXT DEFAULT "";
DECLARE done INT DEFAULT FALSE;
DECLARE cursorDS CURSOR FOR SELECT column_name FROM information_schema.columns cols
WHERE cols.table_name = CONCAT(tableName, '_AUD')
and (column_name != 'REV' && column_name != 'REVTYPE');
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
SET triggerSQL = 'DELIMITER ;; \n\n';
SET triggerSQL = CONCAT(triggerSQL, 'drop trigger if exists tr_', tableName, '_update_audit;; \n\n');
SET triggerSQL = CONCAT(triggerSQL, 'create trigger tr_', tableName, '_update_audit \n');
SET triggerSQL = CONCAT(triggerSQL, 'after update \n');
SET triggerSQL = CONCAT(triggerSQL, '\t on ', tableName, '\n');
SET triggerSQL = CONCAT(triggerSQL, 'for each row \n');
SET triggerSQL = CONCAT(triggerSQL, 'begin \n');
SET triggerSQL = CONCAT(triggerSQL, '\t DECLARE tmpInt INT; \n');
SET triggerSQL = CONCAT(triggerSQL, '\t SELECT COALESCE(MAX(REV), 0) FROM REVINFO into tmpInt; \n\n');
SET triggerSQL = CONCAT(triggerSQL, '\t INSERT INTO REVINFO (REV, REVTSTMP) VALUES (tmpInt+1, CURRENT_TIMESTAMP()); \n\n');
SET insert_query = CONCAT(insert_query, 'INSERT INTO ', CONCAT(tableName, '_AUD'), ' (');
OPEN cursorDS;
ds_loop: LOOP
FETCH cursorDS INTO colName;
IF done THEN
LEAVE ds_loop;
END IF;
SET cols = CONCAT(cols, colName, ', ');
SET col_values = CONCAT(col_values, 'new.', colName, ', ');
END LOOP;
SET insert_query = CONCAT(insert_query, cols, 'REV, REVTYPE) VALUES \n');
SET insert_query = CONCAT(insert_query, '\t\t(', col_values, 'tmpInt+1, 1', ');');
CLOSE cursorDS;
SET triggerSQL = CONCAT(triggerSQL, '\t ',insert_query, ' \n\n');
SET triggerSQL = CONCAT(triggerSQL, 'end;; \n\n');
SET triggerSQL = CONCAT(triggerSQL, 'DELIMITER ; \n\n');
SELECT triggerSQL;
END $$
DELIMITER ;
call proc_trigger_generator('customer');
Calling the procedure by using customer table name generates SQL for the desired trigger:
DELIMITER ;;
drop trigger if exists tr_customer_update_audit;;
create trigger tr_customer_update_audit
after update
on customer
for each row
begin
DECLARE tmpInt INT;
SELECT COALESCE(MAX(REV), 0) FROM REVINFO into tmpInt;
INSERT INTO REVINFO (REV, REVTSTMP) VALUES (tmpInt+1, CURRENT_TIMESTAMP());
INSERT INTO customer_AUD (id, created_on, first_name, last_name, REV, REVTYPE) VALUES
(new.id, new.created_on, new.first_name, new.last_name, tmpInt+1, 1);
end;;
DELIMITER ;
The above trigger should do auditing tasks for customer table. The trigger generator procedure can be applied to any other table now that we wish to apply auditing related tasks.