mirror of
https://github.com/MariaDB/server.git
synced 2025-04-18 21:44:20 +03:00
MDEV-10164: Add support for TRIGGERS that fire on multiple events
Added capability to create a trigger associated with several trigger events. For this goal, the syntax of the CREATE TRIGGER statement was extended to support the syntax structure { event [ OR ... ] } for the `trigger_event` clause. Since one trigger will be able to handle several events it should be provided a way to determine what kind of event is handled on execution of a trigger. For this goal support of the clauses INSERTING, UPDATING , DELETING was added by this patch. These clauses can be used inside a trigger body to detect what kind of trigger action is currently processed using the following boilerplate: IF INSERTING THEN ... ELSIF UPDATING THEN ... ELSIF DELETING THEN ... In case one of the clauses INSERTING, UPDATING, DELETING specified in a trigger's body not matched with a trigger event type, the error ER_INCOMPATIBLE_EVENT_FLAG is emitted. After this patch be pushed, one Trigger object will be associated with several trigger events. It means that the array Table_triggers_list::triggers can contain several pointers to the same Trigger object in array members corresponding to different events. Moreover, support of several trigger events for the same trigger requires that the data members `next` and `action_order` of the Trigger class be converted to arrays to store relating information per trigger event base. Ability to specify the same trigger for different event types results in necessity to handle invalid cases on execution of the multi-event trigger, when the OLD or NEW qualifiers doesn't match a current event type against that the trigger is run. The clause OLD should produces the NULL value for INSERT event, whereas the clause NEW should produce the NULL value for DELETE event.
This commit is contained in:
parent
cb2d6abae1
commit
8d1691286c
@ -1341,7 +1341,7 @@ table_schema='information_schema' and
|
||||
group by column_type order by num;
|
||||
column_type group_concat(table_schema, '.', table_name) num
|
||||
varchar(7) information_schema.ROUTINES,information_schema.VIEWS,information_schema.SLAVE_STATUS 3
|
||||
varchar(20) information_schema.ALL_PLUGINS,information_schema.ALL_PLUGINS,information_schema.ALL_PLUGINS,information_schema.FILES,information_schema.FILES,information_schema.PLUGINS,information_schema.PLUGINS,information_schema.PLUGINS,information_schema.PROFILING 9
|
||||
varchar(20) information_schema.ALL_PLUGINS,information_schema.ALL_PLUGINS,information_schema.ALL_PLUGINS,information_schema.FILES,information_schema.FILES,information_schema.PLUGINS,information_schema.PLUGINS,information_schema.PLUGINS,information_schema.PROFILING,information_schema.TRIGGERS 10
|
||||
create table t1(f1 char(1) not null, f2 char(9) not null)
|
||||
default character set utf8;
|
||||
select CHARACTER_MAXIMUM_LENGTH, CHARACTER_OCTET_LENGTH from
|
||||
|
@ -1018,7 +1018,7 @@ c int(11) NO PRI NULL
|
||||
SHOW TRIGGERS LIKE 't1';
|
||||
Catalog Database Table Table_alias Column Column_alias Type Length Max length Is_null Flags Decimals Charsetnr
|
||||
def information_schema TRIGGERS TRIGGERS TRIGGER_NAME Trigger 253 192 5 N 4097 0 33
|
||||
def information_schema TRIGGERS TRIGGERS EVENT_MANIPULATION Event 253 18 6 N 4097 0 33
|
||||
def information_schema TRIGGERS TRIGGERS EVENT_MANIPULATION Event 253 60 6 N 4097 0 33
|
||||
def information_schema TRIGGERS TRIGGERS EVENT_OBJECT_TABLE Table 253 192 2 N 4097 0 33
|
||||
def information_schema TRIGGERS TRIGGERS ACTION_STATEMENT Statement 252 589815 10 N 4113 0 33
|
||||
def information_schema TRIGGERS TRIGGERS ACTION_TIMING Timing 253 18 6 N 4097 0 33
|
||||
@ -1055,7 +1055,7 @@ Catalog Database Table Table_alias Column Column_alias Type Length Max length Is
|
||||
def information_schema TRIGGERS TRIGGERS TRIGGER_CATALOG TRIGGER_CATALOG 253 1536 3 N 4097 0 33
|
||||
def information_schema TRIGGERS TRIGGERS TRIGGER_SCHEMA TRIGGER_SCHEMA 253 192 4 N 4097 0 33
|
||||
def information_schema TRIGGERS TRIGGERS TRIGGER_NAME TRIGGER_NAME 253 192 5 N 4097 0 33
|
||||
def information_schema TRIGGERS TRIGGERS EVENT_MANIPULATION EVENT_MANIPULATION 253 18 6 N 4097 0 33
|
||||
def information_schema TRIGGERS TRIGGERS EVENT_MANIPULATION EVENT_MANIPULATION 253 60 6 N 4097 0 33
|
||||
def information_schema TRIGGERS TRIGGERS EVENT_OBJECT_CATALOG EVENT_OBJECT_CATALOG 253 1536 3 N 4097 0 33
|
||||
def information_schema TRIGGERS TRIGGERS EVENT_OBJECT_SCHEMA EVENT_OBJECT_SCHEMA 253 192 4 N 4097 0 33
|
||||
def information_schema TRIGGERS TRIGGERS EVENT_OBJECT_TABLE EVENT_OBJECT_TABLE 253 192 2 N 4097 0 33
|
||||
|
@ -2646,5 +2646,317 @@ a_old b_old a_new b_new
|
||||
# Clean up
|
||||
DROP TABLE t1, t2;
|
||||
#
|
||||
# MDEV-10164: Add support for TRIGGERS that fire on multiple events
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE TABLE t2 (a INT);
|
||||
CREATE TRIGGER t1_b_any BEFORE INSERT OR UPDATE OR DELETE ON t1 FOR EACH ROW INSERT INTO t2 VALUES (1000);
|
||||
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW INSERT INTO t2 VALUES (NEW.a);
|
||||
CREATE TRIGGER t1_bu BEFORE UPDATE ON t1 FOR EACH ROW INSERT INTO t2 VALUES (NEW.a);
|
||||
CREATE TRIGGER t1_bd BEFORE DELETE ON t1 FOR EACH ROW INSERT INTO t2 VALUES (OLD.a);
|
||||
INSERT INTO t1 VALUES (1);
|
||||
# The previous statement should fire the triggers t1_b_any and t1_bi.
|
||||
# Check it. The following query should return the rows (1000), (1)
|
||||
SELECT * FROM t2;
|
||||
a
|
||||
1000
|
||||
1
|
||||
# Clean up the table t2 before running the next test case
|
||||
TRUNCATE TABLE t2;
|
||||
UPDATE t1 SET a = -1;
|
||||
# The previous statement should fire the triggers t1_b_any and t1_bu.
|
||||
# Check it. The following query should return the rows (1000), (-1)
|
||||
SELECT * FROM t2;
|
||||
a
|
||||
1000
|
||||
-1
|
||||
# Clean up the table t2 before running the next test case
|
||||
TRUNCATE TABLE t2;
|
||||
DELETE FROM t1 WHERE a = -1;
|
||||
# The previous statement should fire the triggers t1_b_any and t1_bu.
|
||||
# Check it. The following query should return the rows (1000), (-1)
|
||||
SELECT * FROM t2;
|
||||
a
|
||||
1000
|
||||
-1
|
||||
# Clean up
|
||||
DROP TABLE t1, t2;
|
||||
# The following test case is about handling the new clauses
|
||||
# INSERTING, UPDATING, DELETING
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE TABLE t2 (a INT, b VARCHAR(10));
|
||||
CREATE TRIGGER t1_b_any BEFORE INSERT OR UPDATE OR DELETE ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
IF INSERTING THEN
|
||||
INSERT INTO t2 VALUES (NEW.a, 'INSERTING');
|
||||
ELSEIF UPDATING THEN
|
||||
INSERT INTO t2 VALUES (NEW.a, 'UPDATING');
|
||||
ELSEIF DELETING THEN
|
||||
INSERT INTO t2 VALUES (OLD.a, 'DELETING');
|
||||
END IF;
|
||||
END
|
||||
$
|
||||
INSERT INTO t1 VALUES(100);
|
||||
UPDATE t1 SET a = 300;
|
||||
DELETE FROM t1;
|
||||
# Query results of trigger executions
|
||||
SELECT * FROM t2;
|
||||
a b
|
||||
100 INSERTING
|
||||
300 UPDATING
|
||||
300 DELETING
|
||||
# Check that SHOW TRIGGERS outputs data about every specified event type
|
||||
# in the column `event`. For the trigger t1_b_any the column `event`
|
||||
# must contain the value `INSERT,UPDATE,DELETE`
|
||||
SHOW TRIGGERS;
|
||||
Trigger Event Table Statement Timing Created sql_mode Definer character_set_client collation_connection Database Collation
|
||||
t1_b_any INSERT,UPDATE,DELETE t1 BEGIN
|
||||
IF INSERTING THEN
|
||||
INSERT INTO t2 VALUES (NEW.a, 'INSERTING');
|
||||
ELSEIF UPDATING THEN
|
||||
INSERT INTO t2 VALUES (NEW.a, 'UPDATING');
|
||||
ELSEIF DELETING THEN
|
||||
INSERT INTO t2 VALUES (OLD.a, 'DELETING');
|
||||
END IF;
|
||||
END BEFORE # STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION root@localhost latin1 latin1_swedish_ci utf8mb4_uca1400_ai_ci
|
||||
# Check that DROP TRIGGER does work correctly
|
||||
DROP TRIGGER t1_b_any;
|
||||
# No triggers in output of SHOW TRIGGERS;
|
||||
SHOW TRIGGERS;
|
||||
Trigger Event Table Statement Timing Created sql_mode Definer character_set_client collation_connection Database Collation
|
||||
# Clean up
|
||||
DROP TABLE t1, t2;
|
||||
#
|
||||
# Check that event flag in condition must match the event type of a trigger
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
# The following CREATE TRIGGER statement must fail since
|
||||
# the UPDATING clause can be specified only for UPDATE trigger event
|
||||
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
IF UPDATING THEN
|
||||
SET @a=100;
|
||||
END IF;
|
||||
END;
|
||||
$
|
||||
ERROR HY000: Event flag 'UPDATING' in the condition expression is not compatible with the trigger event type 'INSERT'
|
||||
# The following CREATE TRIGGER statement must fail since
|
||||
# the DELETING clause can be specified only for DELETE trigger event
|
||||
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
IF DELETING THEN
|
||||
SET @a=100;
|
||||
END IF;
|
||||
END;
|
||||
$
|
||||
ERROR HY000: Event flag 'DELETING' in the condition expression is not compatible with the trigger event type 'INSERT'
|
||||
# The following CREATE TRIGGER statement must fail since
|
||||
# the INSERTING clause can be specified only for INSERT trigger event
|
||||
CREATE TRIGGER t1_bi BEFORE UPDATE ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
IF INSERTING THEN
|
||||
SET @a=100;
|
||||
END IF;
|
||||
END;
|
||||
$
|
||||
ERROR HY000: Event flag 'INSERTING' in the condition expression is not compatible with the trigger event type 'UPDATE'
|
||||
# The following CREATE TRIGGER statement must fail since
|
||||
# the INSERTING clause can be specified only for INSERT trigger event
|
||||
CREATE TRIGGER t1_bi BEFORE DELETE ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
IF INSERTING THEN
|
||||
SET @a=100;
|
||||
END IF;
|
||||
END;
|
||||
$
|
||||
ERROR HY000: Event flag 'INSERTING' in the condition expression is not compatible with the trigger event type 'DELETE'
|
||||
# The following CREATE TRIGGER statement must fail since
|
||||
# the DELETING clause can be specified only for DELETE trigger event
|
||||
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
IF DELETING THEN
|
||||
SET @a=100;
|
||||
END IF;
|
||||
END;
|
||||
$
|
||||
ERROR HY000: Event flag 'DELETING' in the condition expression is not compatible with the trigger event type 'INSERT'
|
||||
# The following CREATE TRIGGER statement must fail since
|
||||
# the DELETING clause can be specified only for DELETE trigger event
|
||||
CREATE TRIGGER t1_bi BEFORE UPDATE ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
IF DELETING THEN
|
||||
SET @a=100;
|
||||
END IF;
|
||||
END;
|
||||
$
|
||||
ERROR HY000: Event flag 'DELETING' in the condition expression is not compatible with the trigger event type 'UPDATE'
|
||||
# Clean up
|
||||
DROP TABLE t1;
|
||||
# Check that NEW and OLD clause is handled correctly for different trigger
|
||||
# event types.
|
||||
# For INSERT trigger, referencing to a table's column via the OLD clause
|
||||
# should return the NULL value; for DELETE trigger, referencing to a table's
|
||||
# column via the NEW clause should return the NULL value.
|
||||
# First, check that for integral types
|
||||
CREATE TABLE t1 (a INT, b TINYINT, c SMALLINT, d MEDIUMINT, e BIGINT, f DECIMAL, g FLOAT, h DOUBLE);
|
||||
CREATE TABLE t2 (old_a INT, new_a INT, old_b TINYINT, new_b TINYINT, old_c SMALLINT, new_c SMALLINT, old_d MEDIUMINT, new_d MEDIUMINT, old_e BIGINT, new_e BIGINT, old_f DECIMAL, new_f DECIMAL, old_g FLOAT, new_g FLOAT, old_h DOUBLE, new_h DOUBLE, event_name VARCHAR(20));
|
||||
CREATE TRIGGER t1_b_all BEFORE INSERT OR UPDATE OR DELETE ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
IF INSERTING THEN
|
||||
INSERT INTO t2 VALUES (OLD.a, NEW.a, OLD.b, NEW.b, OLD.c, NEW.c, OLD.d, NEW.d, OLD.e, NEW.e, OLD.f, NEW.f, OLD.g, NEW.g, OLD.h, NEW.h, 'INSERTING');
|
||||
SET @old_a = OLD.a;
|
||||
SET @new_a = NEW.a;
|
||||
SET @old_b = OLD.b;
|
||||
SET @new_b = NEW.b;
|
||||
SET @old_c = OLD.c;
|
||||
SET @new_c = NEW.c;
|
||||
SET @old_d = OLD.d;
|
||||
SET @new_d = NEW.d;
|
||||
SET @old_e = OLD.e;
|
||||
SET @new_e = NEW.e;
|
||||
SET @old_f = OLD.f;
|
||||
SET @new_f = NEW.f;
|
||||
SET @old_g = OLD.g;
|
||||
SET @new_g = NEW.g;
|
||||
SET @old_h = OLD.h;
|
||||
SET @new_h = NEW.h;
|
||||
ELSEIF UPDATING THEN
|
||||
INSERT INTO t2 VALUES (OLD.a, NEW.a, OLD.b, NEW.b, OLD.c, NEW.c, OLD.d, NEW.d, OLD.e, NEW.e, OLD.f, NEW.f, OLD.g, NEW.g, OLD.h, NEW.h, 'UPDATING');
|
||||
SET @old_a = OLD.a;
|
||||
SET @new_a = NEW.a;
|
||||
SET @old_b = OLD.b;
|
||||
SET @new_b = NEW.b;
|
||||
SET @old_c = OLD.c;
|
||||
SET @new_c = NEW.c;
|
||||
SET @old_d = OLD.d;
|
||||
SET @new_d = NEW.d;
|
||||
SET @old_e = OLD.e;
|
||||
SET @new_e = NEW.e;
|
||||
SET @old_f = OLD.f;
|
||||
SET @new_f = NEW.f;
|
||||
SET @old_g = OLD.g;
|
||||
SET @new_g = NEW.g;
|
||||
SET @old_h = OLD.h;
|
||||
SET @new_h = NEW.h;
|
||||
ELSEIF DELETING THEN
|
||||
INSERT INTO t2 VALUES (OLD.a, NEW.a, OLD.b, NEW.b, OLD.c, NEW.c, OLD.d, NEW.d, OLD.e, NEW.e, OLD.f, NEW.f, OLD.g, NEW.g, OLD.h, NEW.h, 'DELETING');
|
||||
SET @old_a = OLD.a;
|
||||
SET @new_a = NEW.a;
|
||||
SET @old_b = OLD.b;
|
||||
SET @new_b = NEW.b;
|
||||
SET @old_c = OLD.c;
|
||||
SET @new_c = NEW.c;
|
||||
SET @old_d = OLD.d;
|
||||
SET @new_d = NEW.d;
|
||||
SET @old_e = OLD.e;
|
||||
SET @new_e = NEW.e;
|
||||
SET @old_f = OLD.f;
|
||||
SET @new_f = NEW.f;
|
||||
SET @old_g = OLD.g;
|
||||
SET @new_g = NEW.g;
|
||||
SET @old_h = OLD.h;
|
||||
SET @new_h = NEW.h;
|
||||
END IF;
|
||||
END
|
||||
$
|
||||
INSERT INTO t1 VALUES (100, 100, 100, 100, 100, 100, 100, 100);
|
||||
SELECT @old_a, @new_a, @old_b, @new_b, @old_c, @new_c, @old_d, @new_d, @old_e, @new_e, @old_f, @new_f, @old_g, @new_g, @old_h, @new_h;
|
||||
@old_a @new_a @old_b @new_b @old_c @new_c @old_d @new_d @old_e @new_e @old_f @new_f @old_g @new_g @old_h @new_h
|
||||
NULL 100 NULL 100 NULL 100 NULL 100 NULL 100 NULL 100 NULL 100 NULL 100
|
||||
UPDATE t1 SET a = 150, b=110, c=150, d=150, e=150, f=150, g=150, h=150 WHERE a = 100;
|
||||
SELECT @old_a, @new_a, @old_b, @new_b, @old_c, @new_c, @old_d, @new_d, @old_e, @new_e, @old_f, @new_f, @old_g, @new_g, @old_h, @new_h;
|
||||
@old_a @new_a @old_b @new_b @old_c @new_c @old_d @new_d @old_e @new_e @old_f @new_f @old_g @new_g @old_h @new_h
|
||||
100 150 100 110 100 150 100 150 100 150 100 150 100 150 100 150
|
||||
DELETE FROM t1 WHERE a = 150;
|
||||
SELECT @old_a, @new_a, @old_b, @new_b, @old_c, @new_c, @old_d, @new_d, @old_e, @new_e, @old_f, @new_f, @old_g, @new_g, @old_h, @new_h;
|
||||
@old_a @new_a @old_b @new_b @old_c @new_c @old_d @new_d @old_e @new_e @old_f @new_f @old_g @new_g @old_h @new_h
|
||||
150 NULL 110 NULL 150 NULL 150 NULL 150 NULL 150 NULL 150 NULL 150 NULL
|
||||
SELECT * FROM t2;
|
||||
old_a new_a old_b new_b old_c new_c old_d new_d old_e new_e old_f new_f old_g new_g old_h new_h event_name
|
||||
NULL 100 NULL 100 NULL 100 NULL 100 NULL 100 NULL 100 NULL 100 NULL 100 INSERTING
|
||||
100 150 100 110 100 150 100 150 100 150 100 150 100 150 100 150 UPDATING
|
||||
150 NULL 110 NULL 150 NULL 150 NULL 150 NULL 150 NULL 150 NULL 150 NULL DELETING
|
||||
# Clean up
|
||||
DROP TABLE t1, t2;
|
||||
# Then, check this assertion for string types
|
||||
CREATE TABLE t1 (a CHAR(10), b VARCHAR(10));
|
||||
CREATE TABLE t2 (old_a CHAR(10), new_a CHAR(10), old_b VARCHAR(10), new_b VARCHAR(10), event_name VARCHAR(20));
|
||||
CREATE TRIGGER t1_b_all BEFORE INSERT OR UPDATE OR DELETE ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
IF INSERTING THEN
|
||||
INSERT INTO t2 VALUES (OLD.a, NEW.a, OLD.b, NEW.b, 'INSERTING');
|
||||
SET @old_a = OLD.a;
|
||||
SET @new_a = NEW.a;
|
||||
SET @old_b = OLD.b;
|
||||
SET @new_b = NEW.b;
|
||||
ELSEIF UPDATING THEN
|
||||
INSERT INTO t2 VALUES (OLD.a, NEW.a, OLD.b, NEW.b, 'UPDATING');
|
||||
SET @old_a = OLD.a;
|
||||
SET @new_a = NEW.a;
|
||||
SET @old_b = OLD.b;
|
||||
SET @new_b = NEW.b;
|
||||
ELSEIF DELETING THEN
|
||||
INSERT INTO t2 VALUES (OLD.a, NEW.a, OLD.b, NEW.b, 'DELETING');
|
||||
SET @old_a = OLD.a;
|
||||
SET @new_a = NEW.a;
|
||||
SET @old_b = OLD.b;
|
||||
SET @new_b = NEW.b;
|
||||
END IF;
|
||||
END
|
||||
$
|
||||
INSERT INTO t1 VALUES ('aaa', 'bbb');
|
||||
SELECT @old_a, @new_a, @old_b, @new_b;
|
||||
@old_a @new_a @old_b @new_b
|
||||
NULL aaa NULL bbb
|
||||
UPDATE t1 SET a = 'AAA', b = 'BBB' WHERE a = 'aaa';
|
||||
SELECT @old_a, @new_a, @old_b, @new_b;
|
||||
@old_a @new_a @old_b @new_b
|
||||
aaa AAA bbb BBB
|
||||
DELETE FROM t1 WHERE a = 'AAA';
|
||||
SELECT @old_a, @new_a, @old_b, @new_b;
|
||||
@old_a @new_a @old_b @new_b
|
||||
AAA NULL BBB NULL
|
||||
SELECT * FROM t2;
|
||||
old_a new_a old_b new_b event_name
|
||||
NULL aaa NULL bbb INSERTING
|
||||
aaa AAA bbb BBB UPDATING
|
||||
AAA NULL BBB NULL DELETING
|
||||
# Clean up
|
||||
DROP TABLE t1, t2;
|
||||
# Test for a column declared as NOT NULL
|
||||
CREATE TABLE t1 (a INT NOT NULL);
|
||||
CREATE TABLE t2 (old_a INT, new_a INT, event_name VARCHAR(20));
|
||||
CREATE TRIGGER t1_b_all BEFORE INSERT OR UPDATE OR DELETE ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
IF INSERTING THEN
|
||||
INSERT INTO t2 VALUES (OLD.a, NEW.a, 'INSERTING');
|
||||
ELSEIF DELETING THEN
|
||||
INSERT INTO t2 VALUES (OLD.a, NEW.a, 'DELETING');
|
||||
END IF;
|
||||
END;
|
||||
$
|
||||
# Check that OLD.a is NULL in spite of the fact that the column `a`
|
||||
# is declared as NOT NULL. OLD.a is referenced from the trigger t1_b_all
|
||||
# when it is fired on INSERT event
|
||||
INSERT INTO t1 VALUES (1);
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
1
|
||||
SELECT * FROM t2;
|
||||
old_a new_a event_name
|
||||
NULL 1 INSERTING
|
||||
TRUNCATE TABLE t2;
|
||||
# Check that NEL.a is NULL in spite of the fact that the column `a`
|
||||
# is declared as NOT NULL. NEW.a is referenced from the trigger t1_b_all
|
||||
# when it is fired on DELETE event
|
||||
DELETE FROM t1;
|
||||
SELECT * FROM t1;
|
||||
a
|
||||
SELECT * FROM t2;
|
||||
old_a new_a event_name
|
||||
1 NULL DELETING
|
||||
DROP TABLE t1, t2;
|
||||
# End of tests for MDEV-10164
|
||||
#
|
||||
# End of 11.8 tests
|
||||
#
|
||||
|
@ -2991,6 +2991,316 @@ SELECT * FROM t2;
|
||||
--echo # Clean up
|
||||
DROP TABLE t1, t2;
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-10164: Add support for TRIGGERS that fire on multiple events
|
||||
--echo #
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE TABLE t2 (a INT);
|
||||
CREATE TRIGGER t1_b_any BEFORE INSERT OR UPDATE OR DELETE ON t1 FOR EACH ROW INSERT INTO t2 VALUES (1000);
|
||||
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW INSERT INTO t2 VALUES (NEW.a);
|
||||
CREATE TRIGGER t1_bu BEFORE UPDATE ON t1 FOR EACH ROW INSERT INTO t2 VALUES (NEW.a);
|
||||
CREATE TRIGGER t1_bd BEFORE DELETE ON t1 FOR EACH ROW INSERT INTO t2 VALUES (OLD.a);
|
||||
|
||||
INSERT INTO t1 VALUES (1);
|
||||
--echo # The previous statement should fire the triggers t1_b_any and t1_bi.
|
||||
--echo # Check it. The following query should return the rows (1000), (1)
|
||||
SELECT * FROM t2;
|
||||
|
||||
--echo # Clean up the table t2 before running the next test case
|
||||
TRUNCATE TABLE t2;
|
||||
|
||||
UPDATE t1 SET a = -1;
|
||||
--echo # The previous statement should fire the triggers t1_b_any and t1_bu.
|
||||
--echo # Check it. The following query should return the rows (1000), (-1)
|
||||
SELECT * FROM t2;
|
||||
|
||||
--echo # Clean up the table t2 before running the next test case
|
||||
TRUNCATE TABLE t2;
|
||||
|
||||
DELETE FROM t1 WHERE a = -1;
|
||||
--echo # The previous statement should fire the triggers t1_b_any and t1_bu.
|
||||
--echo # Check it. The following query should return the rows (1000), (-1)
|
||||
SELECT * FROM t2;
|
||||
|
||||
--echo # Clean up
|
||||
DROP TABLE t1, t2;
|
||||
|
||||
--echo # The following test case is about handling the new clauses
|
||||
--echo # INSERTING, UPDATING, DELETING
|
||||
CREATE TABLE t1 (a INT);
|
||||
CREATE TABLE t2 (a INT, b VARCHAR(10));
|
||||
|
||||
--delimiter $
|
||||
CREATE TRIGGER t1_b_any BEFORE INSERT OR UPDATE OR DELETE ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
IF INSERTING THEN
|
||||
INSERT INTO t2 VALUES (NEW.a, 'INSERTING');
|
||||
ELSEIF UPDATING THEN
|
||||
INSERT INTO t2 VALUES (NEW.a, 'UPDATING');
|
||||
ELSEIF DELETING THEN
|
||||
INSERT INTO t2 VALUES (OLD.a, 'DELETING');
|
||||
END IF;
|
||||
END
|
||||
$
|
||||
|
||||
--delimiter ;
|
||||
|
||||
INSERT INTO t1 VALUES(100);
|
||||
UPDATE t1 SET a = 300;
|
||||
DELETE FROM t1;
|
||||
|
||||
--echo # Query results of trigger executions
|
||||
SELECT * FROM t2;
|
||||
|
||||
--echo # Check that SHOW TRIGGERS outputs data about every specified event type
|
||||
--echo # in the column `event`. For the trigger t1_b_any the column `event`
|
||||
--echo # must contain the value `INSERT,UPDATE,DELETE`
|
||||
--replace_column 6 #
|
||||
SHOW TRIGGERS;
|
||||
|
||||
--echo # Check that DROP TRIGGER does work correctly
|
||||
DROP TRIGGER t1_b_any;
|
||||
--echo # No triggers in output of SHOW TRIGGERS;
|
||||
SHOW TRIGGERS;
|
||||
--echo # Clean up
|
||||
DROP TABLE t1, t2;
|
||||
|
||||
--echo #
|
||||
--echo # Check that event flag in condition must match the event type of a trigger
|
||||
--echo #
|
||||
CREATE TABLE t1 (a INT);
|
||||
--delimiter $
|
||||
--echo # The following CREATE TRIGGER statement must fail since
|
||||
--echo # the UPDATING clause can be specified only for UPDATE trigger event
|
||||
--error ER_INCOMPATIBLE_EVENT_FLAG
|
||||
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
IF UPDATING THEN
|
||||
SET @a=100;
|
||||
END IF;
|
||||
END;
|
||||
$
|
||||
--echo # The following CREATE TRIGGER statement must fail since
|
||||
--echo # the DELETING clause can be specified only for DELETE trigger event
|
||||
--error ER_INCOMPATIBLE_EVENT_FLAG
|
||||
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
IF DELETING THEN
|
||||
SET @a=100;
|
||||
END IF;
|
||||
END;
|
||||
$
|
||||
--echo # The following CREATE TRIGGER statement must fail since
|
||||
--echo # the INSERTING clause can be specified only for INSERT trigger event
|
||||
--error ER_INCOMPATIBLE_EVENT_FLAG
|
||||
CREATE TRIGGER t1_bi BEFORE UPDATE ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
IF INSERTING THEN
|
||||
SET @a=100;
|
||||
END IF;
|
||||
END;
|
||||
$
|
||||
--echo # The following CREATE TRIGGER statement must fail since
|
||||
--echo # the INSERTING clause can be specified only for INSERT trigger event
|
||||
--error ER_INCOMPATIBLE_EVENT_FLAG
|
||||
CREATE TRIGGER t1_bi BEFORE DELETE ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
IF INSERTING THEN
|
||||
SET @a=100;
|
||||
END IF;
|
||||
END;
|
||||
$
|
||||
--echo # The following CREATE TRIGGER statement must fail since
|
||||
--echo # the DELETING clause can be specified only for DELETE trigger event
|
||||
--error ER_INCOMPATIBLE_EVENT_FLAG
|
||||
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
IF DELETING THEN
|
||||
SET @a=100;
|
||||
END IF;
|
||||
END;
|
||||
$
|
||||
--echo # The following CREATE TRIGGER statement must fail since
|
||||
--echo # the DELETING clause can be specified only for DELETE trigger event
|
||||
--error ER_INCOMPATIBLE_EVENT_FLAG
|
||||
CREATE TRIGGER t1_bi BEFORE UPDATE ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
IF DELETING THEN
|
||||
SET @a=100;
|
||||
END IF;
|
||||
END;
|
||||
$
|
||||
--delimiter ;
|
||||
--echo # Clean up
|
||||
DROP TABLE t1;
|
||||
|
||||
--echo # Check that NEW and OLD clause is handled correctly for different trigger
|
||||
--echo # event types.
|
||||
--echo # For INSERT trigger, referencing to a table's column via the OLD clause
|
||||
--echo # should return the NULL value; for DELETE trigger, referencing to a table's
|
||||
--echo # column via the NEW clause should return the NULL value.
|
||||
|
||||
--echo # First, check that for integral types
|
||||
# Cursor protocol be enabled as soon as the bug MDEV-36258 be fixed
|
||||
--disable_cursor_protocol
|
||||
CREATE TABLE t1 (a INT, b TINYINT, c SMALLINT, d MEDIUMINT, e BIGINT, f DECIMAL, g FLOAT, h DOUBLE);
|
||||
CREATE TABLE t2 (old_a INT, new_a INT, old_b TINYINT, new_b TINYINT, old_c SMALLINT, new_c SMALLINT, old_d MEDIUMINT, new_d MEDIUMINT, old_e BIGINT, new_e BIGINT, old_f DECIMAL, new_f DECIMAL, old_g FLOAT, new_g FLOAT, old_h DOUBLE, new_h DOUBLE, event_name VARCHAR(20));
|
||||
|
||||
--delimiter $
|
||||
CREATE TRIGGER t1_b_all BEFORE INSERT OR UPDATE OR DELETE ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
IF INSERTING THEN
|
||||
INSERT INTO t2 VALUES (OLD.a, NEW.a, OLD.b, NEW.b, OLD.c, NEW.c, OLD.d, NEW.d, OLD.e, NEW.e, OLD.f, NEW.f, OLD.g, NEW.g, OLD.h, NEW.h, 'INSERTING');
|
||||
SET @old_a = OLD.a;
|
||||
SET @new_a = NEW.a;
|
||||
SET @old_b = OLD.b;
|
||||
SET @new_b = NEW.b;
|
||||
SET @old_c = OLD.c;
|
||||
SET @new_c = NEW.c;
|
||||
SET @old_d = OLD.d;
|
||||
SET @new_d = NEW.d;
|
||||
SET @old_e = OLD.e;
|
||||
SET @new_e = NEW.e;
|
||||
SET @old_f = OLD.f;
|
||||
SET @new_f = NEW.f;
|
||||
SET @old_g = OLD.g;
|
||||
SET @new_g = NEW.g;
|
||||
SET @old_h = OLD.h;
|
||||
SET @new_h = NEW.h;
|
||||
ELSEIF UPDATING THEN
|
||||
INSERT INTO t2 VALUES (OLD.a, NEW.a, OLD.b, NEW.b, OLD.c, NEW.c, OLD.d, NEW.d, OLD.e, NEW.e, OLD.f, NEW.f, OLD.g, NEW.g, OLD.h, NEW.h, 'UPDATING');
|
||||
SET @old_a = OLD.a;
|
||||
SET @new_a = NEW.a;
|
||||
SET @old_b = OLD.b;
|
||||
SET @new_b = NEW.b;
|
||||
SET @old_c = OLD.c;
|
||||
SET @new_c = NEW.c;
|
||||
SET @old_d = OLD.d;
|
||||
SET @new_d = NEW.d;
|
||||
SET @old_e = OLD.e;
|
||||
SET @new_e = NEW.e;
|
||||
SET @old_f = OLD.f;
|
||||
SET @new_f = NEW.f;
|
||||
SET @old_g = OLD.g;
|
||||
SET @new_g = NEW.g;
|
||||
SET @old_h = OLD.h;
|
||||
SET @new_h = NEW.h;
|
||||
ELSEIF DELETING THEN
|
||||
INSERT INTO t2 VALUES (OLD.a, NEW.a, OLD.b, NEW.b, OLD.c, NEW.c, OLD.d, NEW.d, OLD.e, NEW.e, OLD.f, NEW.f, OLD.g, NEW.g, OLD.h, NEW.h, 'DELETING');
|
||||
SET @old_a = OLD.a;
|
||||
SET @new_a = NEW.a;
|
||||
SET @old_b = OLD.b;
|
||||
SET @new_b = NEW.b;
|
||||
SET @old_c = OLD.c;
|
||||
SET @new_c = NEW.c;
|
||||
SET @old_d = OLD.d;
|
||||
SET @new_d = NEW.d;
|
||||
SET @old_e = OLD.e;
|
||||
SET @new_e = NEW.e;
|
||||
SET @old_f = OLD.f;
|
||||
SET @new_f = NEW.f;
|
||||
SET @old_g = OLD.g;
|
||||
SET @new_g = NEW.g;
|
||||
SET @old_h = OLD.h;
|
||||
SET @new_h = NEW.h;
|
||||
END IF;
|
||||
END
|
||||
$
|
||||
|
||||
--delimiter ;
|
||||
|
||||
INSERT INTO t1 VALUES (100, 100, 100, 100, 100, 100, 100, 100);
|
||||
SELECT @old_a, @new_a, @old_b, @new_b, @old_c, @new_c, @old_d, @new_d, @old_e, @new_e, @old_f, @new_f, @old_g, @new_g, @old_h, @new_h;
|
||||
UPDATE t1 SET a = 150, b=110, c=150, d=150, e=150, f=150, g=150, h=150 WHERE a = 100;
|
||||
SELECT @old_a, @new_a, @old_b, @new_b, @old_c, @new_c, @old_d, @new_d, @old_e, @new_e, @old_f, @new_f, @old_g, @new_g, @old_h, @new_h;
|
||||
DELETE FROM t1 WHERE a = 150;
|
||||
SELECT @old_a, @new_a, @old_b, @new_b, @old_c, @new_c, @old_d, @new_d, @old_e, @new_e, @old_f, @new_f, @old_g, @new_g, @old_h, @new_h;
|
||||
|
||||
SELECT * FROM t2;
|
||||
|
||||
--echo # Clean up
|
||||
DROP TABLE t1, t2;
|
||||
--enable_cursor_protocol
|
||||
|
||||
--echo # Then, check this assertion for string types
|
||||
CREATE TABLE t1 (a CHAR(10), b VARCHAR(10));
|
||||
CREATE TABLE t2 (old_a CHAR(10), new_a CHAR(10), old_b VARCHAR(10), new_b VARCHAR(10), event_name VARCHAR(20));
|
||||
|
||||
--delimiter $
|
||||
CREATE TRIGGER t1_b_all BEFORE INSERT OR UPDATE OR DELETE ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
IF INSERTING THEN
|
||||
INSERT INTO t2 VALUES (OLD.a, NEW.a, OLD.b, NEW.b, 'INSERTING');
|
||||
SET @old_a = OLD.a;
|
||||
SET @new_a = NEW.a;
|
||||
SET @old_b = OLD.b;
|
||||
SET @new_b = NEW.b;
|
||||
ELSEIF UPDATING THEN
|
||||
INSERT INTO t2 VALUES (OLD.a, NEW.a, OLD.b, NEW.b, 'UPDATING');
|
||||
SET @old_a = OLD.a;
|
||||
SET @new_a = NEW.a;
|
||||
SET @old_b = OLD.b;
|
||||
SET @new_b = NEW.b;
|
||||
ELSEIF DELETING THEN
|
||||
INSERT INTO t2 VALUES (OLD.a, NEW.a, OLD.b, NEW.b, 'DELETING');
|
||||
SET @old_a = OLD.a;
|
||||
SET @new_a = NEW.a;
|
||||
SET @old_b = OLD.b;
|
||||
SET @new_b = NEW.b;
|
||||
END IF;
|
||||
END
|
||||
$
|
||||
|
||||
--delimiter ;
|
||||
|
||||
INSERT INTO t1 VALUES ('aaa', 'bbb');
|
||||
SELECT @old_a, @new_a, @old_b, @new_b;
|
||||
UPDATE t1 SET a = 'AAA', b = 'BBB' WHERE a = 'aaa';
|
||||
SELECT @old_a, @new_a, @old_b, @new_b;
|
||||
DELETE FROM t1 WHERE a = 'AAA';
|
||||
SELECT @old_a, @new_a, @old_b, @new_b;
|
||||
|
||||
SELECT * FROM t2;
|
||||
|
||||
--echo # Clean up
|
||||
DROP TABLE t1, t2;
|
||||
|
||||
--echo # Test for a column declared as NOT NULL
|
||||
CREATE TABLE t1 (a INT NOT NULL);
|
||||
CREATE TABLE t2 (old_a INT, new_a INT, event_name VARCHAR(20));
|
||||
|
||||
--delimiter $
|
||||
CREATE TRIGGER t1_b_all BEFORE INSERT OR UPDATE OR DELETE ON t1 FOR EACH ROW
|
||||
BEGIN
|
||||
IF INSERTING THEN
|
||||
INSERT INTO t2 VALUES (OLD.a, NEW.a, 'INSERTING');
|
||||
ELSEIF DELETING THEN
|
||||
INSERT INTO t2 VALUES (OLD.a, NEW.a, 'DELETING');
|
||||
END IF;
|
||||
END;
|
||||
$
|
||||
--delimiter ;
|
||||
--echo # Check that OLD.a is NULL in spite of the fact that the column `a`
|
||||
--echo # is declared as NOT NULL. OLD.a is referenced from the trigger t1_b_all
|
||||
--echo # when it is fired on INSERT event
|
||||
INSERT INTO t1 VALUES (1);
|
||||
|
||||
SELECT * FROM t1;
|
||||
SELECT * FROM t2;
|
||||
|
||||
TRUNCATE TABLE t2;
|
||||
|
||||
--echo # Check that NEL.a is NULL in spite of the fact that the column `a`
|
||||
--echo # is declared as NOT NULL. NEW.a is referenced from the trigger t1_b_all
|
||||
--echo # when it is fired on DELETE event
|
||||
DELETE FROM t1;
|
||||
|
||||
SELECT * FROM t1;
|
||||
SELECT * FROM t2;
|
||||
|
||||
DROP TABLE t1, t2;
|
||||
--echo # End of tests for MDEV-10164
|
||||
|
||||
--echo #
|
||||
--echo # End of 11.8 tests
|
||||
--echo #
|
||||
|
@ -563,7 +563,7 @@ def information_schema TRIGGERS COLLATION_CONNECTION 21 NULL NO varchar 64 192 N
|
||||
def information_schema TRIGGERS CREATED 17 NULL YES datetime NULL NULL NULL NULL 2 NULL NULL datetime(2) select NEVER NULL NO NO
|
||||
def information_schema TRIGGERS DATABASE_COLLATION 22 NULL NO varchar 64 192 NULL NULL NULL utf8mb3 utf8mb3_general_ci varchar(64) select NEVER NULL NO NO
|
||||
def information_schema TRIGGERS DEFINER 19 NULL NO varchar 384 1152 NULL NULL NULL utf8mb3 utf8mb3_general_ci varchar(384) select NEVER NULL NO NO
|
||||
def information_schema TRIGGERS EVENT_MANIPULATION 4 NULL NO varchar 6 18 NULL NULL NULL utf8mb3 utf8mb3_general_ci varchar(6) select NEVER NULL NO NO
|
||||
def information_schema TRIGGERS EVENT_MANIPULATION 4 NULL NO varchar 20 60 NULL NULL NULL utf8mb3 utf8mb3_general_ci varchar(20) select NEVER NULL NO NO
|
||||
def information_schema TRIGGERS EVENT_OBJECT_CATALOG 5 NULL NO varchar 512 1536 NULL NULL NULL utf8mb3 utf8mb3_general_ci varchar(512) select NEVER NULL NO NO
|
||||
def information_schema TRIGGERS EVENT_OBJECT_SCHEMA 6 NULL NO varchar 64 192 NULL NULL NULL utf8mb3 utf8mb3_general_ci varchar(64) select NEVER NULL NO NO
|
||||
def information_schema TRIGGERS EVENT_OBJECT_TABLE 7 NULL NO varchar 64 192 NULL NULL NULL utf8mb3 utf8mb3_general_ci varchar(64) select NEVER NULL NO NO
|
||||
@ -1229,7 +1229,7 @@ NULL information_schema TABLE_STATISTICS PAGES_READ_FROM_DISK bigint NULL NULL N
|
||||
3.0000 information_schema TRIGGERS TRIGGER_CATALOG varchar 512 1536 utf8mb3 utf8mb3_general_ci varchar(512)
|
||||
3.0000 information_schema TRIGGERS TRIGGER_SCHEMA varchar 64 192 utf8mb3 utf8mb3_general_ci varchar(64)
|
||||
3.0000 information_schema TRIGGERS TRIGGER_NAME varchar 64 192 utf8mb3 utf8mb3_general_ci varchar(64)
|
||||
3.0000 information_schema TRIGGERS EVENT_MANIPULATION varchar 6 18 utf8mb3 utf8mb3_general_ci varchar(6)
|
||||
3.0000 information_schema TRIGGERS EVENT_MANIPULATION varchar 20 60 utf8mb3 utf8mb3_general_ci varchar(20)
|
||||
3.0000 information_schema TRIGGERS EVENT_OBJECT_CATALOG varchar 512 1536 utf8mb3 utf8mb3_general_ci varchar(512)
|
||||
3.0000 information_schema TRIGGERS EVENT_OBJECT_SCHEMA varchar 64 192 utf8mb3 utf8mb3_general_ci varchar(64)
|
||||
3.0000 information_schema TRIGGERS EVENT_OBJECT_TABLE varchar 64 192 utf8mb3 utf8mb3_general_ci varchar(64)
|
||||
|
@ -496,7 +496,7 @@ def information_schema TRIGGERS COLLATION_CONNECTION 21 NULL NO varchar 64 192 N
|
||||
def information_schema TRIGGERS CREATED 17 NULL YES datetime NULL NULL NULL NULL 2 NULL NULL datetime(2) NEVER NULL NO NO
|
||||
def information_schema TRIGGERS DATABASE_COLLATION 22 NULL NO varchar 64 192 NULL NULL NULL utf8mb3 utf8mb3_general_ci varchar(64) NEVER NULL NO NO
|
||||
def information_schema TRIGGERS DEFINER 19 NULL NO varchar 384 1152 NULL NULL NULL utf8mb3 utf8mb3_general_ci varchar(384) NEVER NULL NO NO
|
||||
def information_schema TRIGGERS EVENT_MANIPULATION 4 NULL NO varchar 6 18 NULL NULL NULL utf8mb3 utf8mb3_general_ci varchar(6) NEVER NULL NO NO
|
||||
def information_schema TRIGGERS EVENT_MANIPULATION 4 NULL NO varchar 20 60 NULL NULL NULL utf8mb3 utf8mb3_general_ci varchar(20) NEVER NULL NO NO
|
||||
def information_schema TRIGGERS EVENT_OBJECT_CATALOG 5 NULL NO varchar 512 1536 NULL NULL NULL utf8mb3 utf8mb3_general_ci varchar(512) NEVER NULL NO NO
|
||||
def information_schema TRIGGERS EVENT_OBJECT_SCHEMA 6 NULL NO varchar 64 192 NULL NULL NULL utf8mb3 utf8mb3_general_ci varchar(64) NEVER NULL NO NO
|
||||
def information_schema TRIGGERS EVENT_OBJECT_TABLE 7 NULL NO varchar 64 192 NULL NULL NULL utf8mb3 utf8mb3_general_ci varchar(64) NEVER NULL NO NO
|
||||
@ -1094,7 +1094,7 @@ NULL information_schema TABLE_STATISTICS PAGES_READ_FROM_DISK bigint NULL NULL N
|
||||
3.0000 information_schema TRIGGERS TRIGGER_CATALOG varchar 512 1536 utf8mb3 utf8mb3_general_ci varchar(512)
|
||||
3.0000 information_schema TRIGGERS TRIGGER_SCHEMA varchar 64 192 utf8mb3 utf8mb3_general_ci varchar(64)
|
||||
3.0000 information_schema TRIGGERS TRIGGER_NAME varchar 64 192 utf8mb3 utf8mb3_general_ci varchar(64)
|
||||
3.0000 information_schema TRIGGERS EVENT_MANIPULATION varchar 6 18 utf8mb3 utf8mb3_general_ci varchar(6)
|
||||
3.0000 information_schema TRIGGERS EVENT_MANIPULATION varchar 20 60 utf8mb3 utf8mb3_general_ci varchar(20)
|
||||
3.0000 information_schema TRIGGERS EVENT_OBJECT_CATALOG varchar 512 1536 utf8mb3 utf8mb3_general_ci varchar(512)
|
||||
3.0000 information_schema TRIGGERS EVENT_OBJECT_SCHEMA varchar 64 192 utf8mb3 utf8mb3_general_ci varchar(64)
|
||||
3.0000 information_schema TRIGGERS EVENT_OBJECT_TABLE varchar 64 192 utf8mb3 utf8mb3_general_ci varchar(64)
|
||||
|
@ -33,7 +33,7 @@ Field Type Null Key Default Extra
|
||||
TRIGGER_CATALOG varchar(512) NO NULL
|
||||
TRIGGER_SCHEMA varchar(64) NO NULL
|
||||
TRIGGER_NAME varchar(64) NO NULL
|
||||
EVENT_MANIPULATION varchar(6) NO NULL
|
||||
EVENT_MANIPULATION varchar(20) NO NULL
|
||||
EVENT_OBJECT_CATALOG varchar(512) NO NULL
|
||||
EVENT_OBJECT_SCHEMA varchar(64) NO NULL
|
||||
EVENT_OBJECT_TABLE varchar(64) NO NULL
|
||||
@ -58,7 +58,7 @@ TRIGGERS CREATE TEMPORARY TABLE `TRIGGERS` (
|
||||
`TRIGGER_CATALOG` varchar(512) NOT NULL,
|
||||
`TRIGGER_SCHEMA` varchar(64) NOT NULL,
|
||||
`TRIGGER_NAME` varchar(64) NOT NULL,
|
||||
`EVENT_MANIPULATION` varchar(6) NOT NULL,
|
||||
`EVENT_MANIPULATION` varchar(20) NOT NULL,
|
||||
`EVENT_OBJECT_CATALOG` varchar(512) NOT NULL,
|
||||
`EVENT_OBJECT_SCHEMA` varchar(64) NOT NULL,
|
||||
`EVENT_OBJECT_TABLE` varchar(64) NOT NULL,
|
||||
@ -83,7 +83,7 @@ Field Type Null Key Default Extra
|
||||
TRIGGER_CATALOG varchar(512) NO NULL
|
||||
TRIGGER_SCHEMA varchar(64) NO NULL
|
||||
TRIGGER_NAME varchar(64) NO NULL
|
||||
EVENT_MANIPULATION varchar(6) NO NULL
|
||||
EVENT_MANIPULATION varchar(20) NO NULL
|
||||
EVENT_OBJECT_CATALOG varchar(512) NO NULL
|
||||
EVENT_OBJECT_SCHEMA varchar(64) NO NULL
|
||||
EVENT_OBJECT_TABLE varchar(64) NO NULL
|
||||
|
@ -33,7 +33,7 @@ Field Type Null Key Default Extra
|
||||
TRIGGER_CATALOG varchar(512) NO NULL
|
||||
TRIGGER_SCHEMA varchar(64) NO NULL
|
||||
TRIGGER_NAME varchar(64) NO NULL
|
||||
EVENT_MANIPULATION varchar(6) NO NULL
|
||||
EVENT_MANIPULATION varchar(20) NO NULL
|
||||
EVENT_OBJECT_CATALOG varchar(512) NO NULL
|
||||
EVENT_OBJECT_SCHEMA varchar(64) NO NULL
|
||||
EVENT_OBJECT_TABLE varchar(64) NO NULL
|
||||
@ -58,7 +58,7 @@ TRIGGERS CREATE TEMPORARY TABLE `TRIGGERS` (
|
||||
`TRIGGER_CATALOG` varchar(512) NOT NULL,
|
||||
`TRIGGER_SCHEMA` varchar(64) NOT NULL,
|
||||
`TRIGGER_NAME` varchar(64) NOT NULL,
|
||||
`EVENT_MANIPULATION` varchar(6) NOT NULL,
|
||||
`EVENT_MANIPULATION` varchar(20) NOT NULL,
|
||||
`EVENT_OBJECT_CATALOG` varchar(512) NOT NULL,
|
||||
`EVENT_OBJECT_SCHEMA` varchar(64) NOT NULL,
|
||||
`EVENT_OBJECT_TABLE` varchar(64) NOT NULL,
|
||||
@ -83,7 +83,7 @@ Field Type Null Key Default Extra
|
||||
TRIGGER_CATALOG varchar(512) NO NULL
|
||||
TRIGGER_SCHEMA varchar(64) NO NULL
|
||||
TRIGGER_NAME varchar(64) NO NULL
|
||||
EVENT_MANIPULATION varchar(6) NO NULL
|
||||
EVENT_MANIPULATION varchar(20) NO NULL
|
||||
EVENT_OBJECT_CATALOG varchar(512) NO NULL
|
||||
EVENT_OBJECT_SCHEMA varchar(64) NO NULL
|
||||
EVENT_OBJECT_TABLE varchar(64) NO NULL
|
||||
|
71
sql/item.cc
71
sql/item.cc
@ -10360,6 +10360,25 @@ bool Item_trigger_field::set_value(THD *thd, sp_rcontext * /*ctx*/, Item **it)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Check whether a value of the clause OLD or NEW can produce meaning value:
|
||||
for INSERT event, evaluation of the OLD clause should return NULL;
|
||||
for DELETE event, evaluation of the NEW clause should return NULL.
|
||||
|
||||
@param thd current thread context
|
||||
*/
|
||||
|
||||
void
|
||||
Item_trigger_field::check_new_old_qulifiers_comform_with_trg_event(THD *thd)
|
||||
{
|
||||
if ((thd->current_trg_event() == TRG_EVENT_INSERT && row_version == OLD_ROW) ||
|
||||
(thd->current_trg_event() == TRG_EVENT_DELETE && row_version == NEW_ROW))
|
||||
null_value= true;
|
||||
else
|
||||
null_value= false;
|
||||
}
|
||||
|
||||
|
||||
bool Item_trigger_field::fix_fields(THD *thd, Item **items)
|
||||
{
|
||||
/*
|
||||
@ -10395,6 +10414,7 @@ bool Item_trigger_field::fix_fields(THD *thd, Item **items)
|
||||
field= (row_version == OLD_ROW) ? triggers->old_field[field_idx] :
|
||||
triggers->new_field[field_idx];
|
||||
set_field(field);
|
||||
check_new_old_qulifiers_comform_with_trg_event(thd);
|
||||
base_flags|= item_base_t::FIXED;
|
||||
return FALSE;
|
||||
}
|
||||
@ -10420,6 +10440,52 @@ bool Item_trigger_field::check_vcol_func_processor(void *arg)
|
||||
}
|
||||
|
||||
|
||||
int Item_trigger_field::save_in_field(Field *to, bool no_conversions)
|
||||
{
|
||||
if (null_value)
|
||||
return set_field_to_null_with_conversions(to, no_conversions);
|
||||
|
||||
return Item_field::save_in_field(to, no_conversions);
|
||||
}
|
||||
|
||||
|
||||
double Item_trigger_field::val_real()
|
||||
{
|
||||
if (null_value)
|
||||
return 0.0;
|
||||
return Item_field::val_real();
|
||||
}
|
||||
|
||||
longlong Item_trigger_field::val_int()
|
||||
{
|
||||
if (null_value)
|
||||
return 0;
|
||||
return Item_field::val_int();
|
||||
}
|
||||
|
||||
bool Item_trigger_field::val_bool()
|
||||
{
|
||||
if (null_value)
|
||||
return false;
|
||||
return Item_field::val_bool();
|
||||
}
|
||||
|
||||
my_decimal *
|
||||
Item_trigger_field::val_decimal(my_decimal *decimal_value)
|
||||
{
|
||||
if (null_value)
|
||||
return 0;
|
||||
return Item_field::val_decimal(decimal_value);
|
||||
}
|
||||
|
||||
String *
|
||||
Item_trigger_field::val_str(String *str)
|
||||
{
|
||||
if (null_value)
|
||||
return nullptr;
|
||||
return Item_field::val_str(str);
|
||||
}
|
||||
|
||||
void Item_trigger_field::cleanup()
|
||||
{
|
||||
want_privilege= original_privilege;
|
||||
@ -10497,6 +10563,11 @@ int stored_field_cmp_to_item(THD *thd, Field *field, Item *item)
|
||||
}
|
||||
|
||||
|
||||
bool Item_trigger_type_of_statement::val_bool()
|
||||
{
|
||||
return m_trigger_stmt_type == m_thd->current_active_stmt();
|
||||
}
|
||||
|
||||
void Item_cache::store(Item *item)
|
||||
{
|
||||
example= item;
|
||||
|
37
sql/item.h
37
sql/item.h
@ -28,6 +28,8 @@
|
||||
#include <typeinfo>
|
||||
|
||||
#include "cset_narrowing.h"
|
||||
#include "sql_basic_types.h"
|
||||
|
||||
|
||||
C_MODE_START
|
||||
#include <ma_dyncol.h>
|
||||
@ -7349,7 +7351,7 @@ private:
|
||||
privilege_t want_privilege;
|
||||
public:
|
||||
|
||||
Item_trigger_field(THD *thd, Name_resolution_context *context_arg,
|
||||
Item_trigger_field(THD *thd, Name_resolution_context *context_arg,
|
||||
row_version_type row_ver_arg,
|
||||
const LEX_CSTRING &field_name_arg,
|
||||
privilege_t priv, const bool ro)
|
||||
@ -7378,6 +7380,7 @@ private:
|
||||
void set_required_privilege(bool rw) override;
|
||||
bool set_value(THD *thd, sp_rcontext *ctx, Item **it) override;
|
||||
|
||||
void check_new_old_qulifiers_comform_with_trg_event(THD *thd);
|
||||
public:
|
||||
Settable_routine_parameter *get_settable_routine_parameter() override
|
||||
{
|
||||
@ -7392,6 +7395,38 @@ public:
|
||||
public:
|
||||
bool unknown_splocal_processor(void *) override { return false; }
|
||||
bool check_vcol_func_processor(void *arg) override;
|
||||
|
||||
int save_in_field(Field *to, bool no_conversions) override;
|
||||
double val_real() override;
|
||||
longlong val_int() override;
|
||||
bool val_bool() override;
|
||||
my_decimal *val_decimal(my_decimal *) override;
|
||||
String *val_str(String*) override;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
This item is instantiated in case one of the clauses
|
||||
INSERTING, UPDATING, DELETING
|
||||
encountered in trigger's body. The method val_bool() of this class returns
|
||||
true if currently running DML statement matches the type of DML
|
||||
activity (insert, update, delete) describing by the one of the clauses
|
||||
INSERTING, UPDATING, DELETING
|
||||
*/
|
||||
|
||||
class Item_trigger_type_of_statement : public Item_int
|
||||
{
|
||||
public:
|
||||
Item_trigger_type_of_statement(THD *thd,
|
||||
active_dml_stmt stmt_type)
|
||||
: Item_int(thd, 0), m_thd{thd}, m_trigger_stmt_type{stmt_type}
|
||||
{}
|
||||
|
||||
bool val_bool() override;
|
||||
|
||||
private:
|
||||
THD *m_thd;
|
||||
active_dml_stmt m_trigger_stmt_type;
|
||||
};
|
||||
|
||||
|
||||
|
@ -12299,3 +12299,5 @@ ER_SIGNAL_SKIP_ROW_FROM_TRIGGER
|
||||
eng "The row is skipped by a trigger implementation"
|
||||
ER_TEMPORARY_TABLES_PREVENT_SWITCH_GTID_DOMAIN_ID
|
||||
eng "Cannot modify @@session.gtid_domain_id while there are open temporary tables being binlogged"
|
||||
ER_INCOMPATIBLE_EVENT_FLAG
|
||||
eng "Event flag '%s' in the condition expression is not compatible with the trigger event type '%s'"
|
||||
|
@ -842,7 +842,7 @@ LEX* sp_lex_instr::parse_expr(THD *thd, sp_head *sp, LEX *sp_instr_lex)
|
||||
*/
|
||||
thd->lex->trg_chistics.action_time=
|
||||
thd->spcont->m_sp->m_trg->action_time;
|
||||
thd->lex->trg_chistics.event= thd->spcont->m_sp->m_trg->event;
|
||||
thd->lex->trg_chistics.events= thd->spcont->m_sp->m_trg->events;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -338,4 +338,12 @@ static const time_round_mode_t
|
||||
TIME_FRAC_ROUND (time_round_mode_t::FRAC_ROUND);
|
||||
|
||||
|
||||
enum class active_dml_stmt
|
||||
{
|
||||
NO_DML_STMT= 0,
|
||||
INSERTING_STMT= 1,
|
||||
UPDATING_STMT= 2,
|
||||
DELETING_STMT= 3
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -4225,6 +4225,62 @@ void Statement::restore_backup_statement(Statement *stmt, Statement *backup)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Get a type of DML statement currently is running
|
||||
*/
|
||||
|
||||
active_dml_stmt Statement::current_active_stmt()
|
||||
{
|
||||
return *m_running_stmts.back();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Store information about a type of the current DML statement being executed
|
||||
*/
|
||||
|
||||
bool Statement::push_active_stmt(active_dml_stmt new_active_stmt)
|
||||
{
|
||||
return m_running_stmts.push(new_active_stmt);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Remove information about a type of completed DML statement
|
||||
*/
|
||||
|
||||
void Statement::pop_current_active_stmt()
|
||||
{
|
||||
m_running_stmts.pop();
|
||||
}
|
||||
|
||||
|
||||
trg_event_type Statement::current_trg_event()
|
||||
{
|
||||
/*
|
||||
current_trg_event() is called indirectly by Item_trigger_field::fix_fields
|
||||
both on handling DML statements INSERT/UPDATE/DELETE and DDL statement
|
||||
CREATE TRIGGER. For the last one, m_running_trgs is empty since the
|
||||
method push_current_trg_event() is run only on processing triggers, not on
|
||||
thier creation. So take care about this case.
|
||||
*/
|
||||
if (unlikely(m_running_trgs.elements() == 0))
|
||||
return TRG_EVENT_MAX;
|
||||
return *m_running_trgs.back();
|
||||
}
|
||||
|
||||
|
||||
bool Statement::push_current_trg_event(trg_event_type trg_event)
|
||||
{
|
||||
return m_running_trgs.push(trg_event);
|
||||
}
|
||||
|
||||
|
||||
void Statement::pop_current_trg_event()
|
||||
{
|
||||
m_running_trgs.pop();
|
||||
}
|
||||
|
||||
void THD::end_statement()
|
||||
{
|
||||
DBUG_ENTER("THD::end_statement");
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <atomic>
|
||||
#include "dur_prop.h"
|
||||
#include <waiting_threads.h>
|
||||
#include "sql_array.h"
|
||||
#include "sql_const.h"
|
||||
#include "lex_ident.h"
|
||||
#include "sql_used.h"
|
||||
@ -55,6 +56,8 @@
|
||||
#include "scope.h"
|
||||
#include "ddl_log.h" /* DDL_LOG_STATE */
|
||||
#include "ha_handler_stats.h" // ha_handler_stats */
|
||||
#include "sql_basic_types.h" // enum class active_dml_stmt
|
||||
#include "sql_trigger.h"
|
||||
|
||||
extern "C"
|
||||
void set_thd_stage_info(void *thd,
|
||||
@ -1707,6 +1710,63 @@ public:
|
||||
void restore_backup_statement(Statement *stmt, Statement *backup);
|
||||
/* return class type */
|
||||
Type type() const override;
|
||||
|
||||
private:
|
||||
Dynamic_array<active_dml_stmt> m_running_stmts{PSI_INSTRUMENT_MEM};
|
||||
|
||||
/**
|
||||
Stack of events of triggers being invoked on running a DML statement.
|
||||
E.g. if there is a trigger BEFORE INSERT ON t1 that calls the statement
|
||||
`DELETE FROM t2` and there is a BEFORE DELETE trigger for the table t2
|
||||
that runs the statement `UPDATE t3` and there is a BEFORE UPDATE trigger
|
||||
for the table t3 then at the moment when the statement `UPDATE t3 ...`
|
||||
be invoked, the stack m_running_trgs would contain the following events:
|
||||
top -> TRG_EVENT_UPDATE
|
||||
TRG_EVENT_DELETE
|
||||
bottom ->TRG_EVENT_INSERT
|
||||
}
|
||||
*/
|
||||
Dynamic_array<trg_event_type> m_running_trgs{PSI_INSTRUMENT_MEM};
|
||||
|
||||
public:
|
||||
active_dml_stmt current_active_stmt();
|
||||
bool push_active_stmt(active_dml_stmt new_active_stmt);
|
||||
void pop_current_active_stmt();
|
||||
|
||||
trg_event_type current_trg_event();
|
||||
bool push_current_trg_event(trg_event_type trg_event);
|
||||
void pop_current_trg_event();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
This class is responsible for storing a kind of current DML statement
|
||||
for further matching with type of statement represented by the clauses
|
||||
INSERTING / UPDATING / DELETING.
|
||||
On handling the statements INSERT / UPDATE / DELETE the corresponding type
|
||||
of the statement specified by the enum active_dml_stmt is pushed on top of
|
||||
the Statement's stack in constructor of the class Running_stmt_guard and
|
||||
popped up on finishing execution of the statement by destructor of the class
|
||||
Running_stmt_guard.
|
||||
Every time when the one of the clauses INSERTING / UPDATING / DELETING
|
||||
is evaluated, the last pushed type of DML statement matched with the type
|
||||
representing by the clause INSERTING / UPDATING / DELETING.
|
||||
@see Item_trigger_type_of_statement::val_bool()
|
||||
*/
|
||||
class Running_stmt_guard
|
||||
{
|
||||
Statement *m_stmt;
|
||||
public:
|
||||
Running_stmt_guard(Statement *stmt,
|
||||
active_dml_stmt new_active_stmt)
|
||||
: m_stmt{stmt}
|
||||
{
|
||||
m_stmt->push_active_stmt(new_active_stmt);
|
||||
}
|
||||
~Running_stmt_guard()
|
||||
{
|
||||
m_stmt->pop_current_active_stmt();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
@ -2049,6 +2049,8 @@ err:
|
||||
|
||||
bool Sql_cmd_delete::execute_inner(THD *thd)
|
||||
{
|
||||
Running_stmt_guard guard(thd, active_dml_stmt::DELETING_STMT);
|
||||
|
||||
if (!multitable)
|
||||
{
|
||||
if (lex->has_returning())
|
||||
|
@ -738,7 +738,7 @@ bool mysql_insert(THD *thd, TABLE_LIST *table_list,
|
||||
Name_resolution_context_state ctx_state;
|
||||
SELECT_LEX *returning= thd->lex->has_returning() ? thd->lex->returning() : 0;
|
||||
unsigned char *readbuff= NULL;
|
||||
|
||||
Running_stmt_guard guard(thd, active_dml_stmt::INSERTING_STMT);
|
||||
#ifndef EMBEDDED_LIBRARY
|
||||
char *query= thd->query();
|
||||
/*
|
||||
|
@ -220,8 +220,8 @@ bool LEX::set_trigger_new_row(const LEX_CSTRING *name, Item *val,
|
||||
val= new (thd->mem_root) Item_null(thd);
|
||||
|
||||
DBUG_ASSERT(trg_chistics.action_time == TRG_ACTION_BEFORE &&
|
||||
(trg_chistics.event == TRG_EVENT_INSERT ||
|
||||
trg_chistics.event == TRG_EVENT_UPDATE));
|
||||
(is_trg_event_on(trg_chistics.events, TRG_EVENT_INSERT) ||
|
||||
is_trg_event_on(trg_chistics.events, TRG_EVENT_UPDATE)));
|
||||
|
||||
trg_fld= new (thd->mem_root)
|
||||
Item_trigger_field(thd, current_context(),
|
||||
@ -8253,21 +8253,39 @@ Item *LEX::create_and_link_Item_trigger_field(THD *thd,
|
||||
{
|
||||
Item_trigger_field *trg_fld;
|
||||
|
||||
if (unlikely(trg_chistics.event == TRG_EVENT_INSERT && !new_row))
|
||||
if (unlikely(is_trg_event_on(trg_chistics.events, TRG_EVENT_INSERT) &&
|
||||
!new_row &&
|
||||
/*
|
||||
OLD is not compatible only with INSERT event, so
|
||||
emits the error in case neither UPDATE nor DELETE
|
||||
is also specified for in the trigger definition
|
||||
*/
|
||||
|
||||
!(is_trg_event_on(trg_chistics.events,TRG_EVENT_UPDATE) ||
|
||||
is_trg_event_on(trg_chistics.events,TRG_EVENT_DELETE))))
|
||||
{
|
||||
my_error(ER_TRG_NO_SUCH_ROW_IN_TRG, MYF(0), "OLD", "on INSERT");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (unlikely(trg_chistics.event == TRG_EVENT_DELETE && new_row))
|
||||
if (unlikely(is_trg_event_on(trg_chistics.events, TRG_EVENT_DELETE) &&
|
||||
new_row &&
|
||||
/*
|
||||
NEW is not compatible only with DELETE event, so
|
||||
emits the error in case neither UPDATE nor INSERT
|
||||
is also specified for in the trigger definition
|
||||
*/
|
||||
!(is_trg_event_on(trg_chistics.events,TRG_EVENT_UPDATE) ||
|
||||
is_trg_event_on(trg_chistics.events,TRG_EVENT_INSERT))
|
||||
))
|
||||
{
|
||||
my_error(ER_TRG_NO_SUCH_ROW_IN_TRG, MYF(0), "NEW", "on DELETE");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DBUG_ASSERT(!new_row ||
|
||||
(trg_chistics.event == TRG_EVENT_INSERT ||
|
||||
trg_chistics.event == TRG_EVENT_UPDATE));
|
||||
(is_trg_event_on(trg_chistics.events, TRG_EVENT_INSERT) ||
|
||||
is_trg_event_on(trg_chistics.events, TRG_EVENT_UPDATE)));
|
||||
|
||||
const bool tmp_read_only=
|
||||
!(new_row && trg_chistics.action_time == TRG_ACTION_BEFORE);
|
||||
@ -8675,6 +8693,41 @@ Item *LEX::create_item_ident(THD *thd,
|
||||
}
|
||||
|
||||
|
||||
Item *LEX::create_item_ident_trigger_specific(THD *thd,
|
||||
active_dml_stmt stmt_type,
|
||||
bool *throw_error)
|
||||
{
|
||||
if (stmt_type == active_dml_stmt::INSERTING_STMT &&
|
||||
!is_trg_event_on(trg_chistics.events, TRG_EVENT_INSERT))
|
||||
{
|
||||
my_error(ER_INCOMPATIBLE_EVENT_FLAG, MYF(0), "INSERTING",
|
||||
trg_event_type_names[trg_chistics.events].str);
|
||||
*throw_error= true;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (stmt_type == active_dml_stmt::UPDATING_STMT &&
|
||||
!is_trg_event_on(trg_chistics.events, TRG_EVENT_UPDATE))
|
||||
{
|
||||
my_error(ER_INCOMPATIBLE_EVENT_FLAG, MYF(0), "UPDATING",
|
||||
trg_event_type_names[trg_chistics.events].str);
|
||||
*throw_error= true;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (stmt_type == active_dml_stmt::DELETING_STMT &&
|
||||
!is_trg_event_on(trg_chistics.events, TRG_EVENT_DELETE))
|
||||
{
|
||||
my_error(ER_INCOMPATIBLE_EVENT_FLAG, MYF(0), "DELETING",
|
||||
trg_event_type_names[trg_chistics.events].str);
|
||||
*throw_error= true;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new (thd->mem_root) Item_trigger_type_of_statement(thd, stmt_type);
|
||||
}
|
||||
|
||||
|
||||
Item *LEX::create_item_limit(THD *thd, const Lex_ident_cli_st *ca)
|
||||
{
|
||||
DBUG_ASSERT(thd->m_parser_state->m_lip.get_buf() <= ca->pos());
|
||||
@ -8825,6 +8878,31 @@ Item *LEX::create_item_ident_sp(THD *thd, Lex_ident_sys_st *name,
|
||||
return new (thd->mem_root) Item_func_sqlerrm(thd);
|
||||
}
|
||||
|
||||
/*
|
||||
Check the supplied identifier name for reserved names having the special
|
||||
meaning in trigger context. Use this checking after call to find_variable()
|
||||
to don't break backward compatibility - names of variables is resolved
|
||||
before checking an identifier name for reserved values, so behavior of
|
||||
user's triggers that use local variable names coinciding with the reserved
|
||||
values wouldn't be changed
|
||||
*/
|
||||
bool got_error;
|
||||
Item *trigger_specific_item=
|
||||
create_item_ident_trigger_specific(thd,
|
||||
Lex_ident_sys(thd, name), &got_error);
|
||||
if (trigger_specific_item)
|
||||
/*
|
||||
trigger_specific_item != nullptr if the argument 'name' equals one of
|
||||
the following clauses `INSERTING`, `UPDATING`, `DELETING`
|
||||
*/
|
||||
return trigger_specific_item;
|
||||
else if (got_error)
|
||||
/*
|
||||
The supplied clause INSERTING or UPDATING or DELETING isn't compatible
|
||||
with the trigger event type
|
||||
*/
|
||||
return NULL;
|
||||
|
||||
if (fields_are_impossible() &&
|
||||
(current_select->parsing_place != FOR_LOOP_BOUND ||
|
||||
spcont->find_cursor(name, &unused_off, false) == NULL))
|
||||
@ -8949,7 +9027,7 @@ bool LEX::set_trigger_field(const LEX_CSTRING *name1, const LEX_CSTRING *name2,
|
||||
my_error(ER_TRG_CANT_CHANGE_ROW, MYF(0), "OLD", "");
|
||||
return true;
|
||||
}
|
||||
if (unlikely(trg_chistics.event == TRG_EVENT_DELETE))
|
||||
if (unlikely(is_trg_event_on(trg_chistics.events, TRG_EVENT_DELETE)))
|
||||
{
|
||||
my_error(ER_TRG_NO_SUCH_ROW_IN_TRG, MYF(0), "NEW", "on DELETE");
|
||||
return true;
|
||||
|
@ -1595,7 +1595,7 @@ public:
|
||||
struct st_trg_chistics: public st_trg_execution_order
|
||||
{
|
||||
enum trg_action_time_type action_time;
|
||||
enum trg_event_type event;
|
||||
trg_event_set events;
|
||||
|
||||
const char *ordering_clause_begin;
|
||||
const char *ordering_clause_end;
|
||||
@ -4074,6 +4074,44 @@ public:
|
||||
return a.is_null() ? NULL : create_item_ident(thd, &a, &b, &c);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
The wrapper around new Item_trigger_type_of_statement to simply debugging
|
||||
*/
|
||||
|
||||
Item *create_item_ident_trigger_specific(THD *thd,
|
||||
active_dml_stmt stmt_type,
|
||||
bool *throw_error);
|
||||
|
||||
|
||||
/**
|
||||
Create an item for any of the clauses INSERTING/UPDATING/DELETING used
|
||||
inside trigger body to distinguish type of a statement that fires
|
||||
the trigger in case the one was defined to be run on several events.
|
||||
*/
|
||||
|
||||
Item *create_item_ident_trigger_specific(THD *thd,
|
||||
const Lex_ident_sys &clause,
|
||||
bool *throw_error)
|
||||
{
|
||||
*throw_error= false;
|
||||
|
||||
if (Lex_ident_ci(clause).streq("INSERTING"_Lex_ident_column))
|
||||
return create_item_ident_trigger_specific(thd,
|
||||
active_dml_stmt::INSERTING_STMT,
|
||||
throw_error);
|
||||
else if (Lex_ident_ci(clause).streq("UPDATING"_Lex_ident_column))
|
||||
return create_item_ident_trigger_specific(thd,
|
||||
active_dml_stmt::UPDATING_STMT,
|
||||
throw_error);
|
||||
else if (Lex_ident_ci(clause).streq("DELETING"_Lex_ident_column))
|
||||
return create_item_ident_trigger_specific(thd,
|
||||
active_dml_stmt::DELETING_STMT,
|
||||
throw_error);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/*
|
||||
Create an item for "NEXT VALUE FOR sequence_name"
|
||||
*/
|
||||
|
@ -120,11 +120,16 @@ static const LEX_CSTRING trg_action_time_type_names[]=
|
||||
{ STRING_WITH_LEN("AFTER") }
|
||||
};
|
||||
|
||||
static const LEX_CSTRING trg_event_type_names[]=
|
||||
const LEX_CSTRING trg_event_type_names[]=
|
||||
{
|
||||
{ STRING_WITH_LEN("INSERT") },
|
||||
{ STRING_WITH_LEN("UPDATE") },
|
||||
{ STRING_WITH_LEN("DELETE") }
|
||||
{ STRING_WITH_LEN("<invalid>") }, // 0x00
|
||||
{ STRING_WITH_LEN("INSERT") }, // 0x01
|
||||
{ STRING_WITH_LEN("UPDATE") }, // 0x02
|
||||
{ STRING_WITH_LEN("INSERT,UPDATE") }, // 0x03
|
||||
{ STRING_WITH_LEN("DELETE") }, // 0x04
|
||||
{ STRING_WITH_LEN("INSERT,DELETE") }, // 0x05
|
||||
{ STRING_WITH_LEN("UPDATE,DELETE") }, // 0x06
|
||||
{ STRING_WITH_LEN("INSERT,UPDATE,DELETE") } // 0x07
|
||||
};
|
||||
|
||||
static const LEX_CSTRING sp_data_access_name[]=
|
||||
@ -7752,12 +7757,25 @@ static bool store_trigger(THD *thd, Trigger *trigger,
|
||||
table->field[0]->store(STRING_WITH_LEN("def"), cs);
|
||||
table->field[1]->store(db_name->str, db_name->length, cs);
|
||||
table->field[2]->store(trigger->name.str, trigger->name.length, cs);
|
||||
table->field[3]->store(trg_event_type_names[trigger->event].str,
|
||||
trg_event_type_names[trigger->event].length, cs);
|
||||
DBUG_ASSERT(trigger->events < trg2bit(TRG_EVENT_MAX));
|
||||
table->field[3]->store(trg_event_type_names[trigger->events].str,
|
||||
trg_event_type_names[trigger->events].length, cs);
|
||||
table->field[4]->store(STRING_WITH_LEN("def"), cs);
|
||||
table->field[5]->store(db_name->str, db_name->length, cs);
|
||||
table->field[6]->store(table_name->str, table_name->length, cs);
|
||||
table->field[7]->store(trigger->action_order);
|
||||
String buff;
|
||||
for (int i=0;i < TRG_EVENT_MAX; i++)
|
||||
{
|
||||
if (trigger->action_order[i])
|
||||
{
|
||||
if (!buff.is_empty())
|
||||
buff.append(',');
|
||||
buff.append_longlong(trigger->action_order[i]);
|
||||
}
|
||||
|
||||
table->field[7]->store(buff.ptr(), buff.length(), cs);
|
||||
}
|
||||
|
||||
table->field[9]->store(trigger_body.str, trigger_body.length, cs);
|
||||
table->field[10]->store(STRING_WITH_LEN("ROW"), cs);
|
||||
table->field[11]->store(trg_action_time_type_names[trigger->action_time].str,
|
||||
@ -7810,9 +7828,11 @@ static int get_schema_triggers_record(THD *thd, TABLE_LIST *tables,
|
||||
get_trigger((enum trg_event_type) event,
|
||||
(enum trg_action_time_type) timing) ;
|
||||
trigger;
|
||||
trigger= trigger->next)
|
||||
trigger= trigger->next[event])
|
||||
{
|
||||
if (store_trigger(thd, trigger, table, db_name, table_name))
|
||||
if (is_the_right_most_event_bit(trigger->events,
|
||||
(trg_event_type)event) &&
|
||||
store_trigger(thd, trigger, table, db_name, table_name))
|
||||
DBUG_RETURN(1);
|
||||
}
|
||||
}
|
||||
@ -10296,7 +10316,7 @@ ST_FIELD_INFO triggers_fields_info[]=
|
||||
Column("TRIGGER_CATALOG", Catalog(), NOT_NULL, OPEN_FRM_ONLY),
|
||||
Column("TRIGGER_SCHEMA", Name(), NOT_NULL, OPEN_FRM_ONLY),
|
||||
Column("TRIGGER_NAME", Name(), NOT_NULL, "Trigger", OPEN_FRM_ONLY),
|
||||
Column("EVENT_MANIPULATION", Varchar(6), NOT_NULL, "Event", OPEN_FRM_ONLY),
|
||||
Column("EVENT_MANIPULATION", Varchar(20), NOT_NULL, "Event", OPEN_FRM_ONLY),
|
||||
Column("EVENT_OBJECT_CATALOG", Catalog(), NOT_NULL, OPEN_FRM_ONLY),
|
||||
Column("EVENT_OBJECT_SCHEMA", Name(), NOT_NULL, OPEN_FRM_ONLY),
|
||||
Column("EVENT_OBJECT_TABLE", Name(), NOT_NULL, "Table", OPEN_FRM_ONLY),
|
||||
|
@ -389,9 +389,13 @@ Trigger* Table_triggers_list::for_all_triggers(Triggers_processor func,
|
||||
{
|
||||
for (Trigger *trigger= get_trigger(i,j) ;
|
||||
trigger ;
|
||||
trigger= trigger->next)
|
||||
if ((trigger->*func)(arg))
|
||||
trigger= trigger->next[i])
|
||||
if (is_the_right_most_event_bit(trigger->events, (trg_event_type)i) &&
|
||||
(trigger->*func)(arg))
|
||||
{
|
||||
(trigger->*func)(arg);
|
||||
return trigger;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
@ -997,7 +1001,11 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,
|
||||
if (lex->trg_chistics.ordering_clause != TRG_ORDER_NONE)
|
||||
{
|
||||
if (!(trigger= find_trigger(&lex->trg_chistics.anchor_trigger_name, 0)) ||
|
||||
trigger->event != lex->trg_chistics.event ||
|
||||
/*
|
||||
check that every event listed for the trigger being created is also
|
||||
specified for anchored trigger
|
||||
*/
|
||||
!is_subset_of_trg_events(trigger->events, lex->trg_chistics.events) ||
|
||||
trigger->action_time != lex->trg_chistics.action_time)
|
||||
{
|
||||
my_error(ER_REFERENCED_TRG_DOES_NOT_EXIST, MYF(0),
|
||||
@ -1133,8 +1141,9 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,
|
||||
trigger->db_cl_name= get_default_db_collation(thd, tables->db.str)->coll_name;
|
||||
trigger->name= Lex_ident_trigger(lex->spname->m_name);
|
||||
|
||||
|
||||
/* Add trigger in it's correct place */
|
||||
add_trigger(lex->trg_chistics.event,
|
||||
add_trigger(lex->trg_chistics.events,
|
||||
lex->trg_chistics.action_time,
|
||||
lex->trg_chistics.ordering_clause,
|
||||
Lex_ident_trigger(lex->trg_chistics.anchor_trigger_name),
|
||||
@ -1239,7 +1248,7 @@ bool Trigger::add_to_file_list(void* param_arg)
|
||||
|
||||
bool Trigger::match_updatable_columns(List<Item> &fields)
|
||||
{
|
||||
DBUG_ASSERT(event == TRG_EVENT_UPDATE);
|
||||
DBUG_ASSERT(is_trg_event_on(events, TRG_EVENT_UPDATE));
|
||||
|
||||
/*
|
||||
No table columns were specified in OF col1, col2 ... colN of
|
||||
@ -1359,29 +1368,53 @@ bool Table_triggers_list::save_trigger_file(THD *thd, const LEX_CSTRING *db,
|
||||
Trigger *Table_triggers_list::find_trigger(const LEX_CSTRING *name,
|
||||
bool remove_from_list)
|
||||
{
|
||||
Trigger *trigger = nullptr;
|
||||
|
||||
for (uint i= 0; i < (uint)TRG_EVENT_MAX; i++)
|
||||
{
|
||||
for (uint j= 0; j < (uint)TRG_ACTION_MAX; j++)
|
||||
{
|
||||
Trigger **parent, *trigger;
|
||||
Trigger **parent;
|
||||
|
||||
for (parent= &triggers[i][j];
|
||||
(trigger= *parent);
|
||||
parent= &trigger->next)
|
||||
parent= &trigger->next[i])
|
||||
{
|
||||
if (trigger->name.streq(*name))
|
||||
{
|
||||
if (remove_from_list)
|
||||
{
|
||||
*parent= trigger->next;
|
||||
*parent= trigger->next[i];
|
||||
count--;
|
||||
/*
|
||||
in case only one event left or was assigned to this trigger
|
||||
return it, else continue iterations to remove the trigger
|
||||
from all events entries.
|
||||
*/
|
||||
if (trigger->events != (1 << i))
|
||||
{
|
||||
/*
|
||||
Turn off event bits in the mask as the trigger is removed
|
||||
from the array for corresponding trigger event action.
|
||||
Eventually, we come to the last event this trigger is
|
||||
associated to. The associated trigger be returned from
|
||||
the method and finally deleted.
|
||||
*/
|
||||
trigger->events &= ~(1 << i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return trigger;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
/*
|
||||
We come to this point if either remove_from_list == true and
|
||||
the trigger is associated with multiple events, or there is no a trigger
|
||||
with requested name.
|
||||
*/
|
||||
return trigger;
|
||||
}
|
||||
|
||||
|
||||
@ -1476,15 +1509,26 @@ Table_triggers_list::~Table_triggers_list()
|
||||
{
|
||||
DBUG_ENTER("Table_triggers_list::~Table_triggers_list");
|
||||
|
||||
for (uint i= 0; i < (uint)TRG_EVENT_MAX; i++)
|
||||
/*
|
||||
Iterate over trigger events in descending order to delete only the last
|
||||
instance of the Trigger class in case there are several events associated
|
||||
with the trigger.
|
||||
*/
|
||||
for (int i= (int)TRG_EVENT_MAX - 1; i >= 0; i--)
|
||||
{
|
||||
for (uint j= 0; j < (uint)TRG_ACTION_MAX; j++)
|
||||
{
|
||||
Trigger *next, *trigger;
|
||||
for (trigger= get_trigger(i,j) ; trigger ; trigger= next)
|
||||
{
|
||||
next= trigger->next;
|
||||
delete trigger;
|
||||
next= trigger->next[i];
|
||||
/*
|
||||
Since iteration along triggers is performed in descending order
|
||||
deleting an instance of the Trigger class for the right most event
|
||||
bit guarantees that the instance is deleted only once.
|
||||
*/
|
||||
if (is_the_right_most_event_bit(trigger->events, (trg_event_type)i))
|
||||
delete trigger;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1792,7 +1836,7 @@ bool Table_triggers_list::check_n_load(THD *thd, const LEX_CSTRING *db,
|
||||
thd->spcont= NULL;
|
||||
|
||||
/* The following is for catching parse errors */
|
||||
lex.trg_chistics.event= TRG_EVENT_MAX;
|
||||
lex.trg_chistics.events= TRG_EVENT_UNKNOWN;
|
||||
lex.trg_chistics.action_time= TRG_ACTION_MAX;
|
||||
Deprecated_trigger_syntax_handler error_handler;
|
||||
thd->push_internal_handler(&error_handler);
|
||||
@ -1851,13 +1895,21 @@ bool Table_triggers_list::check_n_load(THD *thd, const LEX_CSTRING *db,
|
||||
lex.trg_chistics.on_update_col_names))
|
||||
goto err_with_lex_cleanup;
|
||||
|
||||
/* event can only be TRG_EVENT_MAX in case of fatal parse errors */
|
||||
if (lex.trg_chistics.event != TRG_EVENT_MAX)
|
||||
trigger_list->add_trigger(lex.trg_chistics.event,
|
||||
/*
|
||||
events can be equal TRG_EVENT_UNKNOWN only in case of
|
||||
fatal parse errors
|
||||
*/
|
||||
if (lex.trg_chistics.events != TRG_EVENT_UNKNOWN)
|
||||
{
|
||||
const Lex_ident_trigger
|
||||
anchor_trg_name(lex.trg_chistics.anchor_trigger_name);
|
||||
|
||||
trigger_list->add_trigger(lex.trg_chistics.events,
|
||||
lex.trg_chistics.action_time,
|
||||
TRG_ORDER_NONE,
|
||||
Lex_ident_trigger(lex.trg_chistics.anchor_trigger_name),
|
||||
anchor_trg_name,
|
||||
trigger);
|
||||
}
|
||||
|
||||
if (unlikely(parse_error))
|
||||
{
|
||||
@ -2011,6 +2063,36 @@ error:
|
||||
}
|
||||
|
||||
|
||||
void Table_triggers_list::add_trigger(trg_event_set trg_events,
|
||||
trg_action_time_type action_time,
|
||||
trigger_order_type ordering_clause,
|
||||
const Lex_ident_trigger &
|
||||
anchor_trigger_name,
|
||||
Trigger *trigger)
|
||||
{
|
||||
if (is_trg_event_on(trg_events, TRG_EVENT_INSERT))
|
||||
add_trigger(TRG_EVENT_INSERT,
|
||||
action_time,
|
||||
ordering_clause,
|
||||
anchor_trigger_name,
|
||||
trigger);
|
||||
|
||||
if (is_trg_event_on(trg_events, TRG_EVENT_UPDATE))
|
||||
add_trigger(TRG_EVENT_UPDATE,
|
||||
action_time,
|
||||
ordering_clause,
|
||||
anchor_trigger_name,
|
||||
trigger);
|
||||
|
||||
if (is_trg_event_on(trg_events, TRG_EVENT_DELETE))
|
||||
add_trigger(TRG_EVENT_DELETE,
|
||||
action_time,
|
||||
ordering_clause,
|
||||
anchor_trigger_name,
|
||||
trigger);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Add trigger in the correct position according to ordering clause
|
||||
Also update action order
|
||||
@ -2028,14 +2110,14 @@ void Table_triggers_list::add_trigger(trg_event_type event,
|
||||
Trigger **parent= &triggers[event][action_time];
|
||||
uint position= 0;
|
||||
|
||||
for ( ; *parent ; parent= &(*parent)->next, position++)
|
||||
for ( ; *parent ; parent= &(*parent)->next[event], position++)
|
||||
{
|
||||
if (ordering_clause != TRG_ORDER_NONE &&
|
||||
anchor_trigger_name.streq((*parent)->name))
|
||||
{
|
||||
if (ordering_clause == TRG_ORDER_FOLLOWS)
|
||||
{
|
||||
parent= &(*parent)->next; // Add after this one
|
||||
parent= &(*parent)->next[event]; // Add after this one
|
||||
position++;
|
||||
}
|
||||
break;
|
||||
@ -2043,15 +2125,15 @@ void Table_triggers_list::add_trigger(trg_event_type event,
|
||||
}
|
||||
|
||||
/* Add trigger where parent points to */
|
||||
trigger->next= *parent;
|
||||
trigger->next[event]= *parent;
|
||||
*parent= trigger;
|
||||
|
||||
/* Update action_orders and position */
|
||||
trigger->event= event;
|
||||
trigger->events|= trg2bit(event);
|
||||
trigger->action_time= action_time;
|
||||
trigger->action_order= ++position;
|
||||
while ((trigger= trigger->next))
|
||||
trigger->action_order= ++position;
|
||||
trigger->action_order[event]= ++position;
|
||||
while ((trigger= trigger->next[event]))
|
||||
trigger->action_order[event]= ++position;
|
||||
|
||||
count++;
|
||||
}
|
||||
@ -2217,7 +2299,7 @@ bool Table_triggers_list::drop_all_triggers(THD *thd, const LEX_CSTRING *db,
|
||||
Trigger *trigger;
|
||||
for (trigger= table.triggers->get_trigger(i,j) ;
|
||||
trigger ;
|
||||
trigger= trigger->next)
|
||||
trigger= trigger->next[i])
|
||||
{
|
||||
/*
|
||||
Trigger, which body we failed to parse during call
|
||||
@ -2613,6 +2695,40 @@ static inline bool do_skip_row_indicator(Diagnostics_area *da,
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
This class is responsible for storing a kind of current trigger event
|
||||
for processing of NEW/OLD clauses inside trigger's body.
|
||||
Before start processing of triggers for the given event type, the event type
|
||||
pushed into the stack of events in constructor of the class
|
||||
Trigger_event_guard and popped after processing all triggers of this event
|
||||
type by running destructor of the class Trigger_event_guard.
|
||||
|
||||
Every time when the NEW or OLD clause is evaluated on processing a trigger
|
||||
body, the event type of trigger being executed is consulted to determine
|
||||
whether a value of the clause can produce meaning value: for INSERT event,
|
||||
evaluation of the OLD clause should return NULL; for DELETE event, evaluation
|
||||
of the NEW clause should return NULL.
|
||||
@see Item_trigger_field::check_new_old_qulifiers_comform_with_trg_event()
|
||||
@see Item_trigger_field::save_in_field()
|
||||
@see Item_trigger_field::val_*()
|
||||
*/
|
||||
class Trigger_event_guard
|
||||
{
|
||||
Statement *m_stmt;
|
||||
public:
|
||||
Trigger_event_guard(Statement *stmt,
|
||||
trg_event_type event)
|
||||
: m_stmt{stmt}
|
||||
{
|
||||
m_stmt->push_current_trg_event(event);
|
||||
}
|
||||
~Trigger_event_guard()
|
||||
{
|
||||
m_stmt->pop_current_trg_event();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
Execute trigger for given (event, time) pair.
|
||||
|
||||
@ -2708,6 +2824,7 @@ bool Table_triggers_list::process_triggers(THD *thd,
|
||||
void *save_bulk_param= thd->bulk_param;
|
||||
thd->bulk_param= nullptr;
|
||||
|
||||
Trigger_event_guard guard(thd, event);
|
||||
do {
|
||||
thd->lex->current_select= NULL;
|
||||
|
||||
@ -2742,7 +2859,7 @@ bool Table_triggers_list::process_triggers(THD *thd,
|
||||
}
|
||||
|
||||
status_var_increment(thd->status_var.executed_triggers);
|
||||
} while (!err_status && (trigger= trigger->next));
|
||||
} while (!err_status && (trigger= trigger->next[event]));
|
||||
thd->bulk_param= save_bulk_param;
|
||||
thd->lex->current_select= save_current_select;
|
||||
|
||||
@ -2782,7 +2899,7 @@ add_tables_and_routines_for_triggers(THD *thd,
|
||||
{
|
||||
Trigger *triggers= table_list->table->triggers->get_trigger(i,j);
|
||||
|
||||
for ( ; triggers ; triggers= triggers->next)
|
||||
for ( ; triggers ; triggers= triggers->next[i])
|
||||
{
|
||||
sp_head *trigger= triggers->body;
|
||||
|
||||
@ -2828,7 +2945,7 @@ bool Table_triggers_list::match_updatable_columns(List<Item> *fields)
|
||||
{
|
||||
for (Trigger *trigger= get_trigger(TRG_EVENT_UPDATE, i) ;
|
||||
trigger ;
|
||||
trigger= trigger->next)
|
||||
trigger= trigger->next[TRG_EVENT_UPDATE])
|
||||
if (trigger->match_updatable_columns(*fields))
|
||||
return true;
|
||||
}
|
||||
@ -2859,7 +2976,7 @@ void Table_triggers_list::mark_fields_used(trg_event_type event)
|
||||
{
|
||||
for (Trigger *trigger= get_trigger(event,action_time);
|
||||
trigger ;
|
||||
trigger= trigger->next)
|
||||
trigger= trigger->next[event])
|
||||
{
|
||||
/*
|
||||
Skip a trigger that was parsed with an error.
|
||||
|
@ -32,6 +32,7 @@ typedef struct st_ddl_log_state DDL_LOG_STATE;
|
||||
|
||||
#include <sql_list.h>
|
||||
|
||||
static const uint8 TRG_EVENT_UNKNOWN= 0;
|
||||
/** Event on which trigger is invoked. */
|
||||
enum trg_event_type
|
||||
{
|
||||
@ -41,9 +42,40 @@ enum trg_event_type
|
||||
TRG_EVENT_MAX
|
||||
};
|
||||
|
||||
typedef uint8 trg_event_set;
|
||||
|
||||
static inline uint8 trg2bit(enum trg_event_type trg)
|
||||
{ return static_cast<uint8>(1 << static_cast<int>(trg)); }
|
||||
|
||||
/**
|
||||
Check whether the specified trigger event type is set in
|
||||
the trigger's event mask
|
||||
*/
|
||||
static inline bool is_trg_event_on(uint8 trg_event_mask,
|
||||
enum trg_event_type event_type)
|
||||
{
|
||||
return (trg_event_mask & trg2bit(event_type)) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
Check whether the specified trigger event type is the right most event bit
|
||||
that is set in the trigger's event mask
|
||||
*/
|
||||
static inline bool is_the_right_most_event_bit(trg_event_set events,
|
||||
trg_event_type event_type)
|
||||
{
|
||||
return (1 << event_type) == (events & ~((events - 1) & events));
|
||||
}
|
||||
|
||||
/**
|
||||
Check whether trg_events_mask includes all bits of trg_events
|
||||
*/
|
||||
static inline bool is_subset_of_trg_events(trg_event_set trg_events_mask,
|
||||
trg_event_set trg_events)
|
||||
{
|
||||
return (trg_events_mask & trg_events) == trg_events;
|
||||
}
|
||||
|
||||
#include "table.h" /* GRANT_INFO */
|
||||
|
||||
/*
|
||||
@ -111,26 +143,38 @@ class Table_triggers_list;
|
||||
|
||||
/**
|
||||
The trigger object
|
||||
One instance of the Trigger class can handle several trigger events.
|
||||
E.g., one object of the Trigger class can be instantiated to handle
|
||||
every of events INSERT, UPDATE, DELETE.
|
||||
Since one instance of the Trigger class can be associated with several
|
||||
trigger events, the data member `action_order` and `next` are represented
|
||||
as an array of TRG_EVENT_MAX elements.
|
||||
*/
|
||||
|
||||
class Trigger :public Sql_alloc
|
||||
{
|
||||
public:
|
||||
Trigger(Table_triggers_list *base_arg, sp_head *code):
|
||||
base(base_arg), body(code), next(0),
|
||||
base(base_arg), body(code),
|
||||
sql_mode{0},
|
||||
hr_create_time{(unsigned long long)-1},
|
||||
event{TRG_EVENT_MAX},
|
||||
events{0},
|
||||
action_time{TRG_ACTION_MAX},
|
||||
action_order{0},
|
||||
updatable_columns{nullptr}
|
||||
{
|
||||
bzero((char *)&subject_table_grants, sizeof(subject_table_grants));
|
||||
bzero(next, sizeof(next));
|
||||
bzero(action_order, sizeof(action_order));
|
||||
}
|
||||
|
||||
~Trigger();
|
||||
|
||||
Table_triggers_list *base;
|
||||
sp_head *body;
|
||||
Trigger *next; /* Next trigger of same type */
|
||||
/**
|
||||
Next trigger of the same type in every of the event groups
|
||||
*/
|
||||
Trigger *next[TRG_EVENT_MAX];
|
||||
|
||||
Lex_ident_trigger name;
|
||||
LEX_CSTRING on_table_name; /* Raw table name */
|
||||
@ -146,9 +190,14 @@ public:
|
||||
sql_mode_t sql_mode;
|
||||
/* Store create time. Can't be mysql_time_t as this holds also sub seconds */
|
||||
my_hrtime_t hr_create_time; // Create time timestamp in microseconds
|
||||
trg_event_type event;
|
||||
/* Set of events this trigger object assigned to */
|
||||
trg_event_set events;
|
||||
trg_action_time_type action_time;
|
||||
uint action_order;
|
||||
|
||||
/**
|
||||
action order of the trigger for every of supplied event type
|
||||
*/
|
||||
uint action_order[TRG_EVENT_MAX];
|
||||
List<LEX_CSTRING> *updatable_columns;
|
||||
|
||||
void get_trigger_info(LEX_CSTRING *stmt, LEX_CSTRING *body,
|
||||
@ -282,6 +331,11 @@ public:
|
||||
const LEX_CSTRING *old_table,
|
||||
const LEX_CSTRING *new_db,
|
||||
const LEX_CSTRING *new_table);
|
||||
void add_trigger(trg_event_set trg_events,
|
||||
trg_action_time_type action_time,
|
||||
trigger_order_type ordering_clause,
|
||||
const Lex_ident_trigger &anchor_trigger_name,
|
||||
Trigger *trigger);
|
||||
void add_trigger(trg_event_type event_type,
|
||||
trg_action_time_type action_time,
|
||||
trigger_order_type ordering_clause,
|
||||
@ -388,4 +442,6 @@ bool rm_trigname_file(char *path, const LEX_CSTRING *db,
|
||||
extern const char * const TRG_EXT;
|
||||
extern const char * const TRN_EXT;
|
||||
|
||||
extern const LEX_CSTRING trg_event_type_names[];
|
||||
|
||||
#endif /* SQL_TRIGGER_INCLUDED */
|
||||
|
@ -3195,6 +3195,7 @@ err:
|
||||
bool Sql_cmd_update::execute_inner(THD *thd)
|
||||
{
|
||||
bool res= 0;
|
||||
Running_stmt_guard guard(thd, active_dml_stmt::UPDATING_STMT);
|
||||
|
||||
thd->get_stmt_da()->reset_current_row_for_warning(1);
|
||||
if (!multitable)
|
||||
|
@ -4777,11 +4777,16 @@ trg_action_time:
|
||||
|
||||
trg_event:
|
||||
INSERT
|
||||
{ Lex->trg_chistics.event= TRG_EVENT_INSERT; }
|
||||
{ Lex->trg_chistics.events|= trg2bit(TRG_EVENT_INSERT); }
|
||||
| UPDATE_SYM
|
||||
{ Lex->trg_chistics.event= TRG_EVENT_UPDATE; }
|
||||
{ Lex->trg_chistics.events|= trg2bit(TRG_EVENT_UPDATE); }
|
||||
| DELETE_SYM
|
||||
{ Lex->trg_chistics.event= TRG_EVENT_DELETE; }
|
||||
{ Lex->trg_chistics.events|= trg2bit(TRG_EVENT_DELETE); }
|
||||
;
|
||||
|
||||
trg_events:
|
||||
trg_event
|
||||
| trg_events OR_SYM trg_event
|
||||
;
|
||||
|
||||
create_body:
|
||||
@ -18493,7 +18498,7 @@ opt_on_update_cols:
|
||||
}
|
||||
| OF_SYM on_update_cols
|
||||
{
|
||||
if (Lex->trg_chistics.event != TRG_EVENT_UPDATE)
|
||||
if (!is_trg_event_on(Lex->trg_chistics.events, TRG_EVENT_UPDATE))
|
||||
{
|
||||
thd->parse_error(ER_SYNTAX_ERROR, $1.pos());
|
||||
MYSQL_YYABORT;
|
||||
@ -18541,7 +18546,7 @@ trigger_tail:
|
||||
}
|
||||
sp_name
|
||||
trg_action_time
|
||||
trg_event
|
||||
trg_events
|
||||
opt_on_update_cols
|
||||
ON
|
||||
remember_name /* $9 */
|
||||
|
Loading…
x
Reference in New Issue
Block a user