summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mysql-test/r/sp-error.result30
-rw-r--r--mysql-test/r/trigger.result41
-rw-r--r--mysql-test/t/sp-error.test29
-rw-r--r--mysql-test/t/trigger.test36
-rw-r--r--sql/share/errmsg.txt2
-rw-r--r--sql/sql_base.cc74
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;
+ }
+ }
}
}
}