diff --git a/mysql-test/main/mdev-34724.result b/mysql-test/main/mdev-34724.result new file mode 100644 index 00000000000..03980b045f7 --- /dev/null +++ b/mysql-test/main/mdev-34724.result @@ -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 ''; +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 diff --git a/mysql-test/main/mdev-34724.test b/mysql-test/main/mdev-34724.test new file mode 100644 index 00000000000..fb8ffa8be13 --- /dev/null +++ b/mysql-test/main/mdev-34724.test @@ -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 '' + +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 diff --git a/sql/log_event.h b/sql/log_event.h index 486e89b9324..afa391fe07b 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -4949,7 +4949,7 @@ protected: &m_curr_row_end, &m_master_reclength, m_rows_end); } 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 diff --git a/sql/log_event_server.cc b/sql/log_event_server.cc index 57bd3313acc..a53bf654902 100644 --- a/sql/log_event_server.cc +++ b/sql/log_event_server.cc @@ -6688,7 +6688,8 @@ Write_rows_log_event::do_after_row_operations(int error) bool Rows_log_event::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) { bool result; 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, time_type, - old_row_is_record1); + old_row_is_record1, + skip_row_indicator); } else result= m_table->triggers->process_triggers(thd, event, time_type, - old_row_is_record1); + old_row_is_record1, + skip_row_indicator); 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) table->update_virtual_fields(table->file, VCOL_UPDATE_FOR_WRITE); + bool trg_skip_row= false; 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 } + /* In case any of triggers signals to skip the current row, do it. */ + if (trg_skip_row) + return false; + // Handle INSERT. 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")); if (invoke_triggers && 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 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)); DBUG_RETURN(error); } - if (invoke_triggers && + if (invoke_triggers && !trg_skip_row && 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 } /* Will retry ha_write_row() with the offending row removed. */ } } - if (invoke_triggers && - unlikely(process_triggers(TRG_EVENT_INSERT, TRG_ACTION_AFTER, TRUE))) + if (invoke_triggers && !trg_skip_row && + unlikely(process_triggers(TRG_EVENT_INSERT, TRG_ACTION_AFTER, true, + nullptr))) error= HA_ERR_GENERIC; // in case if error is not set yet DBUG_RETURN(error); @@ -7963,10 +7973,12 @@ int Delete_rows_log_event::do_exec_row(rpl_group_info *rgi) #endif thd_proc_info(thd, message); + bool trg_skip_row= false; 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 - if (likely(!error)) + if (likely(!error) && !trg_skip_row) { 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]); } } - if (invoke_triggers && likely(!error) && - unlikely(process_triggers(TRG_EVENT_DELETE, TRG_ACTION_AFTER, FALSE))) + if (invoke_triggers && likely(!error) && !trg_skip_row && + unlikely(process_triggers(TRG_EVENT_DELETE, TRG_ACTION_AFTER, false, + nullptr))) error= HA_ERR_GENERIC; // in case if error is not set yet 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 char quote_char= get_quote_char_for_identifier(thd, table_name.str, table_name.length); + bool trg_skip_row= false; my_snprintf(msg, sizeof msg, "Update_rows_log_event::find_row() on table %c%.*s%c", 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); 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 goto err; } + if (trg_skip_row) + { + error= 0; + goto err; + } if (m_table->versioned()) { 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) && - 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 diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 73c146472fe..32b2fac5a4c 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -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." ER_VEC_DISTANCE_TYPE 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" diff --git a/sql/sql_base.cc b/sql/sql_base.cc index d7c118c0fdb..b08c543127d 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -9154,6 +9154,32 @@ static bool not_null_fields_have_null_values(TABLE *table) 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 before triggers. @@ -9164,6 +9190,9 @@ static bool not_null_fields_have_null_values(TABLE *table) @param values values to fill with @param ignore_errors TRUE if we should ignore errors @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 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, List &fields, List &values, bool ignore_errors, - enum trg_event_type event) + enum trg_event_type event, + bool *skip_row_indicator) { int result; 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, - true, &fields) || + true, skip_row_indicator, &fields) || not_null_fields_have_null_values(table)) { 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 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_field *item_field= fld->field_for_view_update(); @@ -9344,6 +9375,9 @@ err: @param values values to fill with @param ignore_errors TRUE if we should ignore errors @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 This function assumes that fields which values will be set and triggers @@ -9358,7 +9392,8 @@ err: bool fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, Field **ptr, List &values, bool ignore_errors, - enum trg_event_type event) + enum trg_event_type event, + bool *skip_row_indicator) { bool result; 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); 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); /* Re-calculate virtual fields to cater for cases when base columns are 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); if (table->vfield) diff --git a/sql/sql_base.h b/sql/sql_base.h index 00870fae88d..6b1dee77f54 100644 --- a/sql/sql_base.h +++ b/sql/sql_base.h @@ -173,12 +173,14 @@ bool fill_record_n_invoke_before_triggers(THD *thd, TABLE *table, List &fields, List &values, 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, Field **field, List &values, 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, const Lex_ident_db &db_name, const Lex_ident_table &table_name, diff --git a/sql/sql_class.h b/sql/sql_class.h index 92cca6e4448..208e4ba10e6 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -2130,7 +2130,7 @@ public: #define SUB_STMT_TRIGGER 1 #define SUB_STMT_FUNCTION 2 #define SUB_STMT_STAT_TABLES 4 - +#define SUB_STMT_BEFORE_TRIGGER 8 class Sub_statement_state { @@ -6666,7 +6666,7 @@ class select_insert :public select_result_interceptor { int prepare(List &list, SELECT_LEX_UNIT *u) override; int prepare2(JOIN *join) override; int send_data(List &items) override; - virtual bool store_values(List &values); + virtual bool store_values(List &values, bool *trg_skip_row); virtual bool can_rollback_data() { return 0; } bool prepare_eof(); bool send_ok_packet(); @@ -6711,7 +6711,7 @@ public: int prepare(List &list, SELECT_LEX_UNIT *u) override; int binlog_show_create_table(TABLE **tables, uint count); - bool store_values(List &values) override; + bool store_values(List &values, bool *trg_skip_row) override; bool send_eof() override; void abort_result_set() override; bool can_rollback_data() override { return 1; } diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 17cd70788a2..7749bf86608 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -276,7 +276,7 @@ int update_portion_of_time(THD *thd, TABLE *table, if (likely(!res) && table->triggers) res= table->triggers->process_triggers(thd, TRG_EVENT_INSERT, - TRG_ACTION_AFTER, true); + TRG_ACTION_AFTER, true, nullptr); restore_record(table, record[1]); if (res) table->file->restore_auto_increment(prev_insert_id); @@ -872,14 +872,20 @@ bool Sql_cmd_delete::delete_from_single_table(THD *thd) delete_history); if (delete_record) { + bool trg_skip_row= false; + if (!delete_history && table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_DELETE, - TRG_ACTION_BEFORE, FALSE)) + TRG_ACTION_BEFORE, FALSE, + &trg_skip_row)) { error= 1; break; } + if (trg_skip_row) + continue; + // no LIMIT / OFFSET if (returning && result->send_data(returning->item_list) < 0) { @@ -911,7 +917,8 @@ bool Sql_cmd_delete::delete_from_single_table(THD *thd) deleted++; if (!delete_history && table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_DELETE, - TRG_ACTION_AFTER, FALSE)) + TRG_ACTION_AFTER, false, + nullptr)) { error= 1; break; @@ -1256,12 +1263,19 @@ int multi_delete::send_data(List &values) if (secure_counter < 0) { + bool trg_skip_row= false; + /* We are scanning the current table */ DBUG_ASSERT(del_table == table_being_deleted); if (table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_DELETE, - TRG_ACTION_BEFORE, FALSE)) + TRG_ACTION_BEFORE, false, + &trg_skip_row)) DBUG_RETURN(1); + + if (trg_skip_row) + continue; + table->status|= STATUS_DELETED; error= table->delete_row(); @@ -1272,7 +1286,8 @@ int multi_delete::send_data(List &values) thd->transaction->stmt.modified_non_trans_table= TRUE; if (table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_DELETE, - TRG_ACTION_AFTER, FALSE)) + TRG_ACTION_AFTER, false, + nullptr)) DBUG_RETURN(1); } 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(); while (likely(!(local_error= info.read_record())) && likely(!thd->killed)) { + bool trg_skip_row= false; + if (table->triggers && unlikely(table->triggers->process_triggers(thd, TRG_EVENT_DELETE, - TRG_ACTION_BEFORE, FALSE))) + TRG_ACTION_BEFORE, false, + &trg_skip_row))) { local_error= 1; break; } + if (trg_skip_row) + continue; + local_error= table->delete_row(); if (unlikely(local_error) && !ignore) { @@ -1460,7 +1481,8 @@ int multi_delete::do_table_deletes(TABLE *table, SORT_INFO *sort_info, deleted++; if (table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_DELETE, - TRG_ACTION_AFTER, FALSE)) + TRG_ACTION_AFTER, false, + nullptr)) { local_error= 1; break; diff --git a/sql/sql_error.cc b/sql/sql_error.cc index 89dde3cf190..122f76814ab 100644 --- a/sql/sql_error.cc +++ b/sql/sql_error.cc @@ -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); 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. */ { 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. */ { @@ -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) { if (from) - Sql_state_errno_level::assign_defaults(from); + Sql_state_errno_level::assign_defaults(thd, from); if (!get_message_text()) set_builtin_message_text(ER(get_sql_errno())); } diff --git a/sql/sql_error.h b/sql/sql_error.h index 4751444e2b6..ead860010ad 100644 --- a/sql/sql_error.h +++ b/sql/sql_error.h @@ -167,7 +167,7 @@ protected: /** Severity (error, warning, note) of this condition. */ enum_warning_level m_level; - void assign_defaults(const Sql_state_errno *value); + void assign_defaults(THD *thd, const Sql_state_errno *value); public: /** diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 74b84ddb60d..0fcc92a7d6f 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -1057,6 +1057,8 @@ bool mysql_insert(THD *thd, TABLE_LIST *table_list, while ((values= its++)) { + bool trg_skip_row= false; + thd->get_stmt_da()->inc_current_row_for_warning(); 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, *values, 0, - TRG_EVENT_INSERT))) + TRG_EVENT_INSERT, + &trg_skip_row))) { if (values_list.elements != 1 && ! thd->is_error()) { @@ -1119,7 +1122,8 @@ bool mysql_insert(THD *thd, TABLE_LIST *table_list, table-> field_to_fill(), *values, 0, - TRG_EVENT_INSERT))) + TRG_EVENT_INSERT, + &trg_skip_row))) { 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 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 == info->update_values->elements); + + bool trg_skip_row= false; + if (fill_record_n_invoke_before_triggers(thd, table, *info->update_fields, *info->update_values, info->ignore, - TRG_EVENT_UPDATE)) + TRG_EVENT_UPDATE, + &trg_skip_row)) goto before_trg_err; + if (trg_skip_row) + goto ok; + bool different_records= (!records_are_comparable(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; trg_error= (table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, - TRG_ACTION_AFTER, TRUE)); + TRG_ACTION_AFTER, true, + nullptr)); info->copied++; } @@ -2254,11 +2268,17 @@ int write_record(THD *thd, TABLE *table, COPY_INFO *info, select_result *sink) } else { + bool trg_skip_row= false; + if (table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_DELETE, - TRG_ACTION_BEFORE, TRUE)) + TRG_ACTION_BEFORE, true, + &trg_skip_row)) goto before_trg_err; + if (trg_skip_row) + continue; + if (!table->versioned(VERS_TIMESTAMP)) error= table->file->ha_delete_row(table->record[1]); 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; if (table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_DELETE, - TRG_ACTION_AFTER, TRUE)) + TRG_ACTION_AFTER, true, + nullptr)) { trg_error= 1; 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); trg_error= (table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_INSERT, - TRG_ACTION_AFTER, TRUE)); + TRG_ACTION_AFTER, true, + nullptr)); ok: /* @@ -4279,9 +4301,10 @@ int select_insert::send_data(List &values) { DBUG_ENTER("select_insert::send_data"); bool error=0; + bool trg_skip_row= false; thd->count_cuted_fields= CHECK_FIELD_WARN; // Calculate cuted fields - if (store_values(values)) + if (store_values(values, &trg_skip_row)) DBUG_RETURN(1); thd->count_cuted_fields= CHECK_FIELD_ERROR_FOR_NULL; if (unlikely(thd->is_error())) @@ -4300,10 +4323,11 @@ int select_insert::send_data(List &values) } } - error= write_record(thd, table, &info, sel_result); + if (!trg_skip_row) + error= write_record(thd, table, &info, sel_result); table->auto_increment_field_not_null= FALSE; - if (likely(!error)) + if (likely(!error) && !trg_skip_row) { if (table->triggers || info.handle_duplicates == DUP_UPDATE) { @@ -4337,7 +4361,7 @@ int select_insert::send_data(List &values) } -bool select_insert::store_values(List &values) +bool select_insert::store_values(List &values, bool *trg_skip_row) { DBUG_ENTER("select_insert::store_values"); bool error; @@ -4345,10 +4369,12 @@ bool select_insert::store_values(List &values) table->reset_default_fields(); if (fields->elements) error= fill_record_n_invoke_before_triggers(thd, table, *fields, values, - true, TRG_EVENT_INSERT); + true, TRG_EVENT_INSERT, + trg_skip_row); else 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); } @@ -5138,10 +5164,11 @@ bool binlog_drop_table(THD *thd, TABLE *table) } -bool select_create::store_values(List &values) +bool select_create::store_values(List &values, bool *trg_skip_row) { return fill_record_n_invoke_before_triggers(thd, table, field, values, - true, TRG_EVENT_INSERT); + true, TRG_EVENT_INSERT, + trg_skip_row); } diff --git a/sql/sql_load.cc b/sql/sql_load.cc index 04913182f29..06b761ba4b9 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -1013,7 +1013,7 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, List_iterator_fast it(fields_vars); Item *item; TABLE *table= table_list->table; - bool err, progress_reports; + bool err= false, progress_reports; ulonglong counter, time_to_report_progress; 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()); } + bool trg_skip_row= false; if (thd->killed || fill_record_n_invoke_before_triggers(thd, table, set_fields, set_values, ignore_check_option_errors, - TRG_EVENT_INSERT)) + TRG_EVENT_INSERT, &trg_skip_row)) DBUG_RETURN(1); switch (table_list->view_check_option(thd, ignore_check_option_errors)) { @@ -1104,7 +1105,8 @@ read_fixed_length(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, DBUG_RETURN(-1); } - err= write_record(thd, table, &info); + if (!trg_skip_row) + err= write_record(thd, table, &info); table->auto_increment_field_not_null= FALSE; if (err) DBUG_RETURN(1); @@ -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) || unlikely(fill_record_n_invoke_before_triggers(thd, table, set_fields, set_values, ignore_check_option_errors, - TRG_EVENT_INSERT))) + TRG_EVENT_INSERT, + &trg_skip_row))) DBUG_RETURN(1); + if (trg_skip_row) + { + read_info.next_line(); + continue; + } + switch (table_list->view_check_option(thd, ignore_check_option_errors)) { case VIEW_CHECK_SKIP: @@ -1361,12 +1371,19 @@ read_xml_field(THD *thd, COPY_INFO &info, TABLE_LIST *table_list, DBUG_ASSERT(!item); + bool trg_skip_row= false; if (thd->killed || fill_record_n_invoke_before_triggers(thd, table, set_fields, set_values, ignore_check_option_errors, - TRG_EVENT_INSERT)) + TRG_EVENT_INSERT, &trg_skip_row)) DBUG_RETURN(1); + if (trg_skip_row) + { + read_info.next_line(); + continue; + } + switch (table_list->view_check_option(thd, ignore_check_option_errors)) { case VIEW_CHECK_SKIP: diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 09d95bf8f06..3e14f742fbd 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -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. @@ -2578,6 +2613,8 @@ end: @param event @param time_type @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. @retval FALSE on success. @@ -2588,6 +2625,7 @@ bool Table_triggers_list::process_triggers(THD *thd, trg_event_type event, trg_action_time_type time_type, bool old_row_is_record1, + bool *skip_row_indicator, List *fields_in_update_stmt) { bool err_status; @@ -2595,6 +2633,18 @@ bool Table_triggers_list::process_triggers(THD *thd, Trigger *trigger; 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()) 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)); - thd->reset_sub_statement_state(&statement_state, SUB_STMT_TRIGGER); - + if (time_type == TRG_ACTION_AFTER) + 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 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->table_name, &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); } while (!err_status && (trigger= trigger->next)); thd->bulk_param= save_bulk_param; diff --git a/sql/sql_trigger.h b/sql/sql_trigger.h index e2ac168dd83..45abb75bbca 100644 --- a/sql/sql_trigger.h +++ b/sql/sql_trigger.h @@ -260,6 +260,7 @@ public: bool process_triggers(THD *thd, trg_event_type event, trg_action_time_type time_type, bool old_row_is_record1, + bool *skip_row_indicator, List *fields_in_update_stmt= nullptr); void empty_lists(); bool create_lists_needed_for_files(MEM_ROOT *root); diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 015869cb142..0c1decb3221 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -984,9 +984,18 @@ update_begin: cut_fields_for_portion_of_time(thd, table, table_list->period_conditions); + bool trg_skip_row= false; if (fill_record_n_invoke_before_triggers(thd, table, *fields, *values, 0, - TRG_EVENT_UPDATE)) + TRG_EVENT_UPDATE, + &trg_skip_row)) break; /* purecov: inspected */ + if (trg_skip_row) + { + updated_or_same++; + thd->get_stmt_da()->inc_current_row_for_warning(); + + continue; + } found++; @@ -1115,6 +1124,7 @@ error: if (table->triggers && unlikely(table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, TRG_ACTION_AFTER, true, + nullptr, fields))) { error= 1; @@ -2365,11 +2375,17 @@ int multi_update::send_data(List ¬_used_values) table->status|= STATUS_UPDATED; store_record(table,record[1]); + bool trg_skip_row= false; if (fill_record_n_invoke_before_triggers(thd, table, *fields_for_table[offset], *values_for_table[offset], 0, - TRG_EVENT_UPDATE)) + TRG_EVENT_UPDATE, + &trg_skip_row)) DBUG_RETURN(1); + + if (trg_skip_row) + continue; + /* Reset the table->auto_increment_field_not_null as it is valid for only one row. @@ -2442,6 +2458,7 @@ int multi_update::send_data(List ¬_used_values) if (table->triggers && unlikely(table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, TRG_ACTION_AFTER, true, + nullptr, fields_for_table[offset]))) DBUG_RETURN(1); } @@ -2700,12 +2717,18 @@ int multi_update::do_updates() if (table->vfield && table->update_virtual_fields(table->file, VCOL_UPDATE_FOR_WRITE)) goto err2; + + bool trg_skip_row= false; if (table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, TRG_ACTION_BEFORE, true, + &trg_skip_row, fields_for_table[offset])) goto err2; + if (trg_skip_row) + continue; + if (!can_compare_record || compare_record(table)) { int error; @@ -2764,6 +2787,7 @@ int multi_update::do_updates() if (table->triggers && unlikely(table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, TRG_ACTION_AFTER, true, + nullptr, fields_for_table[offset]))) goto err2; } diff --git a/sql/table.cc b/sql/table.cc index 034e11427fe..84abe704701 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -9467,16 +9467,23 @@ int TABLE::period_make_insert(Item *src, Field *dst) res= update_generated_fields(); } + bool trg_skip_row= false; if (likely(!res) && triggers) 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)) res = file->ha_write_row(record[0]); if (likely(!res) && triggers) res= triggers->process_triggers(thd, TRG_EVENT_INSERT, - TRG_ACTION_AFTER, true); + TRG_ACTION_AFTER, true, nullptr); restore_record(this, record[1]); if (res)