diff --git a/mysql-test/main/information_schema.result b/mysql-test/main/information_schema.result index 820c6c488ef..8708f542fba 100644 --- a/mysql-test/main/information_schema.result +++ b/mysql-test/main/information_schema.result @@ -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 diff --git a/mysql-test/main/show_check.result b/mysql-test/main/show_check.result index d390c69fd63..d7dbb656bb4 100644 --- a/mysql-test/main/show_check.result +++ b/mysql-test/main/show_check.result @@ -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 diff --git a/mysql-test/main/trigger.result b/mysql-test/main/trigger.result index 00a6ecf52a3..a7acb168bf1 100644 --- a/mysql-test/main/trigger.result +++ b/mysql-test/main/trigger.result @@ -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 # diff --git a/mysql-test/main/trigger.test b/mysql-test/main/trigger.test index a611c75aab7..32c312c389d 100644 --- a/mysql-test/main/trigger.test +++ b/mysql-test/main/trigger.test @@ -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 # diff --git a/mysql-test/suite/funcs_1/r/is_columns_is.result b/mysql-test/suite/funcs_1/r/is_columns_is.result index e347bac535e..1b4e74e85cc 100644 --- a/mysql-test/suite/funcs_1/r/is_columns_is.result +++ b/mysql-test/suite/funcs_1/r/is_columns_is.result @@ -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) diff --git a/mysql-test/suite/funcs_1/r/is_columns_is_embedded.result b/mysql-test/suite/funcs_1/r/is_columns_is_embedded.result index 3396f6cffc4..bdbe7b2a90e 100644 --- a/mysql-test/suite/funcs_1/r/is_columns_is_embedded.result +++ b/mysql-test/suite/funcs_1/r/is_columns_is_embedded.result @@ -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) diff --git a/mysql-test/suite/funcs_1/r/is_triggers.result b/mysql-test/suite/funcs_1/r/is_triggers.result index 8ed53cf3413..ff631acd58b 100644 --- a/mysql-test/suite/funcs_1/r/is_triggers.result +++ b/mysql-test/suite/funcs_1/r/is_triggers.result @@ -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 diff --git a/mysql-test/suite/funcs_1/r/is_triggers_embedded.result b/mysql-test/suite/funcs_1/r/is_triggers_embedded.result index 191bd5c4a04..b24c39235d5 100644 --- a/mysql-test/suite/funcs_1/r/is_triggers_embedded.result +++ b/mysql-test/suite/funcs_1/r/is_triggers_embedded.result @@ -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 diff --git a/sql/item.cc b/sql/item.cc index 49c28d5e72e..6e144b0589d 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -10402,6 +10402,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) { /* @@ -10437,6 +10456,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; } @@ -10462,6 +10482,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; @@ -10539,6 +10605,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; diff --git a/sql/item.h b/sql/item.h index 2c545963019..15226a91558 100644 --- a/sql/item.h +++ b/sql/item.h @@ -28,6 +28,8 @@ #include #include "cset_narrowing.h" +#include "sql_basic_types.h" + C_MODE_START #include @@ -7355,7 +7357,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) @@ -7384,6 +7386,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 { @@ -7398,6 +7401,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; }; diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 29c8d86c47d..e83ca657ab1 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -12301,3 +12301,5 @@ ER_TEMPORARY_TABLES_PREVENT_SWITCH_GTID_DOMAIN_ID eng "Cannot modify @@session.gtid_domain_id while there are open temporary tables being binlogged" ER_TOO_MANY_OPEN_CURSORS eng "Too many open cursors; max %u cursors allowed" +ER_INCOMPATIBLE_EVENT_FLAG + eng "Event flag '%s' in the condition expression is not compatible with the trigger event type '%s'" diff --git a/sql/sp_instr.cc b/sql/sp_instr.cc index aeedfbcce04..e8601f052a1 100644 --- a/sql/sp_instr.cc +++ b/sql/sp_instr.cc @@ -902,7 +902,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 diff --git a/sql/sql_basic_types.h b/sql/sql_basic_types.h index 6cfd7ffb762..09eddd02749 100644 --- a/sql/sql_basic_types.h +++ b/sql/sql_basic_types.h @@ -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 diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 8e499e20196..5bc714266bd 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -4289,6 +4289,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"); diff --git a/sql/sql_class.h b/sql/sql_class.h index 73c643898b3..5f4732db6a1 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -23,6 +23,7 @@ #include #include "dur_prop.h" #include +#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, @@ -1734,6 +1737,63 @@ public: void restore_backup_statement(Statement *stmt, Statement *backup); /* return class type */ Type type() const override; + +private: + Dynamic_array 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 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(); + } }; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 12950c54347..6586ec76862 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -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()) diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index ba9d576a91e..55c664c9bf8 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -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(); /* diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 1befa690da5..72f90e51762 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -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(), @@ -8358,21 +8358,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); @@ -8806,6 +8824,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()); @@ -8956,6 +9009,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)) @@ -9080,7 +9158,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; diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 2702fc7960a..ae66a8b7c90 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -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; @@ -4087,6 +4087,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" */ diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 1bff8fb8ec4..f9df46f1214 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -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("") }, // 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[]= @@ -7754,12 +7759,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, @@ -7812,9 +7830,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); } } @@ -10298,7 +10318,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), diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index edadc30d494..c24da7e2532 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -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 &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 *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. diff --git a/sql/sql_trigger.h b/sql/sql_trigger.h index 87f85855026..56d610dc372 100644 --- a/sql/sql_trigger.h +++ b/sql/sql_trigger.h @@ -32,6 +32,7 @@ typedef struct st_ddl_log_state DDL_LOG_STATE; #include +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(1 << static_cast(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 *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 */ diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 7df6076fa3d..988df519d12 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -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) diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index ab86d09a97b..dc1c33c43ea 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -4772,11 +4772,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: @@ -18488,7 +18493,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; @@ -18536,7 +18541,7 @@ trigger_tail: } sp_name trg_action_time - trg_event + trg_events opt_on_update_cols ON remember_name /* $9 */