mirror of
https://github.com/MariaDB/server.git
synced 2025-07-29 05:21:33 +03:00
Ensure that one can drop a trigger with an orphan .TRN file
Before this fix, one would get a 'Trigger ... already exists' when trying to create a trigger matching the original name and 'Trigger ... does not exists" when trying to drop it. Fixes a reported bug in MDEV-25180 Atomic ALTER TABLE MDEV-25517 Atomic DDL: Assertion `query_arg' in THD::binlog_query upon DROP TRIGGER The bug was that the stmt_query variable was not populated with the query in case of DROP TRIGGER of an orphan trigger (.TRN file exists & table exists, but the trigger was not in table->triggers).
This commit is contained in:
@ -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
|
||||
#
|
||||
|
@ -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 #
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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'"
|
||||
|
@ -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,
|
||||
static bool add_table_for_trigger_internal(THD *thd,
|
||||
const sp_name *trg_name,
|
||||
bool if_exists,
|
||||
TABLE_LIST **table)
|
||||
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.
|
||||
|
||||
|
Reference in New Issue
Block a user