diff options
-rw-r--r-- | mysql-test/r/sp-error.result | 30 | ||||
-rw-r--r-- | mysql-test/r/trigger.result | 41 | ||||
-rw-r--r-- | mysql-test/t/sp-error.test | 29 | ||||
-rw-r--r-- | mysql-test/t/trigger.test | 36 | ||||
-rw-r--r-- | sql/share/errmsg.txt | 2 | ||||
-rw-r--r-- | sql/sql_base.cc | 74 |
6 files changed, 188 insertions, 24 deletions
diff --git a/mysql-test/r/sp-error.result b/mysql-test/r/sp-error.result index bd383379abc..4ac29a07757 100644 --- a/mysql-test/r/sp-error.result +++ b/mysql-test/r/sp-error.result @@ -299,6 +299,36 @@ lock tables t1 read, mysql.proc read| unlock tables| lock tables mysql.proc write| unlock tables| +drop function if exists f1| +create function f1(i int) returns int +begin +insert into t1 (val) values (i); +return 0; +end| +select val, f1(val) from t1| +ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. +select val, f1(val) from t1 as tab| +ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. +select * from t1| +val x +42 3.1 +19 1.2 +update t1 set val= f1(val)| +ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. +select * from t1| +val x +42 3.1 +19 1.2 +select f1(17)| +f1(17) +0 +select * from t1| +val x +42 3.1 +19 1.2 +17 NULL +delete from t1 where val= 17| +drop function f1| create procedure bug1965() begin declare c cursor for select val from t1 order by valname; diff --git a/mysql-test/r/trigger.result b/mysql-test/r/trigger.result index 6a5063f54c9..312a7a90fc9 100644 --- a/mysql-test/r/trigger.result +++ b/mysql-test/r/trigger.result @@ -697,3 +697,44 @@ c1 c2 3 NULL drop procedure bug11587; drop table t1; +create table t1 (f1 integer); +create table t2 (f2 integer); +create trigger t1_ai after insert on t1 +for each row insert into t2 values (new.f1+1); +create trigger t2_ai after insert on t2 +for each row insert into t1 values (new.f2+1); +insert into t1 values (1); +ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. +select * from t1; +f1 +1 +select * from t2; +f2 +2 +drop trigger t1_ai; +drop trigger t2_ai; +create trigger t1_bu before update on t1 +for each row insert into t1 values (2); +update t1 set f1= 10; +ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. +select * from t1; +f1 +1 +drop trigger t1_bu; +create trigger t1_bu before update on t1 +for each row delete from t1 where f1=new.f1; +update t1 set f1= 10; +ERROR HY000: Can't update table 't1' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. +select * from t1; +f1 +1 +drop trigger t1_bu; +create trigger t1_bi before insert on t1 +for each row set new.f1=(select sum(f1) from t1); +insert into t1 values (3); +select * from t1; +f1 +1 +1 +drop trigger t1_bi; +drop tables t1, t2; diff --git a/mysql-test/t/sp-error.test b/mysql-test/t/sp-error.test index 85b81f72f40..f68ea1b31a3 100644 --- a/mysql-test/t/sp-error.test +++ b/mysql-test/t/sp-error.test @@ -410,6 +410,35 @@ unlock tables| # +# Check that in functions we don't allow to update tables which +# are used by statements which invoke these functions. +# +--disable_warnings +drop function if exists f1| +--enable_warnings +create function f1(i int) returns int +begin + insert into t1 (val) values (i); + return 0; +end| +--error ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG +select val, f1(val) from t1| +# Table alias should not matter +--error ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG +select val, f1(val) from t1 as tab| +select * from t1| +--error ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG +update t1 set val= f1(val)| +select * from t1| +# But this should be OK +select f1(17)| +select * from t1| +# Cleanup +delete from t1 where val= 17| +drop function f1| + + +# # BUG#1965 # create procedure bug1965() diff --git a/mysql-test/t/trigger.test b/mysql-test/t/trigger.test index cbc9da2bf51..9920f203c94 100644 --- a/mysql-test/t/trigger.test +++ b/mysql-test/t/trigger.test @@ -729,3 +729,39 @@ select * from t1; drop procedure bug11587; drop table t1; + +# Test for bug #11896 "Partial locking in case of recursive trigger +# definitions". Recursion in triggers should not be allowed. +# We also should not allow to change tables which are used in +# statements invoking this trigger. +create table t1 (f1 integer); +create table t2 (f2 integer); +create trigger t1_ai after insert on t1 + for each row insert into t2 values (new.f1+1); +create trigger t2_ai after insert on t2 + for each row insert into t1 values (new.f2+1); +--error ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG +insert into t1 values (1); +select * from t1; +select * from t2; +drop trigger t1_ai; +drop trigger t2_ai; +create trigger t1_bu before update on t1 + for each row insert into t1 values (2); +--error ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG +update t1 set f1= 10; +select * from t1; +drop trigger t1_bu; +create trigger t1_bu before update on t1 + for each row delete from t1 where f1=new.f1; +--error ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG +update t1 set f1= 10; +select * from t1; +drop trigger t1_bu; +# This should work tough +create trigger t1_bi before insert on t1 + for each row set new.f1=(select sum(f1) from t1); +insert into t1 values (3); +select * from t1; +drop trigger t1_bi; +drop tables t1, t2; diff --git a/sql/share/errmsg.txt b/sql/share/errmsg.txt index 65afefef35a..b49b7248021 100644 --- a/sql/share/errmsg.txt +++ b/sql/share/errmsg.txt @@ -5397,3 +5397,5 @@ ER_XAER_DUPID XAE08 eng "XAER_DUPID: The XID already exists" ER_DATETIME_FUNCTION_OVERFLOW 22008 eng "Datetime function: %-.32s field overflow" +ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG + eng "Can't update table '%-.64s' in stored function/trigger because it is already used by statement which invoked this stored function/trigger." diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 186d5984dcd..d1220122264 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -1043,35 +1043,61 @@ 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->key_length == key_length && - !memcmp(table->s->table_cache_key, key, key_length) && - !my_strcasecmp(system_charset_info, table->alias, alias) && - table->query_id != thd->query_id && /* skip tables already used */ - !(thd->prelocked_mode && table->query_id)) + !memcmp(table->s->table_cache_key, key, key_length)) { - int distance= ((int) table->reginfo.lock_type - - (int) table_list->lock_type); - /* - Find a table that either has the exact lock type requested, - or has the best suitable lock. In case there is no locked - table that has an equal or higher lock than requested, - we us the closest matching lock to be able to produce an error - message about wrong lock mode on the table. The best_table is changed - if bd < 0 <= d or bd < d < 0 or 0 <= d < bd. - - distance < 0 - No suitable lock found - distance > 0 - we have lock mode higher then we require - distance == 0 - we have lock mode exactly which we need - */ - if (best_distance < 0 && distance > best_distance || - distance >= 0 && distance < best_distance) + 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); + DBUG_RETURN(0); + } + if (!my_strcasecmp(system_charset_info, table->alias, alias) && + table->query_id != thd->query_id && /* skip tables already used */ + !(thd->prelocked_mode && table->query_id)) { - best_distance= distance; - best_table= table; - if (best_distance == 0) // Found perfect lock - break; + int distance= ((int) table->reginfo.lock_type - + (int) table_list->lock_type); + /* + Find a table that either has the exact lock type requested, + or has the best suitable lock. In case there is no locked + table that has an equal or higher lock than requested, + we us the closest matching lock to be able to produce an error + message about wrong lock mode on the table. The best_table + is changed if bd < 0 <= d or bd < d < 0 or 0 <= d < bd. + + distance < 0 - No suitable lock found + distance > 0 - we have lock mode higher then we require + distance == 0 - we have lock mode exactly which we need + */ + if (best_distance < 0 && distance > best_distance || + distance >= 0 && distance < best_distance) + { + best_distance= distance; + best_table= table; + if (best_distance == 0 && !check_if_used) + { + /* + 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. + */ + break; + } + } } } } |