1
0
mirror of https://github.com/MariaDB/server.git synced 2025-08-08 11:22:35 +03:00

MDEV-34724: Skipping a row operation from a trigger

Implementation of this task adds ability to raise the signal with
SQLSTATE '02TRG' from a BEFORE INSERT/UPDATE/DELETE trigger and handles
this signal as an indicator meaning 'to throw away the current row'
on processing the INSERT/UPDATE/DELETE statement. The signal with
SQLSTATE '02TRG' has special meaning only in case it is raised inside
BEFORE triggers, for AFTER trigger's this value of SQLSTATE isn't treated
in any special way. In according with SQL standard, the SQLSTATE class '02'
means NO DATA and sql_errno for this class is set to value
ER_SIGNAL_NOT_FOUND by current implementation of MariaDB server.
Implementation of this task assigns the value ER_SIGNAL_SKIP_ROW_FROM_TRIGGER
to sql_errno in Diagnostics_area in case the signal is raised from a trigger
and SQLSTATE has value '02TRG'.

To catch signal with SQLTSATE '02TRG' and handle it in special way, the methods
 Table_triggers_list::process_triggers
 select_insert::store_values
 select_create::store_values
 Rows_log_event::process_triggers
and the overloaded function
 fill_record_n_invoke_before_triggers
were extended with extra out parameter for returning the flag whether
to skip the current values being processed by INSERT/UPDATE/DELETE
statement. This extra parameter is passed as nullptr in case of AFTER trigger
and BEFORE trigger this parameter points to a variable to store a marker
whether to skip the current record or store it by calling write_record().
This commit is contained in:
Dmitry Shulga
2025-01-27 16:29:25 +07:00
parent fcf7211136
commit 4c956fa15b
17 changed files with 1097 additions and 64 deletions

View File

@@ -0,0 +1,414 @@
# Test case 1: check that a row being inserted
# can be filtered out by means running of a statement
# SIGNAL SQLSTATE '02TRG' from the trigger body
CREATE TABLE t1 (a INT);
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
BEGIN
IF NEW.a = 1 THEN
SIGNAL SQLSTATE '02TRG';
END IF;
END
$
# Execution of the following INSERT statement produces warnings
# for the row (1) filtered out by the statement SIGNAL SQLSTATE '02TRG'
# invoked from the trigger
INSERT INTO t1 VALUES (1), (2), (3);
Warnings:
Error 4207 The row is skipped by a trigger implementation
Note 4094 At line 4 in test.t1_bi
# Expected output is the rows (2), (3)
SELECT * FROM t1;
a
2
3
# Clean up
TRUNCATE TABLE t1;
# Test case 2: run the statement INSERT SELECT and check
# that the rows (1) is filtered out by the trigger `t1_bi`
CREATE TABLE t2 (a INT);
INSERT INTO t2 VALUES (1), (2), (1), (3), (5);
# Execution of the following INSERT ... SELECT statement produces warnings
# for the row (1) filtered out by the statement SIGNAL SQLSTATE '02TRG'
# invoked from the trigger
INSERT INTO t1 SELECT * FROM t2;
Warnings:
Error 4207 The row is skipped by a trigger implementation
Note 4094 At line 4 in test.t1_bi
Error 4207 The row is skipped by a trigger implementation
Note 4094 At line 4 in test.t1_bi
# Expected output is the rows (2), (3), (5)
SELECT * FROM t1;
a
2
3
5
# Clean up
DROP TABLE t1, t2;
# Test case 3: check for cooperation of the feature
# 'skipping rows from a trigger' and execution of
# the statement LOAD
CREATE TABLE t1 (a INT);
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
BEGIN
IF NEW.a = 1 THEN
SIGNAL SQLSTATE '02TRG';
END IF;
END
$
# Test case 3.1: check for LOAD DATA INFILE
# Prepare a file with dump of the table `t2` and
# then use it for loading data into the table `t1`
CREATE TABLE t2 (a INT);
INSERT INTO t2 VALUES (1), (2), (1), (3), (5);
SELECT * INTO OUTFILE 'MYSQLTEST_VARDIR/tmp/t2' FROM t2;
# Execution of the following LOAD DATA INFILE statement produces warnings
# for the rows (1) filtered out by the statement SIGNAL SQLSTATE '02TRG'
# invoked from the trigger
LOAD DATA INFILE 'MYSQLTEST_VARDIR/tmp/t2' INTO TABLE t1;
Warnings:
Error 4207 The row is skipped by a trigger implementation
Note 4094 At line 4 in test.t1_bi
Error 4207 The row is skipped by a trigger implementation
Note 4094 At line 4 in test.t1_bi
# Querying of the table `t1` should return three rows: (2), (3), (5)
SELECT * FROM t1;
a
2
3
5
# Clean up
TRUNCATE TABLE t1;
# Test case 3.2: check for LOAD XML INFILE
# The same test with loading data from a file in presence of
# a BEFORE INSERT trigger, but in this case the data to be loaded
# is in xml format.
# Prepare a file with dump of the table `t2` in xml format and
# then use it for loading data into the table `t1`
# Execution of the following LOAD XML INFILE statement produces warnings
# for the rows (1) filtered out by the statement SIGNAL SQLSTATE '02TRG'
# invoked from the trigger
LOAD XML INFILE 'MYSQLTEST_VARDIR/tmp/loadxml-dump.xml' INTO TABLE t1 ROWS IDENTIFIED BY '<row>';
Warnings:
Error 4207 The row is skipped by a trigger implementation
Note 4094 At line 4 in test.t1_bi
Error 4207 The row is skipped by a trigger implementation
Note 4094 At line 4 in test.t1_bi
SELECT * FROM t1;
a
2
3
5
# Clean up
DROP TABLE t1, t2;
# Test case 4: check that a row being deleted
# can be filtered out by means running of a statement
# SIGNAL SQLSTATE '02TRG' from the trigger body
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (1), (2), (3), (1);
CREATE TRIGGER t1_bi BEFORE DELETE ON t1 FOR EACH ROW
BEGIN
IF OLD.a = 1 THEN
SIGNAL SQLSTATE '02TRG';
END IF;
END
$
# No rows deleted in result of running the following statement
# Execution of the following DELETE statement produces warnings
# for the row (1) filtered out by the statement SIGNAL SQLSTATE '02TRG'
# invoked from the trigger
DELETE FROM t1 WHERE a = 1;
Warnings:
Error 4207 The row is skipped by a trigger implementation
Note 4094 At line 4 in test.t1_bi
Error 4207 The row is skipped by a trigger implementation
Note 4094 At line 4 in test.t1_bi
# Show that the rows satisfying the condition a = 1 are retained
# in the table. Expected output is the rows (1), (2), (3), (1)
SELECT * FROM t1;
a
1
2
3
1
# Shows that rows with a column value different
# from '1' are deleted successfully
DELETE FROM t1 WHERE a = 2;
# Expected output is the rows (1), (3), (1)
SELECT * FROM t1;
a
1
3
1
# Check that the DELETE statement without condition takes into
# account the fact that some of rows should be skipped.
DELETE FROM t1;
Warnings:
Error 4207 The row is skipped by a trigger implementation
Note 4094 At line 4 in test.t1_bi
Error 4207 The row is skipped by a trigger implementation
Note 4094 At line 4 in test.t1_bi
# Expected output is the rows (1), (1) since they are explicilty
# skipped by the trigger logic
SELECT * FROM t1;
a
1
1
# Clean up
DROP TABLE t1;
# Test case 5: check that AFTER INSERT/UPDATE/DELETE trigger is not fired
# in case a row skipped by corresponding BEFORE trigger
#
CREATE TABLE t1 (a INT);
CREATE TABLE t2 (a INT);
# Test case 5.1: check for the pair BEFORE INSERT/AFTER INSERT
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
BEGIN
IF NEW.a = 1 THEN
SIGNAL SQLSTATE '02TRG';
END IF;
END
$
CREATE TRIGGER t1_ai AFTER INSERT ON t1 FOR EACH ROW
BEGIN
INSERT INTO t2 VALUES (NEW.a);
END
$
INSERT INTO t1 VALUES (1), (2), (3);
Warnings:
Error 4207 The row is skipped by a trigger implementation
Note 4094 At line 4 in test.t1_bi
SELECT * FROM t1;
a
2
3
SELECT * FROM t2;
a
2
3
# Clean up
DROP TABLE t1, t2;
# Test case 5.2: check for the pair BEFORE DELETE/AFTER DELETE
CREATE TABLE t1 (a INT);
CREATE TABLE t2 (a INT);
INSERT INTO t1 VALUES (1), (2), (3);
CREATE TRIGGER t1_bd BEFORE DELETE ON t1 FOR EACH ROW
BEGIN
IF OLD.a = 1 THEN
SIGNAL SQLSTATE '02TRG';
END IF;
END
$
CREATE TRIGGER t1_ad AFTER DELETE ON t1 FOR EACH ROW
BEGIN
INSERT INTO t2 VALUES (OLD.a);
END
$
DELETE FROM t1;
Warnings:
Error 4207 The row is skipped by a trigger implementation
Note 4094 At line 4 in test.t1_bd
# The row (1) is skipped by implementation of the trigger t1_bd,
# therefore the row (1) isn't inserted into the table t2 since
# the trigger t1_ad isn't fired for the row (1)
SELECT * FROM t1;
a
1
SELECT * FROM t2;
a
2
3
# Clean up
DROP TABLE t1, t2;
# Test case 5.3: check for the pair BEFORE UPDATE/AFTER UPDATE
CREATE TABLE t1 (a INT);
CREATE TABLE t2 (old_a INT, new_a INT);
INSERT INTO t1 VALUES (1), (2), (3);
CREATE TRIGGER t1_bu BEFORE UPDATE ON t1 FOR EACH ROW
BEGIN
IF OLD.a = 1 THEN
SIGNAL SQLSTATE '02TRG';
END IF;
END
$
CREATE TRIGGER t1_au AFTER UPDATE ON t1 FOR EACH ROW
BEGIN
INSERT INTO t2 VALUES (OLD.a, NEW.a);
END
$
# The following statement UPDATE doens't modify the row (1)
# since this row is explicitly ignored by implementation of
# trigger t1_bu, therefore the trigger t1_au is not fired
# for this row and the row (1, 11) not inserted into the table t2
UPDATE t1 SET a = a + 10;
Warnings:
Error 4207 The row is skipped by a trigger implementation
Note 4094 At line 4 in test.t1_bu
# Expected output of the following statement SELECT is (1), (12), (13)
SELECT * FROM t1;
a
1
12
13
# Expected output of query from the table t2 is (2, 12), (3, 13)
SELECT * FROM t2;
old_a new_a
2 12
3 13
# Clean up
DROP TABLE t1, t2;
# Test case 6: check cooperation of UPDATE with the
# 'skipping a row from a trigger' feature
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (1), (2), (1), (3), (5);
CREATE TRIGGER t1_bd BEFORE UPDATE ON t1 FOR EACH ROW
BEGIN
IF OLD.a = 1 THEN
SIGNAL SQLSTATE '02TRG';
END IF;
END
$
# Check for update with condition
UPDATE t1 SET a = 1000 WHERE a = 1;
Warnings:
Error 4207 The row is skipped by a trigger implementation
Note 4094 At line 4 in test.t1_bd
Error 4207 The row is skipped by a trigger implementation
Note 4094 At line 4 in test.t1_bd
# Expected result is the rows (1), (2), (1), (3), (5)
SELECT * FROM t1;
a
1
2
1
3
5
# Check for unconditional update
UPDATE t1 SET a = a + 100;
Warnings:
Error 4207 The row is skipped by a trigger implementation
Note 4094 At line 4 in test.t1_bd
Error 4207 The row is skipped by a trigger implementation
Note 4094 At line 4 in test.t1_bd
# Expected result is the rows (1), (102), (1), (103), (105)
SELECT * FROM t1;
a
1
102
1
103
105
# Multi-update
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1), (2), (1), (3), (5);
CREATE TABLE t2 (a INT);
INSERT INTO t2 VALUES (1), (3);
# In multi-update the trigger skips an update of
# the first table only (the one that has an associated trigger),
# the second table (without a trigger) is still updated
UPDATE t1, t2 SET t1.a = t1.a + 300, t2.a = t2.a + 300 WHERE t1.a = t2.a;
Warnings:
Error 4207 The row is skipped by a trigger implementation
Note 4094 At line 4 in test.t1_bd
Error 4207 The row is skipped by a trigger implementation
Note 4094 At line 4 in test.t1_bd
# Expected results is the rows (1), (2), (1), (303), (5)
SELECT * FROM t1;
a
1
2
1
303
5
# Expected results is the rows (301), (303)
SELECT * FROM t2;
a
301
303
# Clean up
DROP TABLE t1, t2;
# Test case 7: check that MESSAGE_TEXT and MYSQL_ERRNOR still
# can be assigned by a user for SQLSTATE '02TRG'
CREATE TABLE t1 (a INT);
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
BEGIN
IF NEW.a = 1 THEN
SIGNAL SQLSTATE '02TRG' SET MYSQL_ERRNO=1, MESSAGE_TEXT='This value is intentionally ignored';
END IF;
END
$
# Execution of the following INSERT statement produces warnings
# for the row (1) filtered out by the statement SIGNAL SQLSTATE '02TRG'
# invoked from the trigger. The errno has value 1 and the message is
# is the text message 'This value is intentionally ignored'
INSERT INTO t1 VALUES (1), (2), (3);
Warnings:
Error 1 This value is intentionally ignored
Note 4094 At line 4 in test.t1_bi
# Expected output is the rows (2), (3)
SELECT * FROM t1;
a
2
3
# Clean up
DROP TABLE t1;
# Test case 8: check that SQLSTATE '02TRG' doesn't have any special
# meaning in AFTER triggers
# Test case 8.1: check it for AFTER INSERT trigger
CREATE TABLE t1 (a INT);
CREATE TRIGGER t1_ai AFTER INSERT ON t1 FOR EACH ROW
BEGIN
IF NEW.a = 1 THEN
SIGNAL SQLSTATE '02TRG';
END IF;
END
$
# There is no a handler for the signal raised from inside
# the trigger t1_ai, so the statement INSERT INTO fails
# with the error ER_SIGNAL_NOT_FOUND
INSERT INTO t1 VALUES (1);
ERROR 02TRG: Unhandled user-defined not found condition
SELECT * FROM t1;
a
1
# Clean up
DROP TABLE t1;
# Test case 8.2: check it for AFTER UPDATE trigger
CREATE TABLE t1 (a INT);
CREATE TRIGGER t1_au AFTER UPDATE ON t1 FOR EACH ROW
BEGIN
IF OLD.a = 1 THEN
SIGNAL SQLSTATE '02TRG';
END IF;
END
$
INSERT INTO t1 VALUES (1);
# There is no a handler for the signal raised from inside
# the trigger t1_au, so the statement UPDATE fails
# with the error ER_SIGNAL_NOT_FOUND
UPDATE t1 SET a = 10;
ERROR 02TRG: Unhandled user-defined not found condition
SELECT * FROM t1;
a
10
# Clean up
DROP TABLE t1;
# Test case 8.3: check it for AFTER DELETE trigger
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (1);
CREATE TRIGGER t1_ad AFTER DELETE ON t1 FOR EACH ROW
BEGIN
IF OLD.a = 1 THEN
SIGNAL SQLSTATE '02TRG';
END IF;
END
$
# There is no a handler for the signal raised from inside
# the trigger t1_ad, so the statement DELETE fails
# with the error ER_SIGNAL_NOT_FOUND
DELETE FROM t1;
ERROR 02TRG: Unhandled user-defined not found condition
SELECT * FROM t1;
a
# Clean up
DROP TABLE t1;
# End of 11.8 tests

