Loading mysql-test/t/trigger.test +113 −3 Original line number Diff line number Diff line Loading @@ -10,6 +10,11 @@ drop function if exists f1; drop procedure if exists p1; --enable_warnings # Create additional connections used through test connect (addconroot1, localhost, root,,); connect (addconroot2, localhost, root,,); connection default; create table t1 (i int); # let us test some very simple trigger Loading Loading @@ -680,12 +685,10 @@ end| delimiter ;| update t1 set data = 1; connect (addconroot, localhost, root,,); connection addconroot; connection addconroot1; update t1 set data = 2; connection default; disconnect addconroot; drop table t1; # Loading Loading @@ -765,3 +768,110 @@ insert into t1 values (3); select * from t1; drop trigger t1_bi; drop tables t1, t2; # Tests for bug #12704 "Server crashes during trigger execution". # If we run DML statements and CREATE TRIGGER statements concurrently # it may happen that trigger will be created while DML statement is # waiting for table lock. In this case we have to reopen tables and # recalculate prelocking set. # Unfortunately these tests rely on the order in which tables are locked # by statement so they are non determenistic and are disabled. --disable_parsing create table t1 (id int); create table t2 (id int); create table t3 (id int); create function f1() returns int return (select max(id)+2 from t2); create view v1 as select f1() as f; # Let us check that we notice trigger at all connection addconroot1; lock tables t2 write; connection default; send insert into t1 values ((select max(id) from t2)), (2); --sleep 1 connection addconroot2; create trigger t1_trg before insert on t1 for each row set NEW.id:= 1; connection addconroot1; unlock tables; connection default; reap; select * from t1; # Check that we properly calculate new prelocking set insert into t2 values (3); connection addconroot1; lock tables t2 write; connection default; send insert into t1 values ((select max(id) from t2)), (4); --sleep 1 connection addconroot2; drop trigger t1_trg; create trigger t1_trg before insert on t1 for each row insert into t3 values (new.id); connection addconroot1; unlock tables; connection default; reap; select * from t1; select * from t3; # We should be able to do this even if fancy views are involved connection addconroot1; lock tables t2 write; connection default; send insert into t1 values ((select max(f) from v1)), (6); --sleep 1 connection addconroot2; drop trigger t1_trg; create trigger t1_trg before insert on t1 for each row insert into t3 values (new.id + 100); connection addconroot1; unlock tables; connection default; reap; select * from t1; select * from t3; # This also should work for multi-update # Let us drop trigger to demonstrate that prelocking set is really # rebuilt drop trigger t1_trg; connection addconroot1; lock tables t2 write; connection default; send update t1, t2 set t1.id=10 where t1.id=t2.id; --sleep 1 connection addconroot2; create trigger t1_trg before update on t1 for each row insert into t3 values (new.id); connection addconroot1; unlock tables; connection default; reap; select * from t1; select * from t3; # And even for multi-update converted from ordinary update thanks to view drop view v1; drop trigger t1_trg; create view v1 as select t1.id as id1 from t1, t2 where t1.id= t2.id; insert into t2 values (10); connection addconroot1; lock tables t2 write; connection default; send update v1 set id1= 11; --sleep 1 connection addconroot2; create trigger t1_trg before update on t1 for each row insert into t3 values (new.id + 100); connection addconroot1; unlock tables; connection default; reap; select * from t1; select * from t3; drop function f1; drop view v1; drop table t1, t2, t3; --enable_parsing sql/lock.cc +18 −3 Original line number Diff line number Diff line Loading @@ -93,23 +93,33 @@ static void print_lock_error(int error, const char *); flags Options: MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK Ignore a global read lock MYSQL_LOCK_IGNORE_FLUSH Ignore a flush tables. MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN Instead of reopening altered or dropped tables by itself, mysql_lock_tables() should notify upper level and rely on caller doing this. need_reopen Out parameter, TRUE if some tables were altered or deleted and should be reopened by caller. RETURN A lock structure pointer on success. NULL on error. NULL on error or if some tables should be reopen. */ /* Map the return value of thr_lock to an error from errmsg.txt */ static int thr_lock_errno_to_mysql[]= { 0, 1, ER_LOCK_WAIT_TIMEOUT, ER_LOCK_DEADLOCK }; MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, uint flags) MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, uint flags, bool *need_reopen) { MYSQL_LOCK *sql_lock; TABLE *write_lock_used; int rc; /* Map the return value of thr_lock to an error from errmsg.txt */ DBUG_ENTER("mysql_lock_tables"); *need_reopen= FALSE; for (;;) { if (!(sql_lock = get_lock_data(thd,tables,count, 0,&write_lock_used))) Loading Loading @@ -178,6 +188,11 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, uint flags) thd->locked=0; retry: sql_lock=0; if (flags & MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN) { *need_reopen= TRUE; break; } if (wait_for_tables(thd)) break; // Couldn't open tables } Loading sql/mysql_priv.h +5 −2 Original line number Diff line number Diff line Loading @@ -952,7 +952,7 @@ int open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags); int simple_open_n_lock_tables(THD *thd,TABLE_LIST *tables); bool open_and_lock_tables(THD *thd,TABLE_LIST *tables); bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags); int lock_tables(THD *thd, TABLE_LIST *tables, uint counter); int lock_tables(THD *thd, TABLE_LIST *tables, uint counter, bool *need_reopen); TABLE *open_temporary_table(THD *thd, const char *path, const char *db, const char *table_name, bool link_in_list); bool rm_temporary_table(enum db_type base, char *path); Loading @@ -960,6 +960,7 @@ void free_io_cache(TABLE *entry); void intern_close_table(TABLE *entry); bool close_thread_table(THD *thd, TABLE **table_ptr); void close_temporary_tables(THD *thd); void close_tables_for_reopen(THD *thd, TABLE_LIST *tables); TABLE_LIST *find_table_in_list(TABLE_LIST *table, uint offset_to_list, const char *db_name, Loading Loading @@ -1238,10 +1239,12 @@ extern pthread_t signal_thread; extern struct st_VioSSLAcceptorFd * ssl_acceptor_fd; #endif /* HAVE_OPENSSL */ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count, uint flags); MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count, uint flags, bool *need_reopen); /* mysql_lock_tables() flags bits */ #define MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK 0x0001 #define MYSQL_LOCK_IGNORE_FLUSH 0x0002 #define MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN 0x0004 void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock); void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock); Loading sql/sp.cc +61 −6 Original line number Diff line number Diff line Loading @@ -107,7 +107,7 @@ TABLE *open_proc_table_for_read(THD *thd, Open_tables_state *backup) { TABLE_LIST tables; TABLE *table; bool refresh; bool not_used; DBUG_ENTER("open_proc_table"); /* Loading @@ -122,7 +122,7 @@ TABLE *open_proc_table_for_read(THD *thd, Open_tables_state *backup) bzero((char*) &tables, sizeof(tables)); tables.db= (char*) "mysql"; tables.table_name= tables.alias= (char*)"proc"; if (!(table= open_table(thd, &tables, thd->mem_root, &refresh, if (!(table= open_table(thd, &tables, thd->mem_root, ¬_used, MYSQL_LOCK_IGNORE_FLUSH))) { thd->restore_backup_open_tables_state(backup); Loading @@ -138,7 +138,7 @@ TABLE *open_proc_table_for_read(THD *thd, Open_tables_state *backup) could lead to a deadlock if we have other tables opened. */ if (!(thd->lock= mysql_lock_tables(thd, &table, 1, MYSQL_LOCK_IGNORE_FLUSH))) MYSQL_LOCK_IGNORE_FLUSH, ¬_used))) { close_proc_table(thd, backup); DBUG_RETURN(0); Loading Loading @@ -1265,7 +1265,8 @@ static bool add_used_routine(LEX *lex, Query_arena *arena, /* Add routine to the set of stored routines used by statement. Add routine which is explicitly used by statement to the set of stored routines used by this statement. SYNOPSIS sp_add_used_routine() Loading @@ -1276,7 +1277,8 @@ static bool add_used_routine(LEX *lex, Query_arena *arena, rt_type - routine type (one of TYPE_ENUM_PROCEDURE/...) NOTES Will also add element to end of 'LEX::sroutines_list' list. Will also add element to end of 'LEX::sroutines_list' list (and will take into account that this is explicitly used routine). To be friendly towards prepared statements one should pass persistent arena as second argument. Loading @@ -1287,6 +1289,37 @@ void sp_add_used_routine(LEX *lex, Query_arena *arena, { rt->set_routine_type(rt_type); (void)add_used_routine(lex, arena, &rt->m_sroutines_key); lex->sroutines_list_own_last= lex->sroutines_list.next; lex->sroutines_list_own_elements= lex->sroutines_list.elements; } /* Remove routines which are only indirectly used by statement from the set of routines used by this statement. SYNOPSIS sp_remove_not_own_routines() lex LEX representing statement */ void sp_remove_not_own_routines(LEX *lex) { Sroutine_hash_entry *not_own_rt, *next_rt; for (not_own_rt= *(Sroutine_hash_entry **)lex->sroutines_list_own_last; not_own_rt; not_own_rt= next_rt) { /* It is safe to obtain not_own_rt->next after calling hash_delete() now but we want to be more future-proof. */ next_rt= not_own_rt->next; hash_delete(&lex->sroutines, (byte *)not_own_rt); } *(Sroutine_hash_entry **)lex->sroutines_list_own_last= NULL; lex->sroutines_list.next= lex->sroutines_list_own_last; lex->sroutines_list.elements= lex->sroutines_list_own_elements; } Loading Loading @@ -1343,6 +1376,28 @@ static void sp_update_stmt_used_routines(THD *thd, LEX *lex, HASH *src) } /* Add contents of list representing set of routines to the set of routines used by statement. SYNOPSIS sp_update_stmt_used_routines() thd Thread context lex LEX representing statement src List representing set from which routines will be added NOTE It will also add elements to end of 'LEX::sroutines_list' list. */ static void sp_update_stmt_used_routines(THD *thd, LEX *lex, SQL_LIST *src) { for (Sroutine_hash_entry *rt= (Sroutine_hash_entry *)src->first; rt; rt= rt->next) (void)add_used_routine(lex, thd->stmt_arena, &rt->key); } /* Cache sub-set of routines used by statement, add tables used by these routines to statement table list. Do the same for all routines used Loading Loading @@ -1463,7 +1518,7 @@ sp_cache_routines_and_add_tables_for_view(THD *thd, LEX *lex, LEX *aux_lex) { Sroutine_hash_entry **last_cached_routine_ptr= (Sroutine_hash_entry **)lex->sroutines_list.next; sp_update_stmt_used_routines(thd, lex, &aux_lex->sroutines); sp_update_stmt_used_routines(thd, lex, &aux_lex->sroutines_list); (void)sp_cache_routines_and_add_tables_aux(thd, lex, *last_cached_routine_ptr, FALSE); } Loading sql/sp.h +1 −0 Original line number Diff line number Diff line Loading @@ -84,6 +84,7 @@ void sp_get_prelocking_info(THD *thd, bool *need_prelocking, bool *first_no_prelocking); void sp_add_used_routine(LEX *lex, Query_arena *arena, sp_name *rt, char rt_type); void sp_remove_not_own_routines(LEX *lex); void sp_update_sp_used_routines(HASH *dst, HASH *src); bool sp_cache_routines_and_add_tables(THD *thd, LEX *lex, bool first_no_prelock); Loading Loading
mysql-test/t/trigger.test +113 −3 Original line number Diff line number Diff line Loading @@ -10,6 +10,11 @@ drop function if exists f1; drop procedure if exists p1; --enable_warnings # Create additional connections used through test connect (addconroot1, localhost, root,,); connect (addconroot2, localhost, root,,); connection default; create table t1 (i int); # let us test some very simple trigger Loading Loading @@ -680,12 +685,10 @@ end| delimiter ;| update t1 set data = 1; connect (addconroot, localhost, root,,); connection addconroot; connection addconroot1; update t1 set data = 2; connection default; disconnect addconroot; drop table t1; # Loading Loading @@ -765,3 +768,110 @@ insert into t1 values (3); select * from t1; drop trigger t1_bi; drop tables t1, t2; # Tests for bug #12704 "Server crashes during trigger execution". # If we run DML statements and CREATE TRIGGER statements concurrently # it may happen that trigger will be created while DML statement is # waiting for table lock. In this case we have to reopen tables and # recalculate prelocking set. # Unfortunately these tests rely on the order in which tables are locked # by statement so they are non determenistic and are disabled. --disable_parsing create table t1 (id int); create table t2 (id int); create table t3 (id int); create function f1() returns int return (select max(id)+2 from t2); create view v1 as select f1() as f; # Let us check that we notice trigger at all connection addconroot1; lock tables t2 write; connection default; send insert into t1 values ((select max(id) from t2)), (2); --sleep 1 connection addconroot2; create trigger t1_trg before insert on t1 for each row set NEW.id:= 1; connection addconroot1; unlock tables; connection default; reap; select * from t1; # Check that we properly calculate new prelocking set insert into t2 values (3); connection addconroot1; lock tables t2 write; connection default; send insert into t1 values ((select max(id) from t2)), (4); --sleep 1 connection addconroot2; drop trigger t1_trg; create trigger t1_trg before insert on t1 for each row insert into t3 values (new.id); connection addconroot1; unlock tables; connection default; reap; select * from t1; select * from t3; # We should be able to do this even if fancy views are involved connection addconroot1; lock tables t2 write; connection default; send insert into t1 values ((select max(f) from v1)), (6); --sleep 1 connection addconroot2; drop trigger t1_trg; create trigger t1_trg before insert on t1 for each row insert into t3 values (new.id + 100); connection addconroot1; unlock tables; connection default; reap; select * from t1; select * from t3; # This also should work for multi-update # Let us drop trigger to demonstrate that prelocking set is really # rebuilt drop trigger t1_trg; connection addconroot1; lock tables t2 write; connection default; send update t1, t2 set t1.id=10 where t1.id=t2.id; --sleep 1 connection addconroot2; create trigger t1_trg before update on t1 for each row insert into t3 values (new.id); connection addconroot1; unlock tables; connection default; reap; select * from t1; select * from t3; # And even for multi-update converted from ordinary update thanks to view drop view v1; drop trigger t1_trg; create view v1 as select t1.id as id1 from t1, t2 where t1.id= t2.id; insert into t2 values (10); connection addconroot1; lock tables t2 write; connection default; send update v1 set id1= 11; --sleep 1 connection addconroot2; create trigger t1_trg before update on t1 for each row insert into t3 values (new.id + 100); connection addconroot1; unlock tables; connection default; reap; select * from t1; select * from t3; drop function f1; drop view v1; drop table t1, t2, t3; --enable_parsing
sql/lock.cc +18 −3 Original line number Diff line number Diff line Loading @@ -93,23 +93,33 @@ static void print_lock_error(int error, const char *); flags Options: MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK Ignore a global read lock MYSQL_LOCK_IGNORE_FLUSH Ignore a flush tables. MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN Instead of reopening altered or dropped tables by itself, mysql_lock_tables() should notify upper level and rely on caller doing this. need_reopen Out parameter, TRUE if some tables were altered or deleted and should be reopened by caller. RETURN A lock structure pointer on success. NULL on error. NULL on error or if some tables should be reopen. */ /* Map the return value of thr_lock to an error from errmsg.txt */ static int thr_lock_errno_to_mysql[]= { 0, 1, ER_LOCK_WAIT_TIMEOUT, ER_LOCK_DEADLOCK }; MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, uint flags) MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, uint flags, bool *need_reopen) { MYSQL_LOCK *sql_lock; TABLE *write_lock_used; int rc; /* Map the return value of thr_lock to an error from errmsg.txt */ DBUG_ENTER("mysql_lock_tables"); *need_reopen= FALSE; for (;;) { if (!(sql_lock = get_lock_data(thd,tables,count, 0,&write_lock_used))) Loading Loading @@ -178,6 +188,11 @@ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **tables, uint count, uint flags) thd->locked=0; retry: sql_lock=0; if (flags & MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN) { *need_reopen= TRUE; break; } if (wait_for_tables(thd)) break; // Couldn't open tables } Loading
sql/mysql_priv.h +5 −2 Original line number Diff line number Diff line Loading @@ -952,7 +952,7 @@ int open_tables(THD *thd, TABLE_LIST **tables, uint *counter, uint flags); int simple_open_n_lock_tables(THD *thd,TABLE_LIST *tables); bool open_and_lock_tables(THD *thd,TABLE_LIST *tables); bool open_normal_and_derived_tables(THD *thd, TABLE_LIST *tables, uint flags); int lock_tables(THD *thd, TABLE_LIST *tables, uint counter); int lock_tables(THD *thd, TABLE_LIST *tables, uint counter, bool *need_reopen); TABLE *open_temporary_table(THD *thd, const char *path, const char *db, const char *table_name, bool link_in_list); bool rm_temporary_table(enum db_type base, char *path); Loading @@ -960,6 +960,7 @@ void free_io_cache(TABLE *entry); void intern_close_table(TABLE *entry); bool close_thread_table(THD *thd, TABLE **table_ptr); void close_temporary_tables(THD *thd); void close_tables_for_reopen(THD *thd, TABLE_LIST *tables); TABLE_LIST *find_table_in_list(TABLE_LIST *table, uint offset_to_list, const char *db_name, Loading Loading @@ -1238,10 +1239,12 @@ extern pthread_t signal_thread; extern struct st_VioSSLAcceptorFd * ssl_acceptor_fd; #endif /* HAVE_OPENSSL */ MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count, uint flags); MYSQL_LOCK *mysql_lock_tables(THD *thd, TABLE **table, uint count, uint flags, bool *need_reopen); /* mysql_lock_tables() flags bits */ #define MYSQL_LOCK_IGNORE_GLOBAL_READ_LOCK 0x0001 #define MYSQL_LOCK_IGNORE_FLUSH 0x0002 #define MYSQL_LOCK_NOTIFY_IF_NEED_REOPEN 0x0004 void mysql_unlock_tables(THD *thd, MYSQL_LOCK *sql_lock); void mysql_unlock_read_tables(THD *thd, MYSQL_LOCK *sql_lock); Loading
sql/sp.cc +61 −6 Original line number Diff line number Diff line Loading @@ -107,7 +107,7 @@ TABLE *open_proc_table_for_read(THD *thd, Open_tables_state *backup) { TABLE_LIST tables; TABLE *table; bool refresh; bool not_used; DBUG_ENTER("open_proc_table"); /* Loading @@ -122,7 +122,7 @@ TABLE *open_proc_table_for_read(THD *thd, Open_tables_state *backup) bzero((char*) &tables, sizeof(tables)); tables.db= (char*) "mysql"; tables.table_name= tables.alias= (char*)"proc"; if (!(table= open_table(thd, &tables, thd->mem_root, &refresh, if (!(table= open_table(thd, &tables, thd->mem_root, ¬_used, MYSQL_LOCK_IGNORE_FLUSH))) { thd->restore_backup_open_tables_state(backup); Loading @@ -138,7 +138,7 @@ TABLE *open_proc_table_for_read(THD *thd, Open_tables_state *backup) could lead to a deadlock if we have other tables opened. */ if (!(thd->lock= mysql_lock_tables(thd, &table, 1, MYSQL_LOCK_IGNORE_FLUSH))) MYSQL_LOCK_IGNORE_FLUSH, ¬_used))) { close_proc_table(thd, backup); DBUG_RETURN(0); Loading Loading @@ -1265,7 +1265,8 @@ static bool add_used_routine(LEX *lex, Query_arena *arena, /* Add routine to the set of stored routines used by statement. Add routine which is explicitly used by statement to the set of stored routines used by this statement. SYNOPSIS sp_add_used_routine() Loading @@ -1276,7 +1277,8 @@ static bool add_used_routine(LEX *lex, Query_arena *arena, rt_type - routine type (one of TYPE_ENUM_PROCEDURE/...) NOTES Will also add element to end of 'LEX::sroutines_list' list. Will also add element to end of 'LEX::sroutines_list' list (and will take into account that this is explicitly used routine). To be friendly towards prepared statements one should pass persistent arena as second argument. Loading @@ -1287,6 +1289,37 @@ void sp_add_used_routine(LEX *lex, Query_arena *arena, { rt->set_routine_type(rt_type); (void)add_used_routine(lex, arena, &rt->m_sroutines_key); lex->sroutines_list_own_last= lex->sroutines_list.next; lex->sroutines_list_own_elements= lex->sroutines_list.elements; } /* Remove routines which are only indirectly used by statement from the set of routines used by this statement. SYNOPSIS sp_remove_not_own_routines() lex LEX representing statement */ void sp_remove_not_own_routines(LEX *lex) { Sroutine_hash_entry *not_own_rt, *next_rt; for (not_own_rt= *(Sroutine_hash_entry **)lex->sroutines_list_own_last; not_own_rt; not_own_rt= next_rt) { /* It is safe to obtain not_own_rt->next after calling hash_delete() now but we want to be more future-proof. */ next_rt= not_own_rt->next; hash_delete(&lex->sroutines, (byte *)not_own_rt); } *(Sroutine_hash_entry **)lex->sroutines_list_own_last= NULL; lex->sroutines_list.next= lex->sroutines_list_own_last; lex->sroutines_list.elements= lex->sroutines_list_own_elements; } Loading Loading @@ -1343,6 +1376,28 @@ static void sp_update_stmt_used_routines(THD *thd, LEX *lex, HASH *src) } /* Add contents of list representing set of routines to the set of routines used by statement. SYNOPSIS sp_update_stmt_used_routines() thd Thread context lex LEX representing statement src List representing set from which routines will be added NOTE It will also add elements to end of 'LEX::sroutines_list' list. */ static void sp_update_stmt_used_routines(THD *thd, LEX *lex, SQL_LIST *src) { for (Sroutine_hash_entry *rt= (Sroutine_hash_entry *)src->first; rt; rt= rt->next) (void)add_used_routine(lex, thd->stmt_arena, &rt->key); } /* Cache sub-set of routines used by statement, add tables used by these routines to statement table list. Do the same for all routines used Loading Loading @@ -1463,7 +1518,7 @@ sp_cache_routines_and_add_tables_for_view(THD *thd, LEX *lex, LEX *aux_lex) { Sroutine_hash_entry **last_cached_routine_ptr= (Sroutine_hash_entry **)lex->sroutines_list.next; sp_update_stmt_used_routines(thd, lex, &aux_lex->sroutines); sp_update_stmt_used_routines(thd, lex, &aux_lex->sroutines_list); (void)sp_cache_routines_and_add_tables_aux(thd, lex, *last_cached_routine_ptr, FALSE); } Loading
sql/sp.h +1 −0 Original line number Diff line number Diff line Loading @@ -84,6 +84,7 @@ void sp_get_prelocking_info(THD *thd, bool *need_prelocking, bool *first_no_prelocking); void sp_add_used_routine(LEX *lex, Query_arena *arena, sp_name *rt, char rt_type); void sp_remove_not_own_routines(LEX *lex); void sp_update_sp_used_routines(HASH *dst, HASH *src); bool sp_cache_routines_and_add_tables(THD *thd, LEX *lex, bool first_no_prelock); Loading