Loading mysql-test/r/trigger.result +102 −1 Original line number Diff line number Diff line drop table if exists t1, t2, t3; drop table if exists t1, t2, t3, t4; drop view if exists v1; drop database if exists mysqltest; drop function if exists f1; Loading Loading @@ -785,6 +785,107 @@ create trigger test.t1_bi before insert on t1 for each row set @a:=0; ERROR 3D000: No database selected drop trigger t1_bi; ERROR 3D000: No database selected create table t1 (id int); create trigger t1_bi before insert on t1 for each row set @a:=new.id; insert into t1 values (101); select @a; @a 101 select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; trigger_schema trigger_name event_object_schema event_object_table action_statement test t1_bi test t1 set @a:=new.id rename table t1 to t2; insert into t2 values (102); select @a; @a 102 select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; trigger_schema trigger_name event_object_schema event_object_table action_statement test t1_bi test t2 set @a:=new.id alter table t2 rename to t3; insert into t3 values (103); select @a; @a 103 select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; trigger_schema trigger_name event_object_schema event_object_table action_statement test t1_bi test t3 set @a:=new.id alter table t3 rename to t4, add column val int default 0; insert into t4 values (104, 1); select @a; @a 104 select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; trigger_schema trigger_name event_object_schema event_object_table action_statement test t1_bi test t4 set @a:=new.id drop trigger t1_bi; drop table t4; create database mysqltest; use mysqltest; create table t1 (id int); create trigger t1_bi before insert on t1 for each row set @a:=new.id; insert into t1 values (101); select @a; @a 101 select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test' or event_object_schema = 'mysqltest'; trigger_schema trigger_name event_object_schema event_object_table action_statement mysqltest t1_bi mysqltest t1 set @a:=new.id rename table t1 to test.t2; ERROR HY000: Trigger in wrong schema insert into t1 values (102); select @a; @a 102 select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test' or event_object_schema = 'mysqltest'; trigger_schema trigger_name event_object_schema event_object_table action_statement mysqltest t1_bi mysqltest t1 set @a:=new.id drop trigger test.t1_bi; ERROR HY000: Trigger does not exist drop trigger t1_bi; drop table t1; drop database mysqltest; use test; create table t1 (id int); create trigger t1_bi before insert on t1 for each row set @a:=new.id; create trigger t1_ai after insert on t1 for each row set @b:=new.id; insert into t1 values (101); select @a, @b; @a @b 101 101 select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; trigger_schema trigger_name event_object_schema event_object_table action_statement test t1_bi test t1 set @a:=new.id test t1_ai test t1 set @b:=new.id rename table t1 to t2; ERROR HY000: Can't create/write to file './test/t1_ai.TRN~' (Errcode: 13) insert into t1 values (102); select @a, @b; @a @b 102 102 select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; trigger_schema trigger_name event_object_schema event_object_table action_statement test t1_bi test t1 set @a:=new.id test t1_ai test t1 set @b:=new.id drop trigger t1_bi; drop trigger t1_ai; drop table t1; create table t1 (i int); create trigger t1_bi before insert on t1 for each row return 0; ERROR 42000: RETURN is only allowed in a FUNCTION Loading mysql-test/t/trigger.test +86 −1 Original line number Diff line number Diff line Loading @@ -3,7 +3,7 @@ # --disable_warnings drop table if exists t1, t2, t3; drop table if exists t1, t2, t3, t4; drop view if exists v1; drop database if exists mysqltest; drop function if exists f1; Loading Loading @@ -959,6 +959,91 @@ create trigger test.t1_bi before insert on t1 for each row set @a:=0; drop trigger t1_bi; connection default; # # Test for bug #13525 "Rename table does not keep info of triggers" # create table t1 (id int); create trigger t1_bi before insert on t1 for each row set @a:=new.id; insert into t1 values (101); select @a; select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; rename table t1 to t2; # Trigger should work after rename insert into t2 values (102); select @a; select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; # Let us check that the same works for simple ALTER TABLE ... RENAME alter table t2 rename to t3; insert into t3 values (103); select @a; select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; # And for more complex ALTER TABLE alter table t3 rename to t4, add column val int default 0; insert into t4 values (104, 1); select @a; select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; # .TRN file should be updated with new table name drop trigger t1_bi; drop table t4; # Rename between different databases if triggers exist should fail create database mysqltest; use mysqltest; create table t1 (id int); create trigger t1_bi before insert on t1 for each row set @a:=new.id; insert into t1 values (101); select @a; select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test' or event_object_schema = 'mysqltest'; --error ER_TRG_IN_WRONG_SCHEMA rename table t1 to test.t2; insert into t1 values (102); select @a; select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test' or event_object_schema = 'mysqltest'; # There should be no fantom .TRN files --error ER_TRG_DOES_NOT_EXIST drop trigger test.t1_bi; drop trigger t1_bi; drop table t1; drop database mysqltest; use test; # And now let us check that the properly handle rename if there is some # error during it (that we rollback such renames completely). create table t1 (id int); create trigger t1_bi before insert on t1 for each row set @a:=new.id; create trigger t1_ai after insert on t1 for each row set @b:=new.id; insert into t1 values (101); select @a, @b; select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; # Trick which makes update of second .TRN file impossible system echo dummy >var/master-data/test/t1_ai.TRN~; system chmod 000 var/master-data/test/t1_ai.TRN~; --error 1 rename table t1 to t2; # 't1' should be still there and triggers should work correctly insert into t1 values (102); select @a, @b; select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; system chmod 600 var/master-data/test/t1_ai.TRN; # Let us check that updates to .TRN files were rolled back too drop trigger t1_bi; drop trigger t1_ai; drop table t1; # Test for bug #16829 "Firing trigger with RETURN crashes the server" # RETURN is not supposed to be used anywhere except functions, so error # should be returned when one attempts to create trigger with RETURN. Loading sql/sql_rename.cc +21 −2 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ */ #include "mysql_priv.h" #include "sql_trigger.h" static TABLE_LIST *rename_tables(THD *thd, TABLE_LIST *table_list, Loading Loading @@ -176,8 +177,26 @@ rename_tables(THD *thd, TABLE_LIST *table_list, bool skip_error) if (table_type == DB_TYPE_UNKNOWN) my_error(ER_FILE_NOT_FOUND, MYF(0), name, my_errno); else rc= mysql_rename_table(table_type, ren_table->db, old_alias, new_table->db, new_alias); { if (!(rc= mysql_rename_table(table_type, ren_table->db, old_alias, new_table->db, new_alias))) { if ((rc= Table_triggers_list::change_table_name(thd, ren_table->db, old_alias, new_table->db, new_alias))) { /* We've succeeded in renaming table's .frm and in updating corresponding handler data, but have failed to update table's triggers appropriately. So let us revert operations on .frm and handler's data and report about failure to rename table. */ (void) mysql_rename_table(table_type, new_table->db, new_alias, ren_table->db, old_alias); } } } break; } case FRMTYPE_VIEW: Loading sql/sql_table.cc +12 −1 Original line number Diff line number Diff line Loading @@ -3293,6 +3293,13 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, close_cached_table(thd, table); if (mysql_rename_table(old_db_type,db,table_name,new_db,new_alias)) error= -1; else if (Table_triggers_list::change_table_name(thd, db, table_name, new_db, new_alias)) { VOID(mysql_rename_table(old_db_type, new_db, new_alias, db, table_name)); error= -1; } } VOID(pthread_mutex_unlock(&LOCK_open)); } Loading Loading @@ -3841,7 +3848,11 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, VOID(quick_rm_table(new_db_type,new_db,tmp_name)); } else if (mysql_rename_table(new_db_type,new_db,tmp_name,new_db, new_alias)) new_alias) || (new_name != table_name || new_db != db) && // we also do rename Table_triggers_list::change_table_name(thd, db, table_name, new_db, new_alias)) { // Try to get everything back error=1; VOID(quick_rm_table(new_db_type,new_db,new_alias)); Loading sql/sql_trigger.cc +269 −21 Original line number Diff line number Diff line Loading @@ -65,7 +65,6 @@ File_option sql_modes_parameters= */ static const int TRG_NUM_REQUIRED_PARAMETERS= 4; static const int TRG_MAX_VERSIONS= 3; /* Structure representing contents of .TRN file which are used to support Loading Loading @@ -467,8 +466,7 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, definer_host->str, NullS) - trg_definer->str; if (!sql_create_definition_file(&dir, &file, &triggers_file_type, (gptr)this, triggers_file_parameters, TRG_MAX_VERSIONS)) (gptr)this, triggers_file_parameters, 0)) return 0; err_with_cleanup: Loading @@ -492,7 +490,8 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, True - error */ static bool rm_trigger_file(char *path, char *db, char *table_name) static bool rm_trigger_file(char *path, const char *db, const char *table_name) { strxnmov(path, FN_REFLEN, mysql_data_home, "/", db, "/", table_name, triggers_file_ext, NullS); Loading @@ -516,7 +515,8 @@ static bool rm_trigger_file(char *path, char *db, char *table_name) True - error */ static bool rm_trigname_file(char *path, char *db, char *trigger_name) static bool rm_trigname_file(char *path, const char *db, const char *trigger_name) { strxnmov(path, FN_REFLEN, mysql_data_home, "/", db, "/", trigger_name, trigname_file_ext, NullS); Loading @@ -525,6 +525,38 @@ static bool rm_trigname_file(char *path, char *db, char *trigger_name) } /* Helper function that saves .TRG file for Table_triggers_list object. SYNOPSIS save_trigger_file() triggers Table_triggers_list object for which file should be saved db Name of database for subject table table_name Name of subject table RETURN VALUE FALSE Success TRUE Error */ static bool save_trigger_file(Table_triggers_list *triggers, const char *db, const char *table_name) { char dir_buff[FN_REFLEN], file_buff[FN_REFLEN]; LEX_STRING dir, file; strxnmov(dir_buff, FN_REFLEN, mysql_data_home, "/", db, "/", NullS); dir.length= unpack_filename(dir_buff, dir_buff); dir.str= dir_buff; file.length= strxnmov(file_buff, FN_REFLEN, table_name, triggers_file_ext, NullS) - file_buff; file.str= file_buff; return sql_create_definition_file(&dir, &file, &triggers_file_type, (gptr)triggers, triggers_file_parameters, 0); } /* Drop trigger for table. Loading Loading @@ -578,20 +610,7 @@ bool Table_triggers_list::drop_trigger(THD *thd, TABLE_LIST *tables) } else { char dir_buff[FN_REFLEN], file_buff[FN_REFLEN]; LEX_STRING dir, file; strxnmov(dir_buff, FN_REFLEN, mysql_data_home, "/", tables->db, "/", NullS); dir.length= unpack_filename(dir_buff, dir_buff); dir.str= dir_buff; file.length= strxnmov(file_buff, FN_REFLEN, tables->table_name, triggers_file_ext, NullS) - file_buff; file.str= file_buff; if (sql_create_definition_file(&dir, &file, &triggers_file_type, (gptr)this, triggers_file_parameters, TRG_MAX_VERSIONS)) if (save_trigger_file(this, tables->db, tables->table_name)) return 1; } Loading Loading @@ -831,12 +850,12 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db, if (!names_only && triggers->prepare_record1_accessors(table)) DBUG_RETURN(1); char *trg_name_buff; List_iterator_fast<ulonglong> itm(triggers->definition_modes_list); List_iterator_fast<LEX_STRING> it_definer(triggers->definers_list); LEX *old_lex= thd->lex, lex; sp_rcontext *save_spcont= thd->spcont; ulong save_sql_mode= thd->variables.sql_mode; LEX_STRING *on_table_name; thd->lex= &lex; Loading Loading @@ -902,6 +921,21 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db, &table->mem_root)) goto err_with_lex_cleanup; if (!(on_table_name= (LEX_STRING*) alloc_root(&table->mem_root, sizeof(LEX_STRING)))) goto err_with_lex_cleanup; *on_table_name= lex.ident; if (triggers->on_table_names_list.push_back(on_table_name, &table->mem_root)) goto err_with_lex_cleanup; /* Let us check that we correctly update trigger definitions when we rename tables with triggers. */ DBUG_ASSERT(!my_strcasecmp(table_alias_charset, lex.query_tables->db, db) && !my_strcasecmp(table_alias_charset, lex.query_tables->table_name, table_name)); if (names_only) { lex_end(&lex); Loading Loading @@ -1067,7 +1101,7 @@ static TABLE_LIST *add_table_for_trigger(THD *thd, sp_name *trig) lex->query_tables= 0; lex->query_tables_last= &lex->query_tables; DBUG_RETURN(sp_add_to_query_tables(thd, lex, trig->m_db.str, trigname.trigger_table.str, TL_WRITE)); trigname.trigger_table.str, TL_IGNORE)); } Loading Loading @@ -1137,6 +1171,220 @@ bool Table_triggers_list::drop_all_triggers(THD *thd, char *db, char *name) } /* Update .TRG file after renaming triggers' subject table (change name of table in triggers' definitions). SYNOPSIS change_table_name_in_triggers() thd Thread context db_name Database of subject table old_table_name Old subject table's name new_table_name New subject table's name RETURN VALUE FALSE Success TRUE Failure */ bool Table_triggers_list::change_table_name_in_triggers(THD *thd, const char *db_name, LEX_STRING *old_table_name, LEX_STRING *new_table_name) { char path_buff[FN_REFLEN]; LEX_STRING *def, *on_table_name, new_def; ulonglong *sql_mode; ulong save_sql_mode= thd->variables.sql_mode; List_iterator_fast<LEX_STRING> it_def(definitions_list); List_iterator_fast<LEX_STRING> it_on_table_name(on_table_names_list); List_iterator_fast<ulonglong> it_mode(definition_modes_list); uint on_q_table_name_len, before_on_len; String buff; DBUG_ASSERT(definitions_list.elements == on_table_names_list.elements && definitions_list.elements == definition_modes_list.elements); while ((def= it_def++)) { on_table_name= it_on_table_name++; thd->variables.sql_mode= *(it_mode++); /* Construct CREATE TRIGGER statement with new table name. */ buff.length(0); before_on_len= on_table_name->str - def->str; buff.append(def->str, before_on_len); buff.append(STRING_WITH_LEN("ON ")); append_identifier(thd, &buff, new_table_name->str, new_table_name->length); on_q_table_name_len= buff.length() - before_on_len; buff.append(on_table_name->str + on_table_name->length, def->length - (before_on_len + on_table_name->length)); /* It is OK to allocate some memory on table's MEM_ROOT since this table instance will be thrown out at the end of rename anyway. */ new_def.str= memdup_root(&table->mem_root, buff.ptr(), buff.length()); new_def.length= buff.length(); on_table_name->str= new_def.str + before_on_len; on_table_name->length= on_q_table_name_len; *def= new_def; } thd->variables.sql_mode= save_sql_mode; if (thd->is_fatal_error) return TRUE; /* OOM */ if (save_trigger_file(this, db_name, new_table_name->str)) return TRUE; if (rm_trigger_file(path_buff, db_name, old_table_name->str)) { (void) rm_trigger_file(path_buff, db_name, new_table_name->str); return TRUE; } return FALSE; } /* Iterate though Table_triggers_list::names_list list and update .TRN files after renaming triggers' subject table. SYNOPSIS change_table_name_in_trignames() db_name Database of subject table new_table_name New subject table's name stopper Pointer to Table_triggers_list::names_list at which we should stop updating. RETURN VALUE 0 Success non-0 Failure, pointer to Table_triggers_list::names_list element for which update failed. */ LEX_STRING* Table_triggers_list::change_table_name_in_trignames(const char *db_name, LEX_STRING *new_table_name, LEX_STRING *stopper) { char dir_buff[FN_REFLEN], trigname_buff[FN_REFLEN]; struct st_trigname trigname; LEX_STRING dir, trigname_file; LEX_STRING *trigger; List_iterator_fast<LEX_STRING> it_name(names_list); strxnmov(dir_buff, FN_REFLEN, mysql_data_home, "/", db_name, "/", NullS); dir.length= unpack_filename(dir_buff, dir_buff); dir.str= dir_buff; while ((trigger= it_name++) != stopper) { trigname_file.length= strxnmov(trigname_buff, FN_REFLEN, trigger->str, trigname_file_ext, NullS) - trigname_buff; trigname_file.str= trigname_buff; trigname.trigger_table= *new_table_name; if (sql_create_definition_file(&dir, &trigname_file, &trigname_file_type, (gptr)&trigname, trigname_file_parameters, 0)) return trigger; } return 0; } /* Update .TRG and .TRN files after renaming triggers' subject table. SYNOPSIS change_table_name() thd Thread context db Old database of subject table old_table Old name of subject table new_db New database for subject table new_table New name of subject table NOTE This method tries to leave trigger related files in consistent state, i.e. it either will complete successfully, or will fail leaving files in their initial state. RETURN VALUE FALSE Success TRUE Error */ bool Table_triggers_list::change_table_name(THD *thd, const char *db, const char *old_table, const char *new_db, const char *new_table) { TABLE table; bool result= 0; LEX_STRING *err_trigname; DBUG_ENTER("change_table_name"); bzero(&table, sizeof(table)); init_alloc_root(&table.mem_root, 8192, 0); safe_mutex_assert_owner(&LOCK_open); if (Table_triggers_list::check_n_load(thd, db, old_table, &table, TRUE)) { result= 1; goto end; } if (table.triggers) { LEX_STRING_WITH_INIT old_table_name(old_table, strlen(old_table)); LEX_STRING_WITH_INIT new_table_name(new_table, strlen(new_table)); /* 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 ?). */ if (my_strcasecmp(table_alias_charset, db, new_db)) { my_error(ER_TRG_IN_WRONG_SCHEMA, MYF(0)); result= 1; goto end; } if (table.triggers->change_table_name_in_triggers(thd, db, &old_table_name, &new_table_name)) { result= 1; goto end; } if ((err_trigname= table.triggers->change_table_name_in_trignames( db, &new_table_name, 0))) { /* If we were unable to update one of .TRN files properly we will revert all changes that we have done and report about error. 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(db, &old_table_name, err_trigname); (void) table.triggers->change_table_name_in_triggers(thd, db, &new_table_name, &old_table_name); result= 1; goto end; } } end: delete table.triggers; free_root(&table.mem_root, MYF(0)); DBUG_RETURN(result); } bool Table_triggers_list::process_triggers(THD *thd, trg_event_type event, trg_action_time_type time_type, Loading Loading
mysql-test/r/trigger.result +102 −1 Original line number Diff line number Diff line drop table if exists t1, t2, t3; drop table if exists t1, t2, t3, t4; drop view if exists v1; drop database if exists mysqltest; drop function if exists f1; Loading Loading @@ -785,6 +785,107 @@ create trigger test.t1_bi before insert on t1 for each row set @a:=0; ERROR 3D000: No database selected drop trigger t1_bi; ERROR 3D000: No database selected create table t1 (id int); create trigger t1_bi before insert on t1 for each row set @a:=new.id; insert into t1 values (101); select @a; @a 101 select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; trigger_schema trigger_name event_object_schema event_object_table action_statement test t1_bi test t1 set @a:=new.id rename table t1 to t2; insert into t2 values (102); select @a; @a 102 select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; trigger_schema trigger_name event_object_schema event_object_table action_statement test t1_bi test t2 set @a:=new.id alter table t2 rename to t3; insert into t3 values (103); select @a; @a 103 select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; trigger_schema trigger_name event_object_schema event_object_table action_statement test t1_bi test t3 set @a:=new.id alter table t3 rename to t4, add column val int default 0; insert into t4 values (104, 1); select @a; @a 104 select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; trigger_schema trigger_name event_object_schema event_object_table action_statement test t1_bi test t4 set @a:=new.id drop trigger t1_bi; drop table t4; create database mysqltest; use mysqltest; create table t1 (id int); create trigger t1_bi before insert on t1 for each row set @a:=new.id; insert into t1 values (101); select @a; @a 101 select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test' or event_object_schema = 'mysqltest'; trigger_schema trigger_name event_object_schema event_object_table action_statement mysqltest t1_bi mysqltest t1 set @a:=new.id rename table t1 to test.t2; ERROR HY000: Trigger in wrong schema insert into t1 values (102); select @a; @a 102 select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test' or event_object_schema = 'mysqltest'; trigger_schema trigger_name event_object_schema event_object_table action_statement mysqltest t1_bi mysqltest t1 set @a:=new.id drop trigger test.t1_bi; ERROR HY000: Trigger does not exist drop trigger t1_bi; drop table t1; drop database mysqltest; use test; create table t1 (id int); create trigger t1_bi before insert on t1 for each row set @a:=new.id; create trigger t1_ai after insert on t1 for each row set @b:=new.id; insert into t1 values (101); select @a, @b; @a @b 101 101 select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; trigger_schema trigger_name event_object_schema event_object_table action_statement test t1_bi test t1 set @a:=new.id test t1_ai test t1 set @b:=new.id rename table t1 to t2; ERROR HY000: Can't create/write to file './test/t1_ai.TRN~' (Errcode: 13) insert into t1 values (102); select @a, @b; @a @b 102 102 select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; trigger_schema trigger_name event_object_schema event_object_table action_statement test t1_bi test t1 set @a:=new.id test t1_ai test t1 set @b:=new.id drop trigger t1_bi; drop trigger t1_ai; drop table t1; create table t1 (i int); create trigger t1_bi before insert on t1 for each row return 0; ERROR 42000: RETURN is only allowed in a FUNCTION Loading
mysql-test/t/trigger.test +86 −1 Original line number Diff line number Diff line Loading @@ -3,7 +3,7 @@ # --disable_warnings drop table if exists t1, t2, t3; drop table if exists t1, t2, t3, t4; drop view if exists v1; drop database if exists mysqltest; drop function if exists f1; Loading Loading @@ -959,6 +959,91 @@ create trigger test.t1_bi before insert on t1 for each row set @a:=0; drop trigger t1_bi; connection default; # # Test for bug #13525 "Rename table does not keep info of triggers" # create table t1 (id int); create trigger t1_bi before insert on t1 for each row set @a:=new.id; insert into t1 values (101); select @a; select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; rename table t1 to t2; # Trigger should work after rename insert into t2 values (102); select @a; select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; # Let us check that the same works for simple ALTER TABLE ... RENAME alter table t2 rename to t3; insert into t3 values (103); select @a; select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; # And for more complex ALTER TABLE alter table t3 rename to t4, add column val int default 0; insert into t4 values (104, 1); select @a; select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; # .TRN file should be updated with new table name drop trigger t1_bi; drop table t4; # Rename between different databases if triggers exist should fail create database mysqltest; use mysqltest; create table t1 (id int); create trigger t1_bi before insert on t1 for each row set @a:=new.id; insert into t1 values (101); select @a; select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test' or event_object_schema = 'mysqltest'; --error ER_TRG_IN_WRONG_SCHEMA rename table t1 to test.t2; insert into t1 values (102); select @a; select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test' or event_object_schema = 'mysqltest'; # There should be no fantom .TRN files --error ER_TRG_DOES_NOT_EXIST drop trigger test.t1_bi; drop trigger t1_bi; drop table t1; drop database mysqltest; use test; # And now let us check that the properly handle rename if there is some # error during it (that we rollback such renames completely). create table t1 (id int); create trigger t1_bi before insert on t1 for each row set @a:=new.id; create trigger t1_ai after insert on t1 for each row set @b:=new.id; insert into t1 values (101); select @a, @b; select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; # Trick which makes update of second .TRN file impossible system echo dummy >var/master-data/test/t1_ai.TRN~; system chmod 000 var/master-data/test/t1_ai.TRN~; --error 1 rename table t1 to t2; # 't1' should be still there and triggers should work correctly insert into t1 values (102); select @a, @b; select trigger_schema, trigger_name, event_object_schema, event_object_table, action_statement from information_schema.triggers where event_object_schema = 'test'; system chmod 600 var/master-data/test/t1_ai.TRN; # Let us check that updates to .TRN files were rolled back too drop trigger t1_bi; drop trigger t1_ai; drop table t1; # Test for bug #16829 "Firing trigger with RETURN crashes the server" # RETURN is not supposed to be used anywhere except functions, so error # should be returned when one attempts to create trigger with RETURN. Loading
sql/sql_rename.cc +21 −2 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ */ #include "mysql_priv.h" #include "sql_trigger.h" static TABLE_LIST *rename_tables(THD *thd, TABLE_LIST *table_list, Loading Loading @@ -176,8 +177,26 @@ rename_tables(THD *thd, TABLE_LIST *table_list, bool skip_error) if (table_type == DB_TYPE_UNKNOWN) my_error(ER_FILE_NOT_FOUND, MYF(0), name, my_errno); else rc= mysql_rename_table(table_type, ren_table->db, old_alias, new_table->db, new_alias); { if (!(rc= mysql_rename_table(table_type, ren_table->db, old_alias, new_table->db, new_alias))) { if ((rc= Table_triggers_list::change_table_name(thd, ren_table->db, old_alias, new_table->db, new_alias))) { /* We've succeeded in renaming table's .frm and in updating corresponding handler data, but have failed to update table's triggers appropriately. So let us revert operations on .frm and handler's data and report about failure to rename table. */ (void) mysql_rename_table(table_type, new_table->db, new_alias, ren_table->db, old_alias); } } } break; } case FRMTYPE_VIEW: Loading
sql/sql_table.cc +12 −1 Original line number Diff line number Diff line Loading @@ -3293,6 +3293,13 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, close_cached_table(thd, table); if (mysql_rename_table(old_db_type,db,table_name,new_db,new_alias)) error= -1; else if (Table_triggers_list::change_table_name(thd, db, table_name, new_db, new_alias)) { VOID(mysql_rename_table(old_db_type, new_db, new_alias, db, table_name)); error= -1; } } VOID(pthread_mutex_unlock(&LOCK_open)); } Loading Loading @@ -3841,7 +3848,11 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, VOID(quick_rm_table(new_db_type,new_db,tmp_name)); } else if (mysql_rename_table(new_db_type,new_db,tmp_name,new_db, new_alias)) new_alias) || (new_name != table_name || new_db != db) && // we also do rename Table_triggers_list::change_table_name(thd, db, table_name, new_db, new_alias)) { // Try to get everything back error=1; VOID(quick_rm_table(new_db_type,new_db,new_alias)); Loading
sql/sql_trigger.cc +269 −21 Original line number Diff line number Diff line Loading @@ -65,7 +65,6 @@ File_option sql_modes_parameters= */ static const int TRG_NUM_REQUIRED_PARAMETERS= 4; static const int TRG_MAX_VERSIONS= 3; /* Structure representing contents of .TRN file which are used to support Loading Loading @@ -467,8 +466,7 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, definer_host->str, NullS) - trg_definer->str; if (!sql_create_definition_file(&dir, &file, &triggers_file_type, (gptr)this, triggers_file_parameters, TRG_MAX_VERSIONS)) (gptr)this, triggers_file_parameters, 0)) return 0; err_with_cleanup: Loading @@ -492,7 +490,8 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, True - error */ static bool rm_trigger_file(char *path, char *db, char *table_name) static bool rm_trigger_file(char *path, const char *db, const char *table_name) { strxnmov(path, FN_REFLEN, mysql_data_home, "/", db, "/", table_name, triggers_file_ext, NullS); Loading @@ -516,7 +515,8 @@ static bool rm_trigger_file(char *path, char *db, char *table_name) True - error */ static bool rm_trigname_file(char *path, char *db, char *trigger_name) static bool rm_trigname_file(char *path, const char *db, const char *trigger_name) { strxnmov(path, FN_REFLEN, mysql_data_home, "/", db, "/", trigger_name, trigname_file_ext, NullS); Loading @@ -525,6 +525,38 @@ static bool rm_trigname_file(char *path, char *db, char *trigger_name) } /* Helper function that saves .TRG file for Table_triggers_list object. SYNOPSIS save_trigger_file() triggers Table_triggers_list object for which file should be saved db Name of database for subject table table_name Name of subject table RETURN VALUE FALSE Success TRUE Error */ static bool save_trigger_file(Table_triggers_list *triggers, const char *db, const char *table_name) { char dir_buff[FN_REFLEN], file_buff[FN_REFLEN]; LEX_STRING dir, file; strxnmov(dir_buff, FN_REFLEN, mysql_data_home, "/", db, "/", NullS); dir.length= unpack_filename(dir_buff, dir_buff); dir.str= dir_buff; file.length= strxnmov(file_buff, FN_REFLEN, table_name, triggers_file_ext, NullS) - file_buff; file.str= file_buff; return sql_create_definition_file(&dir, &file, &triggers_file_type, (gptr)triggers, triggers_file_parameters, 0); } /* Drop trigger for table. Loading Loading @@ -578,20 +610,7 @@ bool Table_triggers_list::drop_trigger(THD *thd, TABLE_LIST *tables) } else { char dir_buff[FN_REFLEN], file_buff[FN_REFLEN]; LEX_STRING dir, file; strxnmov(dir_buff, FN_REFLEN, mysql_data_home, "/", tables->db, "/", NullS); dir.length= unpack_filename(dir_buff, dir_buff); dir.str= dir_buff; file.length= strxnmov(file_buff, FN_REFLEN, tables->table_name, triggers_file_ext, NullS) - file_buff; file.str= file_buff; if (sql_create_definition_file(&dir, &file, &triggers_file_type, (gptr)this, triggers_file_parameters, TRG_MAX_VERSIONS)) if (save_trigger_file(this, tables->db, tables->table_name)) return 1; } Loading Loading @@ -831,12 +850,12 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db, if (!names_only && triggers->prepare_record1_accessors(table)) DBUG_RETURN(1); char *trg_name_buff; List_iterator_fast<ulonglong> itm(triggers->definition_modes_list); List_iterator_fast<LEX_STRING> it_definer(triggers->definers_list); LEX *old_lex= thd->lex, lex; sp_rcontext *save_spcont= thd->spcont; ulong save_sql_mode= thd->variables.sql_mode; LEX_STRING *on_table_name; thd->lex= &lex; Loading Loading @@ -902,6 +921,21 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db, &table->mem_root)) goto err_with_lex_cleanup; if (!(on_table_name= (LEX_STRING*) alloc_root(&table->mem_root, sizeof(LEX_STRING)))) goto err_with_lex_cleanup; *on_table_name= lex.ident; if (triggers->on_table_names_list.push_back(on_table_name, &table->mem_root)) goto err_with_lex_cleanup; /* Let us check that we correctly update trigger definitions when we rename tables with triggers. */ DBUG_ASSERT(!my_strcasecmp(table_alias_charset, lex.query_tables->db, db) && !my_strcasecmp(table_alias_charset, lex.query_tables->table_name, table_name)); if (names_only) { lex_end(&lex); Loading Loading @@ -1067,7 +1101,7 @@ static TABLE_LIST *add_table_for_trigger(THD *thd, sp_name *trig) lex->query_tables= 0; lex->query_tables_last= &lex->query_tables; DBUG_RETURN(sp_add_to_query_tables(thd, lex, trig->m_db.str, trigname.trigger_table.str, TL_WRITE)); trigname.trigger_table.str, TL_IGNORE)); } Loading Loading @@ -1137,6 +1171,220 @@ bool Table_triggers_list::drop_all_triggers(THD *thd, char *db, char *name) } /* Update .TRG file after renaming triggers' subject table (change name of table in triggers' definitions). SYNOPSIS change_table_name_in_triggers() thd Thread context db_name Database of subject table old_table_name Old subject table's name new_table_name New subject table's name RETURN VALUE FALSE Success TRUE Failure */ bool Table_triggers_list::change_table_name_in_triggers(THD *thd, const char *db_name, LEX_STRING *old_table_name, LEX_STRING *new_table_name) { char path_buff[FN_REFLEN]; LEX_STRING *def, *on_table_name, new_def; ulonglong *sql_mode; ulong save_sql_mode= thd->variables.sql_mode; List_iterator_fast<LEX_STRING> it_def(definitions_list); List_iterator_fast<LEX_STRING> it_on_table_name(on_table_names_list); List_iterator_fast<ulonglong> it_mode(definition_modes_list); uint on_q_table_name_len, before_on_len; String buff; DBUG_ASSERT(definitions_list.elements == on_table_names_list.elements && definitions_list.elements == definition_modes_list.elements); while ((def= it_def++)) { on_table_name= it_on_table_name++; thd->variables.sql_mode= *(it_mode++); /* Construct CREATE TRIGGER statement with new table name. */ buff.length(0); before_on_len= on_table_name->str - def->str; buff.append(def->str, before_on_len); buff.append(STRING_WITH_LEN("ON ")); append_identifier(thd, &buff, new_table_name->str, new_table_name->length); on_q_table_name_len= buff.length() - before_on_len; buff.append(on_table_name->str + on_table_name->length, def->length - (before_on_len + on_table_name->length)); /* It is OK to allocate some memory on table's MEM_ROOT since this table instance will be thrown out at the end of rename anyway. */ new_def.str= memdup_root(&table->mem_root, buff.ptr(), buff.length()); new_def.length= buff.length(); on_table_name->str= new_def.str + before_on_len; on_table_name->length= on_q_table_name_len; *def= new_def; } thd->variables.sql_mode= save_sql_mode; if (thd->is_fatal_error) return TRUE; /* OOM */ if (save_trigger_file(this, db_name, new_table_name->str)) return TRUE; if (rm_trigger_file(path_buff, db_name, old_table_name->str)) { (void) rm_trigger_file(path_buff, db_name, new_table_name->str); return TRUE; } return FALSE; } /* Iterate though Table_triggers_list::names_list list and update .TRN files after renaming triggers' subject table. SYNOPSIS change_table_name_in_trignames() db_name Database of subject table new_table_name New subject table's name stopper Pointer to Table_triggers_list::names_list at which we should stop updating. RETURN VALUE 0 Success non-0 Failure, pointer to Table_triggers_list::names_list element for which update failed. */ LEX_STRING* Table_triggers_list::change_table_name_in_trignames(const char *db_name, LEX_STRING *new_table_name, LEX_STRING *stopper) { char dir_buff[FN_REFLEN], trigname_buff[FN_REFLEN]; struct st_trigname trigname; LEX_STRING dir, trigname_file; LEX_STRING *trigger; List_iterator_fast<LEX_STRING> it_name(names_list); strxnmov(dir_buff, FN_REFLEN, mysql_data_home, "/", db_name, "/", NullS); dir.length= unpack_filename(dir_buff, dir_buff); dir.str= dir_buff; while ((trigger= it_name++) != stopper) { trigname_file.length= strxnmov(trigname_buff, FN_REFLEN, trigger->str, trigname_file_ext, NullS) - trigname_buff; trigname_file.str= trigname_buff; trigname.trigger_table= *new_table_name; if (sql_create_definition_file(&dir, &trigname_file, &trigname_file_type, (gptr)&trigname, trigname_file_parameters, 0)) return trigger; } return 0; } /* Update .TRG and .TRN files after renaming triggers' subject table. SYNOPSIS change_table_name() thd Thread context db Old database of subject table old_table Old name of subject table new_db New database for subject table new_table New name of subject table NOTE This method tries to leave trigger related files in consistent state, i.e. it either will complete successfully, or will fail leaving files in their initial state. RETURN VALUE FALSE Success TRUE Error */ bool Table_triggers_list::change_table_name(THD *thd, const char *db, const char *old_table, const char *new_db, const char *new_table) { TABLE table; bool result= 0; LEX_STRING *err_trigname; DBUG_ENTER("change_table_name"); bzero(&table, sizeof(table)); init_alloc_root(&table.mem_root, 8192, 0); safe_mutex_assert_owner(&LOCK_open); if (Table_triggers_list::check_n_load(thd, db, old_table, &table, TRUE)) { result= 1; goto end; } if (table.triggers) { LEX_STRING_WITH_INIT old_table_name(old_table, strlen(old_table)); LEX_STRING_WITH_INIT new_table_name(new_table, strlen(new_table)); /* 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 ?). */ if (my_strcasecmp(table_alias_charset, db, new_db)) { my_error(ER_TRG_IN_WRONG_SCHEMA, MYF(0)); result= 1; goto end; } if (table.triggers->change_table_name_in_triggers(thd, db, &old_table_name, &new_table_name)) { result= 1; goto end; } if ((err_trigname= table.triggers->change_table_name_in_trignames( db, &new_table_name, 0))) { /* If we were unable to update one of .TRN files properly we will revert all changes that we have done and report about error. 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(db, &old_table_name, err_trigname); (void) table.triggers->change_table_name_in_triggers(thd, db, &new_table_name, &old_table_name); result= 1; goto end; } } end: delete table.triggers; free_root(&table.mem_root, MYF(0)); DBUG_RETURN(result); } bool Table_triggers_list::process_triggers(THD *thd, trg_event_type event, trg_action_time_type time_type, Loading