View File

@@ -0,0 +1,373 @@
# Tests for the task MDEV-34724: Skipping a row operation from a trigger
--echo # Test case 1: check that a row being inserted
--echo # can be filtered out by means running of a statement
--echo # SIGNAL SQLSTATE '02TRG' from the trigger body
CREATE TABLE t1 (a INT);
--delimiter $
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
BEGIN
IF NEW.a = 1 THEN
SIGNAL SQLSTATE '02TRG';
END IF;
END
$
--delimiter ;
--echo # Execution of the following INSERT statement produces warnings
--echo # for the row (1) filtered out by the statement SIGNAL SQLSTATE '02TRG'
--echo # invoked from the trigger
INSERT INTO t1 VALUES (1), (2), (3);
--echo # Expected output is the rows (2), (3)
SELECT * FROM t1;
--echo # Clean up
TRUNCATE TABLE t1;
--echo # Test case 2: run the statement INSERT SELECT and check
--echo # that the rows (1) is filtered out by the trigger `t1_bi`
CREATE TABLE t2 (a INT);
INSERT INTO t2 VALUES (1), (2), (1), (3), (5);
--echo # Execution of the following INSERT ... SELECT statement produces warnings
--echo # for the row (1) filtered out by the statement SIGNAL SQLSTATE '02TRG'
--echo # invoked from the trigger
INSERT INTO t1 SELECT * FROM t2;
--echo # Expected output is the rows (2), (3), (5)
SELECT * FROM t1;
--echo # Clean up
DROP TABLE t1, t2;
--echo # Test case 3: check for cooperation of the feature
--echo # 'skipping rows from a trigger' and execution of
--echo # the statement LOAD
CREATE TABLE t1 (a INT);
--delimiter $
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
BEGIN
IF NEW.a = 1 THEN
SIGNAL SQLSTATE '02TRG';
END IF;
END
$
--delimiter ;
--echo # Test case 3.1: check for LOAD DATA INFILE
--echo # Prepare a file with dump of the table `t2` and
--echo # then use it for loading data into the table `t1`
CREATE TABLE t2 (a INT);
INSERT INTO t2 VALUES (1), (2), (1), (3), (5);
--disable_ps2_protocol
--disable_cursor_protocol
--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
--eval SELECT * INTO OUTFILE '$MYSQLTEST_VARDIR/tmp/t2' FROM t2
--enable_cursor_protocol
--enable_ps2_protocol
--echo # Execution of the following LOAD DATA INFILE statement produces warnings
--echo # for the rows (1) filtered out by the statement SIGNAL SQLSTATE '02TRG'
--echo # invoked from the trigger
--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
--eval LOAD DATA INFILE '$MYSQLTEST_VARDIR/tmp/t2' INTO TABLE t1
--echo # Querying of the table `t1` should return three rows: (2), (3), (5)
SELECT * FROM t1;
--echo # Clean up
--remove_file $MYSQLTEST_VARDIR/tmp/t2
TRUNCATE TABLE t1;
--echo # Test case 3.2: check for LOAD XML INFILE
--echo # The same test with loading data from a file in presence of
--echo # a BEFORE INSERT trigger, but in this case the data to be loaded
--echo # is in xml format.
--echo # Prepare a file with dump of the table `t2` in xml format and
--echo # then use it for loading data into the table `t1`
# Running the $MYSQL_DUMP tool against an embedded server does not work.
--source include/not_embedded.inc
--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
--exec $MYSQL_DUMP --xml test t2 > "$MYSQLTEST_VARDIR/tmp/loadxml-dump.xml" 2>&1
--echo # Execution of the following LOAD XML INFILE statement produces warnings
--echo # for the rows (1) filtered out by the statement SIGNAL SQLSTATE '02TRG'
--echo # invoked from the trigger
--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
--eval LOAD XML INFILE '$MYSQLTEST_VARDIR/tmp/loadxml-dump.xml' INTO TABLE t1 ROWS IDENTIFIED BY '<row>'
SELECT * FROM t1;
--echo # Clean up
--remove_file $MYSQLTEST_VARDIR/tmp/loadxml-dump.xml
DROP TABLE t1, t2;
--echo # Test case 4: check that a row being deleted
--echo # can be filtered out by means running of a statement
--echo # SIGNAL SQLSTATE '02TRG' from the trigger body
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (1), (2), (3), (1);
--delimiter $
CREATE TRIGGER t1_bi BEFORE DELETE ON t1 FOR EACH ROW
BEGIN
IF OLD.a = 1 THEN
SIGNAL SQLSTATE '02TRG';
END IF;
END
$
--delimiter ;
--echo # No rows deleted in result of running the following statement
--echo # Execution of the following DELETE statement produces warnings
--echo # for the row (1) filtered out by the statement SIGNAL SQLSTATE '02TRG'
--echo # invoked from the trigger
DELETE FROM t1 WHERE a = 1;
--echo # Show that the rows satisfying the condition a = 1 are retained
--echo # in the table. Expected output is the rows (1), (2), (3), (1)
SELECT * FROM t1;
--echo # Shows that rows with a column value different
--echo # from '1' are deleted successfully
DELETE FROM t1 WHERE a = 2;
--echo # Expected output is the rows (1), (3), (1)
SELECT * FROM t1;
--echo # Check that the DELETE statement without condition takes into
--echo # account the fact that some of rows should be skipped.
DELETE FROM t1;
--echo # Expected output is the rows (1), (1) since they are explicilty
--echo # skipped by the trigger logic
SELECT * FROM t1;
--echo # Clean up
DROP TABLE t1;
--echo # Test case 5: check that AFTER INSERT/UPDATE/DELETE trigger is not fired
--echo # in case a row skipped by corresponding BEFORE trigger
--echo #
CREATE TABLE t1 (a INT);
CREATE TABLE t2 (a INT);
--echo # Test case 5.1: check for the pair BEFORE INSERT/AFTER INSERT
--delimiter $
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
BEGIN
IF NEW.a = 1 THEN
SIGNAL SQLSTATE '02TRG';
END IF;
END
$
--delimiter ;
--delimiter $
CREATE TRIGGER t1_ai AFTER INSERT ON t1 FOR EACH ROW
BEGIN
INSERT INTO t2 VALUES (NEW.a);
END
$
--delimiter ;
INSERT INTO t1 VALUES (1), (2), (3);
SELECT * FROM t1;
SELECT * FROM t2;
--echo # Clean up
DROP TABLE t1, t2;
--echo # Test case 5.2: check for the pair BEFORE DELETE/AFTER DELETE
CREATE TABLE t1 (a INT);
CREATE TABLE t2 (a INT);
INSERT INTO t1 VALUES (1), (2), (3);
--delimiter $
CREATE TRIGGER t1_bd BEFORE DELETE ON t1 FOR EACH ROW
BEGIN
IF OLD.a = 1 THEN
SIGNAL SQLSTATE '02TRG';
END IF;
END
$
--delimiter ;
--delimiter $
CREATE TRIGGER t1_ad AFTER DELETE ON t1 FOR EACH ROW
BEGIN
INSERT INTO t2 VALUES (OLD.a);
END
$
--delimiter ;
DELETE FROM t1;
--echo # The row (1) is skipped by implementation of the trigger t1_bd,
--echo # therefore the row (1) isn't inserted into the table t2 since
--echo # the trigger t1_ad isn't fired for the row (1)
SELECT * FROM t1;
SELECT * FROM t2;
--echo # Clean up
DROP TABLE t1, t2;
--echo # Test case 5.3: check for the pair BEFORE UPDATE/AFTER UPDATE
CREATE TABLE t1 (a INT);
CREATE TABLE t2 (old_a INT, new_a INT);
INSERT INTO t1 VALUES (1), (2), (3);
--delimiter $
CREATE TRIGGER t1_bu BEFORE UPDATE ON t1 FOR EACH ROW
BEGIN
IF OLD.a = 1 THEN
SIGNAL SQLSTATE '02TRG';
END IF;
END
$
CREATE TRIGGER t1_au AFTER UPDATE ON t1 FOR EACH ROW
BEGIN
INSERT INTO t2 VALUES (OLD.a, NEW.a);
END
$
--delimiter ;
--echo # The following statement UPDATE doens't modify the row (1)
--echo # since this row is explicitly ignored by implementation of
--echo # trigger t1_bu, therefore the trigger t1_au is not fired
--echo # for this row and the row (1, 11) not inserted into the table t2
UPDATE t1 SET a = a + 10;
--echo # Expected output of the following statement SELECT is (1), (12), (13)
SELECT * FROM t1;
--echo # Expected output of query from the table t2 is (2, 12), (3, 13)
SELECT * FROM t2;
--echo # Clean up
DROP TABLE t1, t2;
--echo # Test case 6: check cooperation of UPDATE with the
--echo # 'skipping a row from a trigger' feature
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (1), (2), (1), (3), (5);
--delimiter $
CREATE TRIGGER t1_bd BEFORE UPDATE ON t1 FOR EACH ROW
BEGIN
IF OLD.a = 1 THEN
SIGNAL SQLSTATE '02TRG';
END IF;
END
$
--delimiter ;
--echo # Check for update with condition
UPDATE t1 SET a = 1000 WHERE a = 1;
--echo # Expected result is the rows (1), (2), (1), (3), (5)
SELECT * FROM t1;
--echo # Check for unconditional update
UPDATE t1 SET a = a + 100;
--echo # Expected result is the rows (1), (102), (1), (103), (105)
SELECT * FROM t1;
--echo # Multi-update
TRUNCATE TABLE t1;
INSERT INTO t1 VALUES (1), (2), (1), (3), (5);
CREATE TABLE t2 (a INT);
INSERT INTO t2 VALUES (1), (3);
--echo # In multi-update the trigger skips an update of
--echo # the first table only (the one that has an associated trigger),
--echo # the second table (without a trigger) is still updated
UPDATE t1, t2 SET t1.a = t1.a + 300, t2.a = t2.a + 300 WHERE t1.a = t2.a;
--echo # Expected results is the rows (1), (2), (1), (303), (5)
SELECT * FROM t1;
--echo # Expected results is the rows (301), (303)
SELECT * FROM t2;
--echo # Clean up
DROP TABLE t1, t2;
--echo # Test case 7: check that MESSAGE_TEXT and MYSQL_ERRNOR still
--echo # can be assigned by a user for SQLSTATE '02TRG'
CREATE TABLE t1 (a INT);
--delimiter $
CREATE TRIGGER t1_bi BEFORE INSERT ON t1 FOR EACH ROW
BEGIN
IF NEW.a = 1 THEN
SIGNAL SQLSTATE '02TRG' SET MYSQL_ERRNO=1, MESSAGE_TEXT='This value is intentionally ignored';
END IF;
END
$
--delimiter ;
--echo # Execution of the following INSERT statement produces warnings
--echo # for the row (1) filtered out by the statement SIGNAL SQLSTATE '02TRG'
--echo # invoked from the trigger. The errno has value 1 and the message is
--echo # is the text message 'This value is intentionally ignored'
INSERT INTO t1 VALUES (1), (2), (3);
--echo # Expected output is the rows (2), (3)
SELECT * FROM t1;
--echo # Clean up
DROP TABLE t1;
--echo # Test case 8: check that SQLSTATE '02TRG' doesn't have any special
--echo # meaning in AFTER triggers
--echo # Test case 8.1: check it for AFTER INSERT trigger
CREATE TABLE t1 (a INT);
--delimiter $
CREATE TRIGGER t1_ai AFTER INSERT ON t1 FOR EACH ROW
BEGIN
IF NEW.a = 1 THEN
SIGNAL SQLSTATE '02TRG';
END IF;
END
$
--delimiter ;
--echo # There is no a handler for the signal raised from inside
--echo # the trigger t1_ai, so the statement INSERT INTO fails
--echo # with the error ER_SIGNAL_NOT_FOUND
--error ER_SIGNAL_NOT_FOUND
INSERT INTO t1 VALUES (1);
SELECT * FROM t1;
--echo # Clean up
DROP TABLE t1;
--echo # Test case 8.2: check it for AFTER UPDATE trigger
CREATE TABLE t1 (a INT);
--delimiter $
CREATE TRIGGER t1_au AFTER UPDATE ON t1 FOR EACH ROW
BEGIN
IF OLD.a = 1 THEN
SIGNAL SQLSTATE '02TRG';
END IF;
END
$
--delimiter ;
INSERT INTO t1 VALUES (1);
--echo # There is no a handler for the signal raised from inside
--echo # the trigger t1_au, so the statement UPDATE fails
--echo # with the error ER_SIGNAL_NOT_FOUND
--error ER_SIGNAL_NOT_FOUND
UPDATE t1 SET a = 10;
SELECT * FROM t1;
--echo # Clean up
DROP TABLE t1;
--echo # Test case 8.3: check it for AFTER DELETE trigger
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (1);
--delimiter $
CREATE TRIGGER t1_ad AFTER DELETE ON t1 FOR EACH ROW
BEGIN
IF OLD.a = 1 THEN
SIGNAL SQLSTATE '02TRG';
END IF;
END
$
--delimiter ;
--echo # There is no a handler for the signal raised from inside
--echo # the trigger t1_ad, so the statement DELETE fails
--echo # with the error ER_SIGNAL_NOT_FOUND
--error ER_SIGNAL_NOT_FOUND
DELETE FROM t1;
SELECT * FROM t1;
--echo # Clean up
DROP TABLE t1;
--echo # End of 11.8 tests

