1
0
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:
Dmitry Shulga 2025-04-16 20:21:17 +07:00
parent cb2d6abae1
commit 8d1691286c
24 changed files with 1245 additions and 74 deletions

View File

@ -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

View File

@ -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

View File

@ -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
#

View File

@ -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 #

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;
};

View File

@ -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'"

View File

@ -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

View File

@ -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

View File

@ -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");

View File

@ -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();
}
};

View File

@ -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())

View File

@ -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();
/*

View File

@ -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;

View File

@ -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"
*/

View File

@ -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),

View File

@ -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.

View File

@ -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 */

View File

@ -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)

View File

@ -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 */