diff --git a/mysql-test/r/trigger.result b/mysql-test/r/trigger.result index 2290919f191..23f15f618f2 100644 --- a/mysql-test/r/trigger.result +++ b/mysql-test/r/trigger.result @@ -1960,6 +1960,26 @@ select * from t2; s1 drop table t1; drop temporary table t2; +#------------------------------------------------------------------------ +# Bug#39953 Triggers are not working properly with multi table updates +#------------------------------------------------------------------------ +DROP TABLE IF EXISTS t1; +DROP TRIGGER IF EXISTS t_insert; +DROP TABLE IF EXISTS t2; +CREATE TABLE t1 (a int, date_insert timestamp, PRIMARY KEY (a)); +INSERT INTO t1 (a) VALUES (2),(5); +CREATE TABLE t2 (a int, b int, PRIMARY KEY (a)); +CREATE TRIGGER t_insert AFTER INSERT ON t2 FOR EACH ROW BEGIN UPDATE t1,t2 SET +date_insert=NOW() WHERE t1.a=t2.b AND t2.a=NEW.a; END | +INSERT INTO t2 (a,b) VALUES (1,2); +DROP TRIGGER t_insert; +CREATE TRIGGER t_insert AFTER INSERT ON t2 FOR EACH ROW BEGIN UPDATE t1,t2 SET +date_insert=NOW(),b=b+1 WHERE t1.a=t2.b AND t2.a=NEW.a; END | +INSERT INTO t2 (a,b) VALUES (3,5); +ERROR HY000: Can't update table 't2' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. +DROP TABLE t1; +DROP TRIGGER t_insert; +DROP TABLE t2; End of 5.0 tests drop table if exists table_25411_a; drop table if exists table_25411_b; diff --git a/mysql-test/t/trigger.test b/mysql-test/t/trigger.test index 6974a4cc5da..9a5556c518d 100644 --- a/mysql-test/t/trigger.test +++ b/mysql-test/t/trigger.test @@ -2218,6 +2218,39 @@ select * from t1; select * from t2; drop table t1; drop temporary table t2; + +--echo #------------------------------------------------------------------------ +--echo # Bug#39953 Triggers are not working properly with multi table updates +--echo #------------------------------------------------------------------------ + +--disable_warnings +DROP TABLE IF EXISTS t1; +DROP TRIGGER IF EXISTS t_insert; +DROP TABLE IF EXISTS t2; +--enable_warnings + +CREATE TABLE t1 (a int, date_insert timestamp, PRIMARY KEY (a)); +INSERT INTO t1 (a) VALUES (2),(5); +CREATE TABLE t2 (a int, b int, PRIMARY KEY (a)); +DELIMITER |; +CREATE TRIGGER t_insert AFTER INSERT ON t2 FOR EACH ROW BEGIN UPDATE t1,t2 SET +date_insert=NOW() WHERE t1.a=t2.b AND t2.a=NEW.a; END | +DELIMITER ;| +INSERT INTO t2 (a,b) VALUES (1,2); + +DROP TRIGGER t_insert; + +DELIMITER |; +CREATE TRIGGER t_insert AFTER INSERT ON t2 FOR EACH ROW BEGIN UPDATE t1,t2 SET +date_insert=NOW(),b=b+1 WHERE t1.a=t2.b AND t2.a=NEW.a; END | +DELIMITER ;| +--error ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG +INSERT INTO t2 (a,b) VALUES (3,5); + +DROP TABLE t1; +DROP TRIGGER t_insert; +DROP TABLE t2; + --echo End of 5.0 tests # diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 0db371a666d..4f08aa0c07f 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -2600,27 +2600,11 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, { // Using table locks TABLE *best_table= 0; int best_distance= INT_MIN; - bool check_if_used= thd->prelocked_mode && - ((int) table_list->lock_type >= - (int) TL_WRITE_ALLOW_WRITE); for (table=thd->open_tables; table ; table=table->next) { if (table->s->table_cache_key.length == key_length && !memcmp(table->s->table_cache_key.str, key, key_length)) { - if (check_if_used && table->query_id && - table->query_id != thd->query_id) - { - /* - If we are in stored function or trigger we should ensure that - we won't change table that is already used by calling statement. - So if we are opening table for writing, we should check that it - is not already open by some calling stamement. - */ - my_error(ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG, MYF(0), - table->s->table_name.str); - DBUG_RETURN(0); - } /* When looking for a usable TABLE, ignore MERGE children, as they belong to their parent and cannot be used explicitly. @@ -2649,13 +2633,13 @@ TABLE *open_table(THD *thd, TABLE_LIST *table_list, MEM_ROOT *mem_root, { best_distance= distance; best_table= table; - if (best_distance == 0 && !check_if_used) + if (best_distance == 0) { /* - If we have found perfect match and we don't need to check that - table is not used by one of calling statements (assuming that - we are inside of function or trigger) we can finish iterating - through open tables list. + We have found a perfect match and can finish iterating + through open tables list. Check for table use conflict + between calling statement and SP/trigger is done in + lock_tables(). */ break; } @@ -4888,9 +4872,9 @@ TABLE *open_n_lock_single_table(THD *thd, TABLE_LIST *table_l, lock_flags Flags passed to mysql_lock_table NOTE - This function don't do anything like SP/SF/views/triggers analysis done - in open_tables(). It is intended for opening of only one concrete table. - And used only in special contexts. + This function doesn't do anything like SP/SF/views/triggers analysis done + in open_table()/lock_tables(). It is intended for opening of only one + concrete table. And used only in special contexts. RETURN VALUES table Opened table @@ -4908,6 +4892,9 @@ TABLE *open_ltable(THD *thd, TABLE_LIST *table_list, thr_lock_type lock_type, bool refresh; DBUG_ENTER("open_ltable"); + /* should not be used in a prelocked_mode context, see NOTE above */ + DBUG_ASSERT(!thd->prelocked_mode); + thd_proc_info(thd, "Opening table"); thd->current_tablenr= 0; /* open_ltable can be used only for BASIC TABLEs */ @@ -5374,8 +5361,29 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen) table && table != first_not_own; table= table->next_global) { - if (!table->placeholder() && - check_lock_and_start_stmt(thd, table->table, table->lock_type)) + if (table->placeholder()) + continue; + + /* + In a stored function or trigger we should ensure that we won't change + a table that is already used by the calling statement. + */ + if (thd->prelocked_mode && + table->lock_type >= TL_WRITE_ALLOW_WRITE) + { + for (TABLE* opentab= thd->open_tables; opentab; opentab= opentab->next) + { + if (table->table->s == opentab->s && opentab->query_id && + table->table->query_id != opentab->query_id) + { + my_error(ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG, MYF(0), + table->table->s->table_name.str); + DBUG_RETURN(-1); + } + } + } + + if (check_lock_and_start_stmt(thd, table->table, table->lock_type)) { DBUG_RETURN(-1); }