View File

@@ -4949,7 +4949,7 @@ protected:
&m_curr_row_end, &m_master_reclength, m_rows_end); &m_curr_row_end, &m_master_reclength, m_rows_end);
} }
bool process_triggers(trg_event_type event, trg_action_time_type time_type, bool process_triggers(trg_event_type event, trg_action_time_type time_type,
bool old_row_is_record1); bool old_row_is_record1, bool *skip_row_indicator);
/** /**
Helper function to check whether there is an auto increment Helper function to check whether there is an auto increment

View File

@@ -6688,7 +6688,8 @@ Write_rows_log_event::do_after_row_operations(int error)
bool Rows_log_event::process_triggers(trg_event_type event, bool Rows_log_event::process_triggers(trg_event_type event,
trg_action_time_type time_type, trg_action_time_type time_type,
bool old_row_is_record1) bool old_row_is_record1,
bool *skip_row_indicator)
{ {
bool result; bool result;
DBUG_ENTER("Rows_log_event::process_triggers"); DBUG_ENTER("Rows_log_event::process_triggers");
@@ -6697,12 +6698,14 @@ bool Rows_log_event::process_triggers(trg_event_type event,
{ {
result= m_table->triggers->process_triggers(thd, event, result= m_table->triggers->process_triggers(thd, event,
time_type, time_type,
old_row_is_record1); old_row_is_record1,
skip_row_indicator);
} }
else else
result= m_table->triggers->process_triggers(thd, event, result= m_table->triggers->process_triggers(thd, event,
time_type, time_type,
old_row_is_record1); old_row_is_record1,
skip_row_indicator);
DBUG_RETURN(result); DBUG_RETURN(result);
} }
@@ -6875,12 +6878,18 @@ int Rows_log_event::write_row(rpl_group_info *rgi, const bool overwrite)
if (table->s->long_unique_table) if (table->s->long_unique_table)
table->update_virtual_fields(table->file, VCOL_UPDATE_FOR_WRITE); table->update_virtual_fields(table->file, VCOL_UPDATE_FOR_WRITE);
bool trg_skip_row= false;
if (invoke_triggers && if (invoke_triggers &&
unlikely(process_triggers(TRG_EVENT_INSERT, TRG_ACTION_BEFORE, TRUE))) unlikely(process_triggers(TRG_EVENT_INSERT, TRG_ACTION_BEFORE, true,
&trg_skip_row)))
{ {
DBUG_RETURN(HA_ERR_GENERIC); // in case if error is not set yet DBUG_RETURN(HA_ERR_GENERIC); // in case if error is not set yet
} }
/* In case any of triggers signals to skip the current row, do it. */
if (trg_skip_row)
return false;
// Handle INSERT. // Handle INSERT.
if (table->versioned(VERS_TIMESTAMP)) if (table->versioned(VERS_TIMESTAMP))
{ {
@@ -7056,7 +7065,7 @@ int Rows_log_event::write_row(rpl_group_info *rgi, const bool overwrite)
DBUG_PRINT("info",("Deleting offending row and trying to write new one again")); DBUG_PRINT("info",("Deleting offending row and trying to write new one again"));
if (invoke_triggers && if (invoke_triggers &&
unlikely(process_triggers(TRG_EVENT_DELETE, TRG_ACTION_BEFORE, unlikely(process_triggers(TRG_EVENT_DELETE, TRG_ACTION_BEFORE,
TRUE))) true, &trg_skip_row)))
error= HA_ERR_GENERIC; // in case if error is not set yet error= HA_ERR_GENERIC; // in case if error is not set yet
else else
{ {
@@ -7066,17 +7075,18 @@ int Rows_log_event::write_row(rpl_group_info *rgi, const bool overwrite)
table->file->print_error(error, MYF(0)); table->file->print_error(error, MYF(0));
DBUG_RETURN(error); DBUG_RETURN(error);
} }
if (invoke_triggers && if (invoke_triggers && !trg_skip_row &&
unlikely(process_triggers(TRG_EVENT_DELETE, TRG_ACTION_AFTER, unlikely(process_triggers(TRG_EVENT_DELETE, TRG_ACTION_AFTER,
TRUE))) true, nullptr)))
DBUG_RETURN(HA_ERR_GENERIC); // in case if error is not set yet DBUG_RETURN(HA_ERR_GENERIC); // in case if error is not set yet
} }
/* Will retry ha_write_row() with the offending row removed. */ /* Will retry ha_write_row() with the offending row removed. */
} }
} }
if (invoke_triggers && if (invoke_triggers && !trg_skip_row &&
unlikely(process_triggers(TRG_EVENT_INSERT, TRG_ACTION_AFTER, TRUE))) unlikely(process_triggers(TRG_EVENT_INSERT, TRG_ACTION_AFTER, true,
nullptr)))
error= HA_ERR_GENERIC; // in case if error is not set yet error= HA_ERR_GENERIC; // in case if error is not set yet
DBUG_RETURN(error); DBUG_RETURN(error);
@@ -7963,10 +7973,12 @@ int Delete_rows_log_event::do_exec_row(rpl_group_info *rgi)
#endif #endif
thd_proc_info(thd, message); thd_proc_info(thd, message);
bool trg_skip_row= false;
if (invoke_triggers && if (invoke_triggers &&
unlikely(process_triggers(TRG_EVENT_DELETE, TRG_ACTION_BEFORE, FALSE))) unlikely(process_triggers(TRG_EVENT_DELETE, TRG_ACTION_BEFORE, false,
&trg_skip_row)))
error= HA_ERR_GENERIC; // in case if error is not set yet error= HA_ERR_GENERIC; // in case if error is not set yet
if (likely(!error)) if (likely(!error) && !trg_skip_row)
{ {
if (m_vers_from_plain && m_table->versioned(VERS_TIMESTAMP)) if (m_vers_from_plain && m_table->versioned(VERS_TIMESTAMP))
{ {
@@ -7981,8 +7993,9 @@ int Delete_rows_log_event::do_exec_row(rpl_group_info *rgi)
error= m_table->file->ha_delete_row(m_table->record[0]); error= m_table->file->ha_delete_row(m_table->record[0]);
} }
} }
if (invoke_triggers && likely(!error) && if (invoke_triggers && likely(!error) && !trg_skip_row &&
unlikely(process_triggers(TRG_EVENT_DELETE, TRG_ACTION_AFTER, FALSE))) unlikely(process_triggers(TRG_EVENT_DELETE, TRG_ACTION_AFTER, false,
nullptr)))
error= HA_ERR_GENERIC; // in case if error is not set yet error= HA_ERR_GENERIC; // in case if error is not set yet
m_table->file->ha_index_or_rnd_end(); m_table->file->ha_index_or_rnd_end();
} }
@@ -8085,6 +8098,7 @@ Update_rows_log_event::do_exec_row(rpl_group_info *rgi)
const LEX_CSTRING &table_name= m_table->s->table_name; const LEX_CSTRING &table_name= m_table->s->table_name;
const char quote_char= const char quote_char=
get_quote_char_for_identifier(thd, table_name.str, table_name.length); get_quote_char_for_identifier(thd, table_name.str, table_name.length);
bool trg_skip_row= false;
my_snprintf(msg, sizeof msg, my_snprintf(msg, sizeof msg,
"Update_rows_log_event::find_row() on table %c%.*s%c", "Update_rows_log_event::find_row() on table %c%.*s%c",
quote_char, int(table_name.length), table_name.str, quote_char); quote_char, int(table_name.length), table_name.str, quote_char);
@@ -8180,12 +8194,18 @@ Update_rows_log_event::do_exec_row(rpl_group_info *rgi)
thd_proc_info(thd, message); thd_proc_info(thd, message);
if (invoke_triggers && if (invoke_triggers &&
unlikely(process_triggers(TRG_EVENT_UPDATE, TRG_ACTION_BEFORE, TRUE))) unlikely(process_triggers(TRG_EVENT_UPDATE, TRG_ACTION_BEFORE, true,
&trg_skip_row)))
{ {
error= HA_ERR_GENERIC; // in case if error is not set yet error= HA_ERR_GENERIC; // in case if error is not set yet
goto err; goto err;
} }
if (trg_skip_row)
{
error= 0;
goto err;
}
if (m_table->versioned()) if (m_table->versioned())
{ {
if (m_table->versioned(VERS_TIMESTAMP)) if (m_table->versioned(VERS_TIMESTAMP))
@@ -8211,7 +8231,8 @@ Update_rows_log_event::do_exec_row(rpl_group_info *rgi)
} }
if (invoke_triggers && likely(!error) && if (invoke_triggers && likely(!error) &&
unlikely(process_triggers(TRG_EVENT_UPDATE, TRG_ACTION_AFTER, TRUE))) unlikely(process_triggers(TRG_EVENT_UPDATE, TRG_ACTION_AFTER, true,
nullptr)))
error= HA_ERR_GENERIC; // in case if error is not set yet error= HA_ERR_GENERIC; // in case if error is not set yet

