1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-30 16:24:05 +03:00

Check if we can rename triggers before doing an ALTER TABLE ... RENAME

ALTER TABLE .. RENAME, when used with the inplace algorithm, does:
- Do an inplace or online alter to the new definition
- Rename to new name
- Update triggers.

If update triggers would fail, we would rename the table back.
The problem with this approach is that the table would have the new
definition but the rename would fail.  The binary log would also not be
updated.

The solution to this is to very early check if we can rename triggers
and give an error if this would fail.
Both ALTER TABLE ... RENAME and RENAME TABLE is fixed.

This was implemented by moving the pre-check of rename table in triggers
from Table_triggers_list::change_table_name() to
Table_triggers_list::prepare_for_rename().
This commit is contained in:
Monty
2021-03-22 16:05:08 +02:00
committed by Sergei Golubchik
parent c844a76b0a
commit 3c578b0a96
5 changed files with 178 additions and 65 deletions

View File

@ -45,6 +45,18 @@ static bool add_table_for_trigger_internal(THD *thd,
TABLE_LIST **table,
char *trn_path_buff);
/*
Functions for TRIGGER_RENAME_PARAM
*/
void TRIGGER_RENAME_PARAM::reset()
{
delete table.triggers;
table.triggers= 0;
free_root(&table.mem_root, MYF(0));
}
/**
Trigger_creation_ctx -- creation context of triggers.
*/
@ -2206,6 +2218,80 @@ bool Trigger::change_on_table_name(void* param_arg)
}
/*
Check if we can rename triggers in change_table_name()
The idea is to ensure that it is close to impossible that
change_table_name() should fail.
@return 0 ok
@return 1 Error: rename of triggers would fail
*/
bool
Table_triggers_list::prepare_for_rename(THD *thd,
TRIGGER_RENAME_PARAM *param,
const LEX_CSTRING *db,
const LEX_CSTRING *old_alias,
const LEX_CSTRING *old_table,
const LEX_CSTRING *new_db,
const LEX_CSTRING *new_table)
{
TABLE *table= &param->table;
bool result= 0;
DBUG_ENTER("Table_triggers_lists::prepare_change_table_name");
init_sql_alloc(key_memory_Table_trigger_dispatcher,
&table->mem_root, 8192, 0, MYF(0));
DBUG_ASSERT(my_strcasecmp(table_alias_charset, db->str, new_db->str) ||
my_strcasecmp(table_alias_charset, old_alias->str,
new_table->str));
if (Table_triggers_list::check_n_load(thd, db, old_table, table, TRUE))
{
result= 1;
goto end;
}
if (table->triggers)
{
if (table->triggers->check_for_broken_triggers())
{
result= 1;
goto end;
}
/*
Since triggers should be in the same schema as their subject tables
moving table with them between two schemas raises too many questions.
(E.g. what should happen if in new schema we already have trigger
with same name ?).
In case of "ALTER DATABASE `#mysql50#db1` UPGRADE DATA DIRECTORY NAME"
we will be given table name with "#mysql50#" prefix
To remove this prefix we use check_n_cut_mysql50_prefix().
*/
if (my_strcasecmp(table_alias_charset, db->str, new_db->str))
{
char dbname[SAFE_NAME_LEN + 1];
if (check_n_cut_mysql50_prefix(db->str, dbname, sizeof(dbname)) &&
!my_strcasecmp(table_alias_charset, dbname, new_db->str))
{
param->upgrading50to51= TRUE;
}
else
{
my_error(ER_TRG_IN_WRONG_SCHEMA, MYF(0));
result= 1;
goto end;
}
}
}
end:
param->got_error= result;
DBUG_RETURN(result);
}
/**
Update .TRG and .TRN files after renaming triggers' subject table.
@ -2229,22 +2315,21 @@ bool Trigger::change_on_table_name(void* param_arg)
@retval TRUE Error
*/
bool Table_triggers_list::change_table_name(THD *thd, const LEX_CSTRING *db,
bool Table_triggers_list::change_table_name(THD *thd,
TRIGGER_RENAME_PARAM *param,
const LEX_CSTRING *db,
const LEX_CSTRING *old_alias,
const LEX_CSTRING *old_table,
const LEX_CSTRING *new_db,
const LEX_CSTRING *new_table)
{
TABLE table;
TABLE *table= &param->table;
bool result= 0;
bool upgrading50to51= FALSE;
Trigger *err_trigger;
DBUG_ENTER("Triggers::change_table_name");
table.reset();
init_sql_alloc(key_memory_Table_trigger_dispatcher,
&table.mem_root, 8192, 0, MYF(0));
DBUG_ENTER("Table_triggers_list::change_table_name");
DBUG_ASSERT(!param->got_error);
/*
This method interfaces the mysql server code protected by
an exclusive metadata lock.
@ -2253,54 +2338,16 @@ bool Table_triggers_list::change_table_name(THD *thd, const LEX_CSTRING *db,
old_table->str,
MDL_EXCLUSIVE));
DBUG_ASSERT(my_strcasecmp(table_alias_charset, db->str, new_db->str) ||
my_strcasecmp(table_alias_charset, old_alias->str, new_table->str));
if (Table_triggers_list::check_n_load(thd, db, old_table, &table, TRUE))
if (table->triggers)
{
result= 1;
goto end;
}
if (table.triggers)
{
if (table.triggers->check_for_broken_triggers())
{
result= 1;
goto end;
}
/*
Since triggers should be in the same schema as their subject tables
moving table with them between two schemas raises too many questions.
(E.g. what should happen if in new schema we already have trigger
with same name ?).
In case of "ALTER DATABASE `#mysql50#db1` UPGRADE DATA DIRECTORY NAME"
we will be given table name with "#mysql50#" prefix
To remove this prefix we use check_n_cut_mysql50_prefix().
*/
if (my_strcasecmp(table_alias_charset, db->str, new_db->str))
{
char dbname[SAFE_NAME_LEN + 1];
if (check_n_cut_mysql50_prefix(db->str, dbname, sizeof(dbname)) &&
!my_strcasecmp(table_alias_charset, dbname, new_db->str))
{
upgrading50to51= TRUE;
}
else
{
my_error(ER_TRG_IN_WRONG_SCHEMA, MYF(0));
result= 1;
goto end;
}
}
if (unlikely(table.triggers->change_table_name_in_triggers(thd, db, new_db,
if (unlikely(table->triggers->change_table_name_in_triggers(thd, db, new_db,
old_alias,
new_table)))
{
result= 1;
goto end;
}
if ((err_trigger= table.triggers->
if ((err_trigger= table->triggers->
change_table_name_in_trignames( upgrading50to51 ? db : NULL,
new_db, new_table, 0)))
{
@ -2310,10 +2357,10 @@ bool Table_triggers_list::change_table_name(THD *thd, const LEX_CSTRING *db,
We assume that we will be able to undo our changes without errors
(we can't do much if there will be an error anyway).
*/
(void) table.triggers->change_table_name_in_trignames(
(void) table->triggers->change_table_name_in_trignames(
upgrading50to51 ? new_db : NULL, db,
old_alias, err_trigger);
(void) table.triggers->change_table_name_in_triggers(
(void) table->triggers->change_table_name_in_triggers(
thd, db, new_db,
new_table, old_alias);
result= 1;
@ -2322,8 +2369,6 @@ bool Table_triggers_list::change_table_name(THD *thd, const LEX_CSTRING *db,
}
end:
delete table.triggers;
free_root(&table.mem_root, MYF(0));
DBUG_RETURN(result);
}