From 879cf292f2f365f7014b9b7d6e9bd445bda72b02 Mon Sep 17 00:00:00 2001 From: Mohamed Date: Thu, 6 Aug 2026 05:43:52 +0300 Subject: [PATCH] =?UTF-8?q?MDEV-40570=20INSERT=20=E2=80=A6=20ON=20DUPLICAT?= =?UTF-8?q?E=20KEY=20UPDATE=20reports=20the=20wrong=20INSERTING/UPDATING/D?= =?UTF-8?q?ELETING=20flag=20in=20multi-event=20triggers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The clauses INSERTING / UPDATING / DELETING available inside multi-event triggers (MDEV-10164) are evaluated by comparing the trigger's statement type with the type of DML statement on top of the THD's active-statement stack (see Item_trigger_type_of_statement::val_bool()). mysql_insert() pushes INSERTING_STMT for the whole statement via a Running_stmt_guard. However, when INSERT ... ON DUPLICATE KEY UPDATE hits a duplicate key, Write_record::insert_on_duplicate_update() fires UPDATE triggers without changing the active-statement stack, so the UPDATING clause evaluated to FALSE and INSERTING to TRUE inside those triggers. Similarly, when REPLACE deletes a conflicting row, Write_record::replace_row() fires DELETE triggers while INSERTING_STMT is still on top of the stack, so the DELETING clause evaluated to FALSE. Additionally, INSERT ... SELECT and REPLACE ... SELECT never pushed any statement type at all, so all of the clauses INSERTING / UPDATING / DELETING evaluated to FALSE for triggers fired by these statements. Fix: - push UPDATING_STMT in Write_record::insert_on_duplicate_update() around the invocation of BEFORE/AFTER UPDATE triggers; - push DELETING_STMT in Write_record::replace_row() around the invocation of BEFORE/AFTER DELETE triggers; - push INSERTING_STMT for the whole INSERT ... SELECT and REPLACE ... SELECT statements in mysql_execute_command(), the same way mysql_insert() does. The pushed value is popped automatically by the Running_stmt_guard destructor at the end of each scope. --- mysql-test/main/trigger.result | 113 +++++++++++++++++++++++++++++++++ mysql-test/main/trigger.test | 107 +++++++++++++++++++++++++++++++ sql/sql_insert.cc | 12 ++++ sql/sql_parse.cc | 1 + 4 files changed, 233 insertions(+) diff --git a/mysql-test/main/trigger.result b/mysql-test/main/trigger.result index 08c1abd378d32..b708bf10f793e 100644 --- a/mysql-test/main/trigger.result +++ b/mysql-test/main/trigger.result @@ -3065,3 +3065,116 @@ TRIGGER_CATALOG TRIGGER_SCHEMA TRIGGER_NAME EVENT_OBJECT_CATALOG EVENT_OBJECT_SC DROP VIEW IF EXISTS vt; DROP TABLE t, t1; # End of 12.2 tests +# +# MDEV-40570: INSERT ... ON DUPLICATE KEY UPDATE reports the wrong +# INSERTING/UPDATING/DELETING flag in multi-event triggers +# +CREATE TABLE t1 (a INT PRIMARY KEY, b INT); +CREATE TABLE t2 (msg VARCHAR(20)); +CREATE TRIGGER t1_b_all BEFORE INSERT OR UPDATE OR DELETE ON t1 FOR EACH ROW +BEGIN +IF INSERTING THEN +INSERT INTO t2 VALUES ('BEFORE INSERTING'); +ELSEIF UPDATING THEN +INSERT INTO t2 VALUES ('BEFORE UPDATING'); +ELSEIF DELETING THEN +INSERT INTO t2 VALUES ('BEFORE DELETING'); +END IF; +END +$ +CREATE TRIGGER t1_a_all AFTER INSERT OR UPDATE OR DELETE ON t1 FOR EACH ROW +BEGIN +IF INSERTING THEN +INSERT INTO t2 VALUES ('AFTER INSERTING'); +ELSEIF UPDATING THEN +INSERT INTO t2 VALUES ('AFTER UPDATING'); +ELSEIF DELETING THEN +INSERT INTO t2 VALUES ('AFTER DELETING'); +END IF; +END +$ +# Ordinary INSERT fires INSERT triggers with the INSERTING flag set +INSERT INTO t1 VALUES (1, 1); +SELECT * FROM t2; +msg +BEFORE INSERTING +AFTER INSERTING +TRUNCATE TABLE t2; +# INSERT ... ON DUPLICATE KEY UPDATE that hits a duplicate key first +# fires INSERT triggers (INSERTING), then performs an update and so +# must fire UPDATE triggers with the UPDATING flag set +INSERT INTO t1 VALUES (1, 2) ON DUPLICATE KEY UPDATE b = 2; +SELECT * FROM t2; +msg +BEFORE INSERTING +BEFORE UPDATING +AFTER UPDATING +TRUNCATE TABLE t2; +# ON DUPLICATE KEY UPDATE that doesn't change the row still fires +# UPDATE triggers with the UPDATING flag set +INSERT INTO t1 VALUES (1, 2) ON DUPLICATE KEY UPDATE b = 2; +SELECT * FROM t2; +msg +BEFORE INSERTING +BEFORE UPDATING +AFTER UPDATING +TRUNCATE TABLE t2; +# REPLACE on a duplicate key fires INSERT triggers (INSERTING) and +# DELETE triggers with the DELETING flag set +REPLACE INTO t1 VALUES (1, 3); +SELECT * FROM t2; +msg +BEFORE INSERTING +BEFORE DELETING +AFTER DELETING +AFTER INSERTING +TRUNCATE TABLE t2; +DROP TABLE t1, t2; +# +# Check the same for INSERT ... SELECT ... ON DUPLICATE KEY UPDATE +# and REPLACE ... SELECT +# +CREATE TABLE t1 (a INT PRIMARY KEY, b INT); +CREATE TABLE t2 (msg VARCHAR(20)); +CREATE TABLE t3 (a INT, b INT); +INSERT INTO t3 VALUES (1, 2); +CREATE TRIGGER t1_b_all BEFORE INSERT OR UPDATE OR DELETE ON t1 FOR EACH ROW +BEGIN +IF INSERTING THEN +INSERT INTO t2 VALUES ('BEFORE INSERTING'); +ELSEIF UPDATING THEN +INSERT INTO t2 VALUES ('BEFORE UPDATING'); +ELSEIF DELETING THEN +INSERT INTO t2 VALUES ('BEFORE DELETING'); +END IF; +END +$ +CREATE TRIGGER t1_a_all AFTER INSERT OR UPDATE OR DELETE ON t1 FOR EACH ROW +BEGIN +IF INSERTING THEN +INSERT INTO t2 VALUES ('AFTER INSERTING'); +ELSEIF UPDATING THEN +INSERT INTO t2 VALUES ('AFTER UPDATING'); +ELSEIF DELETING THEN +INSERT INTO t2 VALUES ('AFTER DELETING'); +END IF; +END +$ +INSERT INTO t1 VALUES (1, 1); +TRUNCATE TABLE t2; +INSERT INTO t1 SELECT * FROM t3 ON DUPLICATE KEY UPDATE b = 2; +SELECT * FROM t2; +msg +BEFORE INSERTING +BEFORE UPDATING +AFTER UPDATING +TRUNCATE TABLE t2; +REPLACE INTO t1 SELECT a, b + 1 FROM t3; +SELECT * FROM t2; +msg +BEFORE INSERTING +BEFORE DELETING +AFTER DELETING +AFTER INSERTING +DROP TABLE t1, t2, t3; +# End of 12.3 tests diff --git a/mysql-test/main/trigger.test b/mysql-test/main/trigger.test index 16a99c905c68f..44b78e88a371b 100644 --- a/mysql-test/main/trigger.test +++ b/mysql-test/main/trigger.test @@ -3404,3 +3404,110 @@ DROP VIEW IF EXISTS vt; DROP TABLE t, t1; --echo # End of 12.2 tests + +--echo # +--echo # MDEV-40570: INSERT ... ON DUPLICATE KEY UPDATE reports the wrong +--echo # INSERTING/UPDATING/DELETING flag in multi-event triggers +--echo # + +CREATE TABLE t1 (a INT PRIMARY KEY, b INT); +CREATE TABLE t2 (msg VARCHAR(20)); + +--delimiter $ +CREATE TRIGGER t1_b_all BEFORE INSERT OR UPDATE OR DELETE ON t1 FOR EACH ROW +BEGIN + IF INSERTING THEN + INSERT INTO t2 VALUES ('BEFORE INSERTING'); + ELSEIF UPDATING THEN + INSERT INTO t2 VALUES ('BEFORE UPDATING'); + ELSEIF DELETING THEN + INSERT INTO t2 VALUES ('BEFORE DELETING'); + END IF; +END +$ +CREATE TRIGGER t1_a_all AFTER INSERT OR UPDATE OR DELETE ON t1 FOR EACH ROW +BEGIN + IF INSERTING THEN + INSERT INTO t2 VALUES ('AFTER INSERTING'); + ELSEIF UPDATING THEN + INSERT INTO t2 VALUES ('AFTER UPDATING'); + ELSEIF DELETING THEN + INSERT INTO t2 VALUES ('AFTER DELETING'); + END IF; +END +$ +--delimiter ; + +--echo # Ordinary INSERT fires INSERT triggers with the INSERTING flag set +INSERT INTO t1 VALUES (1, 1); +SELECT * FROM t2; +TRUNCATE TABLE t2; + +--echo # INSERT ... ON DUPLICATE KEY UPDATE that hits a duplicate key first +--echo # fires INSERT triggers (INSERTING), then performs an update and so +--echo # must fire UPDATE triggers with the UPDATING flag set +INSERT INTO t1 VALUES (1, 2) ON DUPLICATE KEY UPDATE b = 2; +SELECT * FROM t2; +TRUNCATE TABLE t2; + +--echo # ON DUPLICATE KEY UPDATE that doesn't change the row still fires +--echo # UPDATE triggers with the UPDATING flag set +INSERT INTO t1 VALUES (1, 2) ON DUPLICATE KEY UPDATE b = 2; +SELECT * FROM t2; +TRUNCATE TABLE t2; + +--echo # REPLACE on a duplicate key fires INSERT triggers (INSERTING) and +--echo # DELETE triggers with the DELETING flag set +REPLACE INTO t1 VALUES (1, 3); +SELECT * FROM t2; +TRUNCATE TABLE t2; + +DROP TABLE t1, t2; + +--echo # +--echo # Check the same for INSERT ... SELECT ... ON DUPLICATE KEY UPDATE +--echo # and REPLACE ... SELECT +--echo # +CREATE TABLE t1 (a INT PRIMARY KEY, b INT); +CREATE TABLE t2 (msg VARCHAR(20)); +CREATE TABLE t3 (a INT, b INT); +INSERT INTO t3 VALUES (1, 2); + +--delimiter $ +CREATE TRIGGER t1_b_all BEFORE INSERT OR UPDATE OR DELETE ON t1 FOR EACH ROW +BEGIN + IF INSERTING THEN + INSERT INTO t2 VALUES ('BEFORE INSERTING'); + ELSEIF UPDATING THEN + INSERT INTO t2 VALUES ('BEFORE UPDATING'); + ELSEIF DELETING THEN + INSERT INTO t2 VALUES ('BEFORE DELETING'); + END IF; +END +$ +CREATE TRIGGER t1_a_all AFTER INSERT OR UPDATE OR DELETE ON t1 FOR EACH ROW +BEGIN + IF INSERTING THEN + INSERT INTO t2 VALUES ('AFTER INSERTING'); + ELSEIF UPDATING THEN + INSERT INTO t2 VALUES ('AFTER UPDATING'); + ELSEIF DELETING THEN + INSERT INTO t2 VALUES ('AFTER DELETING'); + END IF; +END +$ +--delimiter ; + +INSERT INTO t1 VALUES (1, 1); +TRUNCATE TABLE t2; + +INSERT INTO t1 SELECT * FROM t3 ON DUPLICATE KEY UPDATE b = 2; +SELECT * FROM t2; +TRUNCATE TABLE t2; + +REPLACE INTO t1 SELECT a, b + 1 FROM t3; +SELECT * FROM t2; + +DROP TABLE t1, t2, t3; + +--echo # End of 12.3 tests diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 60668384b3e2c..1ed567a088079 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -2194,6 +2194,12 @@ int Write_record::replace_row(ha_rows *inserted, ha_rows *deleted) DBUG_PRINT("info", ("Deleting offending row and trying to write" " new one again")); + /* + Make the DELETE triggers fired below see the DELETING clause + set to true + */ + Running_stmt_guard stmt_guard(thd, active_dml_stmt::DELETING_STMT); + auto *trg = table->triggers; bool trg_skip_row= false; if (use_triggers && trg->process_triggers(table->in_use, TRG_EVENT_DELETE, @@ -2262,6 +2268,12 @@ int Write_record::insert_on_duplicate_update(ha_rows *inserted, DBUG_ASSERT(info->update_fields->elements == info->update_values->elements); + /* + Make the UPDATE triggers fired below see the UPDATING clause + set to true + */ + Running_stmt_guard stmt_guard(thd, active_dml_stmt::UPDATING_STMT); + bool trg_skip_row= false; if (fill_record_n_invoke_before_triggers(thd, table, *info->update_fields, diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 496426cf0447c..16a58871bb8f3 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -4545,6 +4545,7 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt) case SQLCOM_INSERT_SELECT: { WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_INSERT_REPLACE); + Running_stmt_guard guard(thd, active_dml_stmt::INSERTING_STMT); select_insert *sel_result; select_result *result= NULL; bool explain= MY_TEST(lex->describe);