View File

@@ -12293,3 +12293,5 @@ ER_VECTOR_FORMAT_INVALID
eng "Invalid vector format at offset: %d for '%-.100s'. Must be a valid JSON array of numbers." eng "Invalid vector format at offset: %d for '%-.100s'. Must be a valid JSON array of numbers."
ER_VEC_DISTANCE_TYPE ER_VEC_DISTANCE_TYPE
eng "Cannot determine distance type for VEC_DISTANCE, index is not found" eng "Cannot determine distance type for VEC_DISTANCE, index is not found"
ER_SIGNAL_SKIP_ROW_FROM_TRIGGER
eng "The row is skipped by a trigger implementation"

View File

@@ -9154,6 +9154,32 @@ static bool not_null_fields_have_null_values(TABLE *table)
return false; return false;
} }
/**
The auxiliary function to determine whether a row should be skipped
or handled after invocation of triggers.
@return true in case either there is no BEFORE INSERT triggers or
the statement SIGNAL SQLSTATE '02TRG' wasn't executed by
BEFORE INSERT triggers, else return false meaning that the row
mustn't be processed by the INSERT statement.
*/
static inline bool no_need_to_skip_a_row(bool *skip_row_indicator)
{
/*
A row currently being processed mustn't be skipped in case the parameter
skip_row_indicator passed into fill_record_n_invoke_before_triggers()
is not null (that is true for BEFORE INSERT triggers) and a value stored by
this pointer has the value false, meaning that no SIGNAL statement with
the SQLSTATE = '02TRG' was raised on processing triggers.
*/
return
!skip_row_indicator ||
!*skip_row_indicator;
}
/** /**
Fill fields in list with values from the list of items and invoke Fill fields in list with values from the list of items and invoke
before triggers. before triggers.
@@ -9164,6 +9190,9 @@ static bool not_null_fields_have_null_values(TABLE *table)
@param values values to fill with @param values values to fill with
@param ignore_errors TRUE if we should ignore errors @param ignore_errors TRUE if we should ignore errors
@param event event type for triggers to be invoked @param event event type for triggers to be invoked
@param [out] skip_row_indicator the flag whose value tells whether to skip
the current record by INSERT/LOAD statements
or process it
@detail @detail
This function assumes that fields which values will be set and triggers This function assumes that fields which values will be set and triggers
@@ -9179,7 +9208,8 @@ bool
fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, fill_record_n_invoke_before_triggers(THD *thd, TABLE *table,
List<Item> &fields, List<Item> &fields,
List<Item> &values, bool ignore_errors, List<Item> &values, bool ignore_errors,
enum trg_event_type event) enum trg_event_type event,
bool *skip_row_indicator)
{ {
int result; int result;
Table_triggers_list *triggers= table->triggers; Table_triggers_list *triggers= table->triggers;
@@ -9191,7 +9221,7 @@ fill_record_n_invoke_before_triggers(THD *thd, TABLE *table,
{ {
if (triggers->process_triggers(thd, event, TRG_ACTION_BEFORE, if (triggers->process_triggers(thd, event, TRG_ACTION_BEFORE,
true, &fields) || true, skip_row_indicator, &fields) ||
not_null_fields_have_null_values(table)) not_null_fields_have_null_values(table))
{ {
return TRUE; return TRUE;
@@ -9201,7 +9231,8 @@ fill_record_n_invoke_before_triggers(THD *thd, TABLE *table,
Re-calculate virtual fields to cater for cases when base columns are Re-calculate virtual fields to cater for cases when base columns are
updated by the triggers. updated by the triggers.
*/ */
if (table->vfield && fields.elements) if (table->vfield && fields.elements &&
no_need_to_skip_a_row(skip_row_indicator))
{ {
Item *fld= (Item_field*) fields.head(); Item *fld= (Item_field*) fields.head();
Item_field *item_field= fld->field_for_view_update(); Item_field *item_field= fld->field_for_view_update();
@@ -9344,6 +9375,9 @@ err:
@param values values to fill with @param values values to fill with
@param ignore_errors TRUE if we should ignore errors @param ignore_errors TRUE if we should ignore errors
@param event event type for triggers to be invoked @param event event type for triggers to be invoked
@param [out] skip_row_indicator the flag whose value tells whether to skip
the current record by INSERT statement or
process it
@detail @detail
This function assumes that fields which values will be set and triggers This function assumes that fields which values will be set and triggers
@@ -9358,7 +9392,8 @@ err:
bool bool
fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, Field **ptr, fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, Field **ptr,
List<Item> &values, bool ignore_errors, List<Item> &values, bool ignore_errors,
enum trg_event_type event) enum trg_event_type event,
bool *skip_row_indicator)
{ {
bool result; bool result;
Table_triggers_list *triggers= table->triggers; Table_triggers_list *triggers= table->triggers;
@@ -9366,13 +9401,15 @@ fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, Field **ptr,
result= fill_record(thd, table, ptr, values, ignore_errors, false, false); result= fill_record(thd, table, ptr, values, ignore_errors, false, false);
if (!result && triggers && *ptr) if (!result && triggers && *ptr)
result= triggers->process_triggers(thd, event, TRG_ACTION_BEFORE, TRUE) || result= triggers->process_triggers(thd, event, TRG_ACTION_BEFORE, true,
skip_row_indicator) ||
not_null_fields_have_null_values(table); not_null_fields_have_null_values(table);
/* /*
Re-calculate virtual fields to cater for cases when base columns are Re-calculate virtual fields to cater for cases when base columns are
updated by the triggers. updated by the triggers.
*/ */
if (!result && triggers && *ptr) if (!result && triggers && *ptr &&
no_need_to_skip_a_row(skip_row_indicator))
{ {
DBUG_ASSERT(table == (*ptr)->table); DBUG_ASSERT(table == (*ptr)->table);
if (table->vfield) if (table->vfield)

View File

@@ -173,12 +173,14 @@ bool fill_record_n_invoke_before_triggers(THD *thd, TABLE *table,
List<Item> &fields, List<Item> &fields,
List<Item> &values, List<Item> &values,
bool ignore_errors, bool ignore_errors,
enum trg_event_type event); enum trg_event_type event,
bool *skip_row_indicator);
bool fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, bool fill_record_n_invoke_before_triggers(THD *thd, TABLE *table,
Field **field, Field **field,
List<Item> &values, List<Item> &values,
bool ignore_errors, bool ignore_errors,
enum trg_event_type event); enum trg_event_type event,
bool *skip_row_indicator);
bool insert_fields(THD *thd, Name_resolution_context *context, bool insert_fields(THD *thd, Name_resolution_context *context,
const Lex_ident_db &db_name, const Lex_ident_db &db_name,
const Lex_ident_table &table_name, const Lex_ident_table &table_name,

View File

@@ -2130,7 +2130,7 @@ public:
#define SUB_STMT_TRIGGER 1 #define SUB_STMT_TRIGGER 1
#define SUB_STMT_FUNCTION 2 #define SUB_STMT_FUNCTION 2
#define SUB_STMT_STAT_TABLES 4 #define SUB_STMT_STAT_TABLES 4
#define SUB_STMT_BEFORE_TRIGGER 8
class Sub_statement_state class Sub_statement_state
{ {
@@ -6666,7 +6666,7 @@ class select_insert :public select_result_interceptor {
int prepare(List<Item> &list, SELECT_LEX_UNIT *u) override; int prepare(List<Item> &list, SELECT_LEX_UNIT *u) override;
int prepare2(JOIN *join) override; int prepare2(JOIN *join) override;
int send_data(List<Item> &items) override; int send_data(List<Item> &items) override;
virtual bool store_values(List<Item> &values); virtual bool store_values(List<Item> &values, bool *trg_skip_row);
virtual bool can_rollback_data() { return 0; } virtual bool can_rollback_data() { return 0; }
bool prepare_eof(); bool prepare_eof();
bool send_ok_packet(); bool send_ok_packet();
@@ -6711,7 +6711,7 @@ public:
int prepare(List<Item> &list, SELECT_LEX_UNIT *u) override; int prepare(List<Item> &list, SELECT_LEX_UNIT *u) override;
int binlog_show_create_table(TABLE **tables, uint count); int binlog_show_create_table(TABLE **tables, uint count);
bool store_values(List<Item> &values) override; bool store_values(List<Item> &values, bool *trg_skip_row) override;
bool send_eof() override; bool send_eof() override;
void abort_result_set() override; void abort_result_set() override;
bool can_rollback_data() override { return 1; } bool can_rollback_data() override { return 1; }

View File

@@ -276,7 +276,7 @@ int update_portion_of_time(THD *thd, TABLE *table,
if (likely(!res) && table->triggers) if (likely(!res) && table->triggers)
res= table->triggers->process_triggers(thd, TRG_EVENT_INSERT, res= table->triggers->process_triggers(thd, TRG_EVENT_INSERT,
TRG_ACTION_AFTER, true); TRG_ACTION_AFTER, true, nullptr);
restore_record(table, record[1]); restore_record(table, record[1]);
if (res) if (res)
table->file->restore_auto_increment(prev_insert_id); table->file->restore_auto_increment(prev_insert_id);
@@ -872,14 +872,20 @@ bool Sql_cmd_delete::delete_from_single_table(THD *thd)
delete_history); delete_history);
if (delete_record) if (delete_record)
{ {
bool trg_skip_row= false;
if (!delete_history && table->triggers && if (!delete_history && table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_DELETE, table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
TRG_ACTION_BEFORE, FALSE)) TRG_ACTION_BEFORE, FALSE,
&trg_skip_row))
{ {
error= 1; error= 1;
break; break;
} }
if (trg_skip_row)
continue;
// no LIMIT / OFFSET // no LIMIT / OFFSET
if (returning && result->send_data(returning->item_list) < 0) if (returning && result->send_data(returning->item_list) < 0)
{ {
@@ -911,7 +917,8 @@ bool Sql_cmd_delete::delete_from_single_table(THD *thd)
deleted++; deleted++;
if (!delete_history && table->triggers && if (!delete_history && table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_DELETE, table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
TRG_ACTION_AFTER, FALSE)) TRG_ACTION_AFTER, false,
nullptr))
{ {
error= 1; error= 1;
break; break;
@@ -1256,12 +1263,19 @@ int multi_delete::send_data(List<Item> &values)
if (secure_counter < 0) if (secure_counter < 0)
{ {
bool trg_skip_row= false;
/* We are scanning the current table */ /* We are scanning the current table */
DBUG_ASSERT(del_table == table_being_deleted); DBUG_ASSERT(del_table == table_being_deleted);
if (table->triggers && if (table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_DELETE, table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
TRG_ACTION_BEFORE, FALSE)) TRG_ACTION_BEFORE, false,
&trg_skip_row))
DBUG_RETURN(1); DBUG_RETURN(1);
if (trg_skip_row)
continue;
table->status|= STATUS_DELETED; table->status|= STATUS_DELETED;
error= table->delete_row(); error= table->delete_row();
@@ -1272,7 +1286,8 @@ int multi_delete::send_data(List<Item> &values)
thd->transaction->stmt.modified_non_trans_table= TRUE; thd->transaction->stmt.modified_non_trans_table= TRUE;
if (table->triggers && if (table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_DELETE, table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
TRG_ACTION_AFTER, FALSE)) TRG_ACTION_AFTER, false,
nullptr))
DBUG_RETURN(1); DBUG_RETURN(1);
} }
else if (!ignore) else if (!ignore)
@@ -1435,14 +1450,20 @@ int multi_delete::do_table_deletes(TABLE *table, SORT_INFO *sort_info,
bool will_batch= !table->file->start_bulk_delete(); bool will_batch= !table->file->start_bulk_delete();
while (likely(!(local_error= info.read_record())) && likely(!thd->killed)) while (likely(!(local_error= info.read_record())) && likely(!thd->killed))
{ {
bool trg_skip_row= false;
if (table->triggers && if (table->triggers &&
unlikely(table->triggers->process_triggers(thd, TRG_EVENT_DELETE, unlikely(table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
TRG_ACTION_BEFORE, FALSE))) TRG_ACTION_BEFORE, false,
&trg_skip_row)))
{ {
local_error= 1; local_error= 1;
break; break;
} }
if (trg_skip_row)
continue;
local_error= table->delete_row(); local_error= table->delete_row();
if (unlikely(local_error) && !ignore) if (unlikely(local_error) && !ignore)
{ {
@@ -1460,7 +1481,8 @@ int multi_delete::do_table_deletes(TABLE *table, SORT_INFO *sort_info,
deleted++; deleted++;
if (table->triggers && if (table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_DELETE, table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
TRG_ACTION_AFTER, FALSE)) TRG_ACTION_AFTER, false,
nullptr))
{ {
local_error= 1; local_error= 1;
break; break;

View File

@@ -235,7 +235,8 @@ Sql_condition::get_message_octet_length() const
} }
void Sql_state_errno_level::assign_defaults(const Sql_state_errno *from) void Sql_state_errno_level::assign_defaults(THD *thd,
const Sql_state_errno *from)
{ {
DBUG_ASSERT(from); DBUG_ASSERT(from);
int sqlerrno= from->get_sql_errno(); int sqlerrno= from->get_sql_errno();
@@ -255,7 +256,17 @@ void Sql_state_errno_level::assign_defaults(const Sql_state_errno *from)
else if (Sql_state::is_not_found()) /* SQLSTATE class "02": not found. */ else if (Sql_state::is_not_found()) /* SQLSTATE class "02": not found. */
{ {
m_level= Sql_condition::WARN_LEVEL_ERROR; m_level= Sql_condition::WARN_LEVEL_ERROR;
m_sql_errno= sqlerrno ? sqlerrno : ER_SIGNAL_NOT_FOUND; if (sqlerrno)
m_sql_errno= sqlerrno;
else
{
if ((thd->in_sub_stmt & (SUB_STMT_TRIGGER | SUB_STMT_BEFORE_TRIGGER)) ==
(SUB_STMT_TRIGGER | SUB_STMT_BEFORE_TRIGGER) &&
strcmp(get_sqlstate(), "02TRG") == 0)
m_sql_errno= ER_SIGNAL_SKIP_ROW_FROM_TRIGGER;
else
m_sql_errno= ER_SIGNAL_NOT_FOUND;
}
} }
else /* other SQLSTATE classes : error. */ else /* other SQLSTATE classes : error. */
{ {
@@ -268,7 +279,7 @@ void Sql_state_errno_level::assign_defaults(const Sql_state_errno *from)
void Sql_condition::assign_defaults(THD *thd, const Sql_state_errno *from) void Sql_condition::assign_defaults(THD *thd, const Sql_state_errno *from)
{ {
if (from) if (from)
Sql_state_errno_level::assign_defaults(from); Sql_state_errno_level::assign_defaults(thd, from);
if (!get_message_text()) if (!get_message_text())
set_builtin_message_text(ER(get_sql_errno())); set_builtin_message_text(ER(get_sql_errno()));
} }

View File

@@ -167,7 +167,7 @@ protected:
/** Severity (error, warning, note) of this condition. */ /** Severity (error, warning, note) of this condition. */
enum_warning_level m_level; enum_warning_level m_level;
void assign_defaults(const Sql_state_errno *value); void assign_defaults(THD *thd, const Sql_state_errno *value);
public: public:
/** /**

View File

@@ -1057,6 +1057,8 @@ bool mysql_insert(THD *thd, TABLE_LIST *table_list,
while ((values= its++)) while ((values= its++))
{ {
bool trg_skip_row= false;
thd->get_stmt_da()->inc_current_row_for_warning(); thd->get_stmt_da()->inc_current_row_for_warning();
if (fields.elements || !value_count) if (fields.elements || !value_count)
{ {
@@ -1070,7 +1072,8 @@ bool mysql_insert(THD *thd, TABLE_LIST *table_list,
if (unlikely(fill_record_n_invoke_before_triggers(thd, table, fields, if (unlikely(fill_record_n_invoke_before_triggers(thd, table, fields,
*values, 0, *values, 0,
TRG_EVENT_INSERT))) TRG_EVENT_INSERT,
&trg_skip_row)))
{ {
if (values_list.elements != 1 && ! thd->is_error()) if (values_list.elements != 1 && ! thd->is_error())
{ {
@@ -1119,7 +1122,8 @@ bool mysql_insert(THD *thd, TABLE_LIST *table_list,
table-> table->
field_to_fill(), field_to_fill(),
*values, 0, *values, 0,
TRG_EVENT_INSERT))) TRG_EVENT_INSERT,
&trg_skip_row)))
{ {
if (values_list.elements != 1 && ! thd->is_error()) if (values_list.elements != 1 && ! thd->is_error())
{ {
@@ -1131,6 +1135,8 @@ bool mysql_insert(THD *thd, TABLE_LIST *table_list,
} }
} }
if (trg_skip_row)
continue;
/* /*
with triggers a field can get a value *conditionally*, so we have to with triggers a field can get a value *conditionally*, so we have to
repeat has_no_default_value() check for every row repeat has_no_default_value() check for every row
@@ -2077,13 +2083,20 @@ int write_record(THD *thd, TABLE *table, COPY_INFO *info, select_result *sink)
*/ */
DBUG_ASSERT(info->update_fields->elements == DBUG_ASSERT(info->update_fields->elements ==
info->update_values->elements); info->update_values->elements);
bool trg_skip_row= false;
if (fill_record_n_invoke_before_triggers(thd, table, if (fill_record_n_invoke_before_triggers(thd, table,
*info->update_fields, *info->update_fields,
*info->update_values, *info->update_values,
info->ignore, info->ignore,
TRG_EVENT_UPDATE)) TRG_EVENT_UPDATE,
&trg_skip_row))
goto before_trg_err; goto before_trg_err;
if (trg_skip_row)
goto ok;
bool different_records= (!records_are_comparable(table) || bool different_records= (!records_are_comparable(table) ||
compare_record(table)); compare_record(table));
/* /*
@@ -2158,7 +2171,8 @@ int write_record(THD *thd, TABLE *table, COPY_INFO *info, select_result *sink)
insert_id_for_cur_row= table->file->insert_id_for_cur_row= 0; insert_id_for_cur_row= table->file->insert_id_for_cur_row= 0;
trg_error= (table->triggers && trg_error= (table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
TRG_ACTION_AFTER, TRUE)); TRG_ACTION_AFTER, true,
nullptr));
info->copied++; info->copied++;
} }
@@ -2254,11 +2268,17 @@ int write_record(THD *thd, TABLE *table, COPY_INFO *info, select_result *sink)
} }
else else
{ {
bool trg_skip_row= false;
if (table->triggers && if (table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_DELETE, table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
TRG_ACTION_BEFORE, TRUE)) TRG_ACTION_BEFORE, true,
&trg_skip_row))
goto before_trg_err; goto before_trg_err;
if (trg_skip_row)
continue;
if (!table->versioned(VERS_TIMESTAMP)) if (!table->versioned(VERS_TIMESTAMP))
error= table->file->ha_delete_row(table->record[1]); error= table->file->ha_delete_row(table->record[1]);
else else
@@ -2280,7 +2300,8 @@ int write_record(THD *thd, TABLE *table, COPY_INFO *info, select_result *sink)
thd->transaction->stmt.modified_non_trans_table= TRUE; thd->transaction->stmt.modified_non_trans_table= TRUE;
if (table->triggers && if (table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_DELETE, table->triggers->process_triggers(thd, TRG_EVENT_DELETE,
TRG_ACTION_AFTER, TRUE)) TRG_ACTION_AFTER, true,
nullptr))
{ {
trg_error= 1; trg_error= 1;
goto after_trg_or_ignored_err; goto after_trg_or_ignored_err;
@@ -2327,7 +2348,8 @@ after_trg_n_copied_inc:
thd->record_first_successful_insert_id_in_cur_stmt(table->file->insert_id_for_cur_row); thd->record_first_successful_insert_id_in_cur_stmt(table->file->insert_id_for_cur_row);
trg_error= (table->triggers && trg_error= (table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_INSERT, table->triggers->process_triggers(thd, TRG_EVENT_INSERT,
TRG_ACTION_AFTER, TRUE)); TRG_ACTION_AFTER, true,
nullptr));
ok: ok:
/* /*
@@ -4279,9 +4301,10 @@ int select_insert::send_data(List<Item> &values)
{ {
DBUG_ENTER("select_insert::send_data"); DBUG_ENTER("select_insert::send_data");
bool error=0; bool error=0;
bool trg_skip_row= false;
thd->count_cuted_fields= CHECK_FIELD_WARN; // Calculate cuted fields thd->count_cuted_fields= CHECK_FIELD_WARN; // Calculate cuted fields
if (store_values(values)) if (store_values(values, &trg_skip_row))
DBUG_RETURN(1); DBUG_RETURN(1);
thd->count_cuted_fields= CHECK_FIELD_ERROR_FOR_NULL; thd->count_cuted_fields= CHECK_FIELD_ERROR_FOR_NULL;
if (unlikely(thd->is_error())) if (unlikely(thd->is_error()))
@@ -4300,10 +4323,11 @@ int select_insert::send_data(List<Item> &values)
} }
} }
if (!trg_skip_row)
error= write_record(thd, table, &info, sel_result); error= write_record(thd, table, &info, sel_result);
table->auto_increment_field_not_null= FALSE; table->auto_increment_field_not_null= FALSE;
if (likely(!error)) if (likely(!error) && !trg_skip_row)
{ {
if (table->triggers || info.handle_duplicates == DUP_UPDATE) if (table->triggers || info.handle_duplicates == DUP_UPDATE)
{ {
@@ -4337,7 +4361,7 @@ int select_insert::send_data(List<Item> &values)
} }
bool select_insert::store_values(List<Item> &values) bool select_insert::store_values(List<Item> &values, bool *trg_skip_row)
{ {
DBUG_ENTER("select_insert::store_values"); DBUG_ENTER("select_insert::store_values");
bool error; bool error;
@@ -4345,10 +4369,12 @@ bool select_insert::store_values(List<Item> &values)
table->reset_default_fields(); table->reset_default_fields();
if (fields->elements) if (fields->elements)
error= fill_record_n_invoke_before_triggers(thd, table, *fields, values, error= fill_record_n_invoke_before_triggers(thd, table, *fields, values,
true, TRG_EVENT_INSERT); true, TRG_EVENT_INSERT,
trg_skip_row);
else else
error= fill_record_n_invoke_before_triggers(thd, table, table->field_to_fill(), error= fill_record_n_invoke_before_triggers(thd, table, table->field_to_fill(),
values, true, TRG_EVENT_INSERT); values, true, TRG_EVENT_INSERT,
trg_skip_row);
DBUG_RETURN(error); DBUG_RETURN(error);
} }
@@ -5138,10 +5164,11 @@ bool binlog_drop_table(THD *thd, TABLE *table)
} }
bool select_create::store_values(List<Item> &values) bool select_create::store_values(List<Item> &values, bool *trg_skip_row)
{ {
return fill_record_n_invoke_before_triggers(thd, table, field, values, return fill_record_n_invoke_before_triggers(thd, table, field, values,
true, TRG_EVENT_INSERT); true, TRG_EVENT_INSERT,
trg_skip_row);
} }

View File

@@ -1013,7 +1013,7 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
List_iterator_fast<Item> it(fields_vars); List_iterator_fast<Item> it(fields_vars);
Item *item; Item *item;
TABLE *table= table_list->table; TABLE *table= table_list->table;
bool err, progress_reports; bool err= false, progress_reports;
ulonglong counter, time_to_report_progress; ulonglong counter, time_to_report_progress;
DBUG_ENTER("read_fixed_length"); DBUG_ENTER("read_fixed_length");
@@ -1090,10 +1090,11 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
thd->get_stmt_da()->current_row_for_warning()); thd->get_stmt_da()->current_row_for_warning());
} }
bool trg_skip_row= false;
if (thd->killed || if (thd->killed ||
fill_record_n_invoke_before_triggers(thd, table, set_fields, set_values, fill_record_n_invoke_before_triggers(thd, table, set_fields, set_values,
ignore_check_option_errors, ignore_check_option_errors,
TRG_EVENT_INSERT)) TRG_EVENT_INSERT, &trg_skip_row))
DBUG_RETURN(1); DBUG_RETURN(1);
switch (table_list->view_check_option(thd, ignore_check_option_errors)) { switch (table_list->view_check_option(thd, ignore_check_option_errors)) {
@@ -1104,6 +1105,7 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
DBUG_RETURN(-1); DBUG_RETURN(-1);
} }
if (!trg_skip_row)
err= write_record(thd, table, &info); err= write_record(thd, table, &info);
table->auto_increment_field_not_null= FALSE; table->auto_increment_field_not_null= FALSE;
if (err) if (err)
@@ -1238,13 +1240,21 @@ read_sep_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
} }
} }
bool trg_skip_row= false;
if (unlikely(thd->killed) || if (unlikely(thd->killed) ||
unlikely(fill_record_n_invoke_before_triggers(thd, table, set_fields, unlikely(fill_record_n_invoke_before_triggers(thd, table, set_fields,
set_values, set_values,
ignore_check_option_errors, ignore_check_option_errors,
TRG_EVENT_INSERT))) TRG_EVENT_INSERT,
&trg_skip_row)))
DBUG_RETURN(1); DBUG_RETURN(1);
if (trg_skip_row)
{
read_info.next_line();
continue;
}
switch (table_list->view_check_option(thd, switch (table_list->view_check_option(thd,
ignore_check_option_errors)) { ignore_check_option_errors)) {
case VIEW_CHECK_SKIP: case VIEW_CHECK_SKIP:
@@ -1361,12 +1371,19 @@ read_xml_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list,
DBUG_ASSERT(!item); DBUG_ASSERT(!item);
bool trg_skip_row= false;
if (thd->killed || if (thd->killed ||
fill_record_n_invoke_before_triggers(thd, table, set_fields, set_values, fill_record_n_invoke_before_triggers(thd, table, set_fields, set_values,
ignore_check_option_errors, ignore_check_option_errors,
TRG_EVENT_INSERT)) TRG_EVENT_INSERT, &trg_skip_row))
DBUG_RETURN(1); DBUG_RETURN(1);
if (trg_skip_row)
{
read_info.next_line();
continue;
}
switch (table_list->view_check_option(thd, switch (table_list->view_check_option(thd,
ignore_check_option_errors)) { ignore_check_option_errors)) {
case VIEW_CHECK_SKIP: case VIEW_CHECK_SKIP:

View File

@@ -2568,6 +2568,41 @@ end:
} }
/**
Check that a BEFORE trigger has raised the signal to inform that
a current row being processed must be skipped.
@param da Diagnostics area
@param[out] skip_row_indicator where to store the fact about skipping
the row
@param time_type time when trigger is invoked (i.e. before or
after)
@return true in case the current row must be skipped, else false
*/
static inline bool do_skip_row_indicator(Diagnostics_area *da,
bool *skip_row_indicator,
trg_action_time_type time_type)
{
if (!skip_row_indicator)
return false;
if (time_type == TRG_ACTION_BEFORE &&
/*
The '02' class signals a 'no data' condition, the subclass '02TRG'
means 'no data in trigger' and this condition shouldn't be treated
as an error.
*/
strcmp(da->get_sqlstate(), "02TRG") == 0)
{
*skip_row_indicator= true;
return true;
}
return false;
}
/** /**
Execute trigger for given (event, time) pair. Execute trigger for given (event, time) pair.
@@ -2578,6 +2613,8 @@ end:
@param event @param event
@param time_type @param time_type
@param old_row_is_record1 @param old_row_is_record1
@param[out] skip_row_indicator the flag to tell whether a row must be
skipped by the INSERT statement
@return Error status. @return Error status.
@retval FALSE on success. @retval FALSE on success.
@@ -2588,6 +2625,7 @@ bool Table_triggers_list::process_triggers(THD *thd,
trg_event_type event, trg_event_type event,
trg_action_time_type time_type, trg_action_time_type time_type,
bool old_row_is_record1, bool old_row_is_record1,
bool *skip_row_indicator,
List<Item> *fields_in_update_stmt) List<Item> *fields_in_update_stmt)
{ {
bool err_status; bool err_status;
@@ -2595,6 +2633,18 @@ bool Table_triggers_list::process_triggers(THD *thd,
Trigger *trigger; Trigger *trigger;
SELECT_LEX *save_current_select; SELECT_LEX *save_current_select;
/*
skip_row_indicator != nullptr for BEFORE INSERT/UPDATE/DELETE triggers
*/
DBUG_ASSERT((time_type == TRG_ACTION_BEFORE && skip_row_indicator) ||
(time_type == TRG_ACTION_AFTER && !skip_row_indicator));
/*
In case skip_indicator points to an out variable, its initial value
must be false
*/
DBUG_ASSERT(!skip_row_indicator ||
(skip_row_indicator && *skip_row_indicator == false));
if (check_for_broken_triggers()) if (check_for_broken_triggers())
return TRUE; return TRUE;
@@ -2618,8 +2668,21 @@ bool Table_triggers_list::process_triggers(THD *thd,
*/ */
DBUG_ASSERT(trigger_table->pos_in_table_list->trg_event_map & trg2bit(event)); DBUG_ASSERT(trigger_table->pos_in_table_list->trg_event_map & trg2bit(event));
if (time_type == TRG_ACTION_AFTER)
thd->reset_sub_statement_state(&statement_state, SUB_STMT_TRIGGER); thd->reset_sub_statement_state(&statement_state, SUB_STMT_TRIGGER);
else
/*
For time type TRG_ACTION_BEFORE, set extra flag SUB_STMT_BEFORE_TRIGGER
at sub statement state in addition to SUB_STMT_TRIGGER in order to
be able to reset the m_sql_errno to the value
ER_SIGNAL_SKIP_ROW_FROM_TRIGGER
in case signal is raised with SQLSTATE "02TRG" from within a
BEFORE trigger and don't modify m_sql_errno in case the signal is raised
from AFTER trigger.
@see Sql_state_errno_level::assign_defaults
*/
thd->reset_sub_statement_state(&statement_state,
SUB_STMT_TRIGGER | SUB_STMT_BEFORE_TRIGGER);
/* /*
Reset current_select before call execute_trigger() and Reset current_select before call execute_trigger() and
restore it after return from one. This way error is set restore it after return from one. This way error is set
@@ -2656,6 +2719,18 @@ bool Table_triggers_list::process_triggers(THD *thd,
&trigger_table->s->db, &trigger_table->s->db,
&trigger_table->s->table_name, &trigger_table->s->table_name,
&trigger->subject_table_grants); &trigger->subject_table_grants);
if (err_status &&
do_skip_row_indicator(thd->get_stmt_da(), skip_row_indicator,
time_type))
{
/* Reset DA that is set on handling the statement
SIGNAL SSQLSTATE "02TRG"
raised from within a trigger */
err_status= false;
thd->get_stmt_da()->reset_diagnostics_area();
}
status_var_increment(thd->status_var.executed_triggers); status_var_increment(thd->status_var.executed_triggers);
} while (!err_status && (trigger= trigger->next)); } while (!err_status && (trigger= trigger->next));
thd->bulk_param= save_bulk_param; thd->bulk_param= save_bulk_param;

View File

@@ -260,6 +260,7 @@ public:
bool process_triggers(THD *thd, trg_event_type event, bool process_triggers(THD *thd, trg_event_type event,
trg_action_time_type time_type, trg_action_time_type time_type,
bool old_row_is_record1, bool old_row_is_record1,
bool *skip_row_indicator,
List<Item> *fields_in_update_stmt= nullptr); List<Item> *fields_in_update_stmt= nullptr);
void empty_lists(); void empty_lists();
bool create_lists_needed_for_files(MEM_ROOT *root); bool create_lists_needed_for_files(MEM_ROOT *root);

View File

@@ -984,9 +984,18 @@ update_begin:
cut_fields_for_portion_of_time(thd, table, cut_fields_for_portion_of_time(thd, table,
table_list->period_conditions); table_list->period_conditions);
bool trg_skip_row= false;
if (fill_record_n_invoke_before_triggers(thd, table, *fields, *values, 0, if (fill_record_n_invoke_before_triggers(thd, table, *fields, *values, 0,
TRG_EVENT_UPDATE)) TRG_EVENT_UPDATE,
&trg_skip_row))
break; /* purecov: inspected */ break; /* purecov: inspected */
if (trg_skip_row)
{
updated_or_same++;
thd->get_stmt_da()->inc_current_row_for_warning();
continue;
}
found++; found++;
@@ -1115,6 +1124,7 @@ error:
if (table->triggers && if (table->triggers &&
unlikely(table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, unlikely(table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
TRG_ACTION_AFTER, true, TRG_ACTION_AFTER, true,
nullptr,
fields))) fields)))
{ {
error= 1; error= 1;
@@ -2365,11 +2375,17 @@ int multi_update::send_data(List<Item> &not_used_values)
table->status|= STATUS_UPDATED; table->status|= STATUS_UPDATED;
store_record(table,record[1]); store_record(table,record[1]);
bool trg_skip_row= false;
if (fill_record_n_invoke_before_triggers(thd, table, if (fill_record_n_invoke_before_triggers(thd, table,
*fields_for_table[offset], *fields_for_table[offset],
*values_for_table[offset], 0, *values_for_table[offset], 0,
TRG_EVENT_UPDATE)) TRG_EVENT_UPDATE,
&trg_skip_row))
DBUG_RETURN(1); DBUG_RETURN(1);
if (trg_skip_row)
continue;
/* /*
Reset the table->auto_increment_field_not_null as it is valid for Reset the table->auto_increment_field_not_null as it is valid for
only one row. only one row.
@@ -2442,6 +2458,7 @@ int multi_update::send_data(List<Item> &not_used_values)
if (table->triggers && if (table->triggers &&
unlikely(table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, unlikely(table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
TRG_ACTION_AFTER, true, TRG_ACTION_AFTER, true,
nullptr,
fields_for_table[offset]))) fields_for_table[offset])))
DBUG_RETURN(1); DBUG_RETURN(1);
} }
@@ -2700,12 +2717,18 @@ int multi_update::do_updates()
if (table->vfield && if (table->vfield &&
table->update_virtual_fields(table->file, VCOL_UPDATE_FOR_WRITE)) table->update_virtual_fields(table->file, VCOL_UPDATE_FOR_WRITE))
goto err2; goto err2;
bool trg_skip_row= false;
if (table->triggers && if (table->triggers &&
table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
TRG_ACTION_BEFORE, true, TRG_ACTION_BEFORE, true,
&trg_skip_row,
fields_for_table[offset])) fields_for_table[offset]))
goto err2; goto err2;
if (trg_skip_row)
continue;
if (!can_compare_record || compare_record(table)) if (!can_compare_record || compare_record(table))
{ {
int error; int error;
@@ -2764,6 +2787,7 @@ int multi_update::do_updates()
if (table->triggers && if (table->triggers &&
unlikely(table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, unlikely(table->triggers->process_triggers(thd, TRG_EVENT_UPDATE,
TRG_ACTION_AFTER, true, TRG_ACTION_AFTER, true,
nullptr,
fields_for_table[offset]))) fields_for_table[offset])))
goto err2; goto err2;
} }

View File

@@ -9467,16 +9467,23 @@ int TABLE::period_make_insert(Item *src, Field *dst)
res= update_generated_fields(); res= update_generated_fields();
} }
bool trg_skip_row= false;
if (likely(!res) && triggers) if (likely(!res) && triggers)
res= triggers->process_triggers(thd, TRG_EVENT_INSERT, res= triggers->process_triggers(thd, TRG_EVENT_INSERT,
TRG_ACTION_BEFORE, true); TRG_ACTION_BEFORE, true, &trg_skip_row);
if (trg_skip_row)
{
restore_record(this, record[1]);
return false;
}
if (likely(!res)) if (likely(!res))
res = file->ha_write_row(record[0]); res = file->ha_write_row(record[0]);
if (likely(!res) && triggers) if (likely(!res) && triggers)
res= triggers->process_triggers(thd, TRG_EVENT_INSERT, res= triggers->process_triggers(thd, TRG_EVENT_INSERT,
TRG_ACTION_AFTER, true); TRG_ACTION_AFTER, true, nullptr);
restore_record(this, record[1]); restore_record(this, record[1]);
if (res) if (res)