Commit c844a76b authored by Monty's avatar Monty Committed by Sergei Golubchik

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).
parent ffe7f19f
......@@ -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 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create)
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.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment