diff --git a/mysql-test/main/trigger.result b/mysql-test/main/trigger.result index 26b14926481..e54ee8fa99f 100644 --- a/mysql-test/main/trigger.result +++ b/mysql-test/main/trigger.result @@ -2450,3 +2450,41 @@ DROP TABLE t1; # # End of 10.3 tests # +# +# Test dropping orphan .trn file +# +create table t1 (a int); +create trigger t1_trg before insert on t1 for each row +begin +if isnull(new.a) then +set new.a:= 1000; +end if; +end| +insert into t1 values (null); +select * from t1; +a +1000 +drop table t1; +drop trigger t1_trg; +Warnings: +Error 1146 Table 'test.t1' doesn't exist +Warning 4181 Dropped orphan trigger 't1_trg', originally created for table: 't1' +create table t1 (a int); +drop trigger t1_trg; +Warnings: +Warning 4181 Dropped orphan trigger 't1_trg', originally created for table: 't1' +create trigger t1_trg_2 before insert on t1 for each row +begin +if isnull(new.a) then +set new.a:= 1000; +end if; +end| +drop trigger t1_trg; +Warnings: +Error 1360 Trigger does not exist +Warning 4181 Dropped orphan trigger 't1_trg', originally created for table: 't1' +drop trigger t1_trg_2; +drop table t1; +# +# End of 10.6 tests +# diff --git a/mysql-test/main/trigger.test b/mysql-test/main/trigger.test index 0461d5195c3..086912000d9 100644 --- a/mysql-test/main/trigger.test +++ b/mysql-test/main/trigger.test @@ -2782,3 +2782,48 @@ DROP TABLE t1; --echo # --echo # End of 10.3 tests --echo # + +--echo # +--echo # Test dropping orphan .trn file +--echo # + +let $MYSQLD_DATADIR= `select @@datadir`; + +create table t1 (a int); +delimiter |; +create trigger t1_trg before insert on t1 for each row +begin + if isnull(new.a) then + set new.a:= 1000; + end if; +end| +delimiter ;| +insert into t1 values (null); +select * from t1; + +--copy_file $MYSQLD_DATADIR/test/t1_trg.TRN $MYSQLD_DATADIR/test/t1_trg.TMP +drop table t1; +--copy_file $MYSQLD_DATADIR/test/t1_trg.TMP $MYSQLD_DATADIR/test/t1_trg.TRN +drop trigger t1_trg; +create table t1 (a int); +--copy_file $MYSQLD_DATADIR/test/t1_trg.TMP $MYSQLD_DATADIR/test/t1_trg.TRN +drop trigger t1_trg; + +# Test creating an additonal trigger for t1, but with different names +delimiter |; +create trigger t1_trg_2 before insert on t1 for each row +begin + if isnull(new.a) then + set new.a:= 1000; + end if; +end| +delimiter ;| +--copy_file $MYSQLD_DATADIR/test/t1_trg.TMP $MYSQLD_DATADIR/test/t1_trg.TRN +drop trigger t1_trg; +drop trigger t1_trg_2; +drop table t1; +--remove_file $MYSQLD_DATADIR/test/t1_trg.TMP + +--echo # +--echo # End of 10.6 tests +--echo # diff --git a/mysql-test/suite/binlog/r/binlog_trigger.result b/mysql-test/suite/binlog/r/binlog_trigger.result index 48ec1dd629e..ee0062db162 100644 --- a/mysql-test/suite/binlog/r/binlog_trigger.result +++ b/mysql-test/suite/binlog/r/binlog_trigger.result @@ -22,3 +22,17 @@ master-bin.000001 # Gtid # # GTID #-#-# master-bin.000001 # Query # # use `test`; CREATE DEFINER=`root`@`localhost` TRIGGER tr3_bi BEFORE INSERT ON t1 FOR EACH ROW precedes tr4_bi INSERT INTO t2 (a) VALUES (NEW.a + 400) master-bin.000001 # Gtid # # GTID #-#-# master-bin.000001 # Query # # use `test`; DROP TABLE `t1` /* generated by server */ +# +# MDEV-25517 Atomic DDL: Assertion `query_arg' in THD::binlog_query +# upon DROP TRIGGER +# +CREATE TABLE t1 (a INT); +CREATE TRIGGER trg AFTER INSERT ON t1 FOR EACH ROW SET @x = 1; +connect con1,localhost,root,,test; +DROP TRIGGER trg; +connection default; +DROP TRIGGER trg; +connection con1; +disconnect con1; +connection default; +DROP TABLE t1; diff --git a/mysql-test/suite/binlog/t/binlog_trigger.test b/mysql-test/suite/binlog/t/binlog_trigger.test index 4ad5d16a1f8..3f93d6e802e 100644 --- a/mysql-test/suite/binlog/t/binlog_trigger.test +++ b/mysql-test/suite/binlog/t/binlog_trigger.test @@ -21,3 +21,31 @@ DROP TABLE t1; --let $binlog_file = LAST source include/show_binlog_events.inc; + +--echo # +--echo # MDEV-25517 Atomic DDL: Assertion `query_arg' in THD::binlog_query +--echo # upon DROP TRIGGER +--echo # + +# This test case is 'random' by design. For most cases the second DROP TRIGGER +# will generate a warning "Dropped orphan trigger...", but if there is a timing +# issue, we may get another error or warning later. This is ok as it enables +# us to have more code paths tested over time. + +CREATE TABLE t1 (a INT); +CREATE TRIGGER trg AFTER INSERT ON t1 FOR EACH ROW SET @x = 1; +--disable_warnings +--connect (con1,localhost,root,,test) +--send + DROP TRIGGER trg; +--connection default +--error 0,ER_TRG_DOES_NOT_EXIST +DROP TRIGGER trg; +# Cleanup +--connection con1 +--error 0,ER_TRG_DOES_NOT_EXIST +--reap +--disconnect con1 +--connection default +--enable_warnings +DROP TABLE t1; diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 1c56f81b12c..63dc9cf024c 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -7988,3 +7988,5 @@ ER_JSON_TABLE_MULTIPLE_MATCHES eng "Can't store multiple matches of the path in the column '%s' of JSON_TABLE '%s'." ER_WITH_TIES_NEEDS_ORDER eng "FETCH ... WITH TIES requires ORDER BY clause to be present" +ER_REMOVED_ORPHAN_TRIGGER + eng "Dropped orphan trigger '%-.64s', originally created for table: '%-.192s'" diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 35d615d325a..e3149799b6e 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -39,6 +39,12 @@ /*************************************************************************/ +static bool add_table_for_trigger_internal(THD *thd, + const sp_name *trg_name, + bool if_exists, + TABLE_LIST **table, + char *trn_path_buff); + /** Trigger_creation_ctx -- creation context of triggers. */ @@ -396,6 +402,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) */ TABLE *table; bool result= TRUE; + bool add_if_exists_to_binlog= 0, action_executed= 0; String stmt_query; bool lock_upgrade_done= FALSE; bool backup_of_table_list_done= 0;; @@ -403,6 +410,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) MDL_request mdl_request_for_trn; Query_tables_list backup; DDL_LOG_STATE ddl_log_state, ddl_log_state_tmp_file; + char trn_path_buff[FN_REFLEN]; DBUG_ENTER("mysql_create_or_drop_trigger"); /* Charset of the buffer for statement must be system one. */ @@ -489,7 +497,8 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) goto end; } - if (add_table_for_trigger(thd, thd->lex->spname, if_exists, & tables)) + if (add_table_for_trigger_internal(thd, thd->lex->spname, if_exists, &tables, + trn_path_buff)) goto end; if (!tables) @@ -505,7 +514,8 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) */ result= FALSE; /* Still, we need to log the query ... */ - stmt_query.append(thd->query(), thd->query_length()); + stmt_query.set(thd->query(), thd->query_length(), system_charset_info); + action_executed= 1; goto end; } } @@ -562,7 +572,15 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) tables->table= open_n_lock_single_table(thd, tables, TL_READ_NO_INSERT, 0); if (! tables->table) + { + if (!create && thd->get_stmt_da()->sql_errno() == ER_NO_SUCH_TABLE) + { + /* TRN file exists but table does not. Drop the orphan trigger */ + thd->clear_error(); // Remove error from open + goto drop_orphan_trn; + } goto end; + } tables->table->use_all_columns(); } table= tables->table; @@ -588,11 +606,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) if (!table->triggers) { if (!create) - { - my_error(ER_TRG_DOES_NOT_EXIST, MYF(0)); - goto end; - } - + goto drop_orphan_trn; if (!(table->triggers= new (&table->mem_root) Table_triggers_list(table))) goto end; } @@ -618,7 +632,13 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) &thd->lex->spname->m_name, &stmt_query, &ddl_log_state); + if (result) + { + thd->clear_error(); // Remove error from drop trigger + goto drop_orphan_trn; + } } + action_executed= 1; close_all_tables_for_name(thd, table->s, HA_EXTRA_NOT_USED, NULL); @@ -637,13 +657,19 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) sp_cache_invalidate(); end: - if (!result) + if (!result && action_executed) { + ulonglong save_option_bits= thd->variables.option_bits; + debug_crash_here("ddl_log_drop_before_binlog"); + if (add_if_exists_to_binlog) + thd->variables.option_bits|= OPTION_IF_EXISTS; thd->binlog_xid= thd->query_id; ddl_log_update_xid(&ddl_log_state, thd->binlog_xid); - result= write_bin_log(thd, TRUE, stmt_query.ptr(), stmt_query.length()); + result= write_bin_log(thd, TRUE, stmt_query.ptr(), + stmt_query.length()); thd->binlog_xid= 0; + thd->variables.option_bits= save_option_bits; debug_crash_here("ddl_log_drop_after_binlog"); } ddl_log_complete(&ddl_log_state); @@ -679,6 +705,16 @@ end: wsrep_error_label: DBUG_RETURN(true); #endif + +drop_orphan_trn: + my_error(ER_REMOVED_ORPHAN_TRIGGER, MYF(ME_WARNING), + thd->lex->spname->m_name.str, tables->table_name.str); + mysql_file_delete(key_file_trg, trn_path_buff, MYF(0)); + result= thd->is_error(); + add_if_exists_to_binlog= 1; + action_executed= 1; // Ensure query is binlogged + stmt_query.set(thd->query(), thd->query_length(), system_charset_info); + goto end; } @@ -1862,17 +1898,16 @@ void Trigger::get_trigger_info(LEX_CSTRING *trigger_stmt, @retval TRUE Otherwise. */ -bool add_table_for_trigger(THD *thd, - const sp_name *trg_name, - bool if_exists, - TABLE_LIST **table) +static bool add_table_for_trigger_internal(THD *thd, + const sp_name *trg_name, + bool if_exists, + TABLE_LIST **table, + char *trn_path_buff) { LEX *lex= thd->lex; - char trn_path_buff[FN_REFLEN]; LEX_CSTRING trn_path= { trn_path_buff, 0 }; LEX_CSTRING tbl_name= null_clex_str; - - DBUG_ENTER("add_table_for_trigger"); + DBUG_ENTER("add_table_for_trigger_internal"); build_trn_path(thd, trg_name, (LEX_STRING*) &trn_path); @@ -1905,6 +1940,23 @@ bool add_table_for_trigger(THD *thd, } +/* + Same as above, but with an allocated buffer. + This is called by mysql_excute_command() in is here to keep stack + space down in the caller. +*/ + +bool add_table_for_trigger(THD *thd, + const sp_name *trg_name, + bool if_exists, + TABLE_LIST **table) +{ + char trn_path_buff[FN_REFLEN]; + return add_table_for_trigger_internal(thd, trg_name, if_exists, + table, trn_path_buff); +} + + /** Drop all triggers for table.