diff options
-rw-r--r-- | mysql-test/r/sp_trans.result | 195 | ||||
-rw-r--r-- | mysql-test/t/sp_trans.test | 180 | ||||
-rw-r--r-- | sql/ha_innodb.cc | 12 | ||||
-rw-r--r-- | sql/handler.cc | 15 | ||||
-rw-r--r-- | sql/sql_class.cc | 18 | ||||
-rw-r--r-- | sql/sql_class.h | 1 | ||||
-rw-r--r-- | sql/sql_parse.cc | 4 |
7 files changed, 411 insertions, 14 deletions
diff --git a/mysql-test/r/sp_trans.result b/mysql-test/r/sp_trans.result index 6077087ab87..bb742d0d3d7 100644 --- a/mysql-test/r/sp_trans.result +++ b/mysql-test/r/sp_trans.result @@ -174,3 +174,198 @@ ERROR HY000: Explicit or implicit commit is not allowed in stored function or tr drop procedure bug10015_8| drop function bug10015_7| drop table t1, t2| +drop function if exists bug13825_0| +drop function if exists bug13825_1| +drop function if exists bug13825_2| +drop function if exists bug13825_3| +drop function if exists bug13825_4| +drop function if exists bug13825_5| +drop procedure if exists bug13825_0| +drop procedure if exists bug13825_1| +drop procedure if exists bug13825_2| +drop table if exists t1| +create table t1 (i int) engine=innodb| +create table t2 (i int) engine=innodb| +create function bug13825_0() returns int +begin +rollback to savepoint x; +return 1; +end| +create function bug13825_1() returns int +begin +release savepoint x; +return 1; +end| +create function bug13825_2() returns int +begin +insert into t1 values (2); +savepoint x; +insert into t1 values (3); +rollback to savepoint x; +insert into t1 values (4); +return 1; +end| +create procedure bug13825_0() +begin +rollback to savepoint x; +end| +create procedure bug13825_1() +begin +release savepoint x; +end| +create procedure bug13825_2() +begin +savepoint x; +end| +insert into t2 values (1)| +create trigger t2_bi before insert on t2 for each row +rollback to savepoint x| +create trigger t2_bu before update on t2 for each row +release savepoint x| +create trigger t2_bd before delete on t2 for each row +begin +insert into t1 values (2); +savepoint x; +insert into t1 values (3); +rollback to savepoint x; +insert into t1 values (4); +end| +create function bug13825_3(rb int) returns int +begin +insert into t1 values(1); +savepoint x; +insert into t1 values(2); +if rb then +rollback to savepoint x; +end if; +insert into t1 values(3); +return rb; +end| +create function bug13825_4() returns int +begin +savepoint x; +insert into t1 values(2); +rollback to savepoint x; +return 0; +end| +create function bug13825_5(p int) returns int +begin +savepoint x; +insert into t2 values(p); +rollback to savepoint x; +insert into t2 values(p+1); +return p; +end| +set autocommit= 0| +begin | +insert into t1 values (1)| +savepoint x| +set @a:= bug13825_0()| +ERROR 42000: SAVEPOINT x does not exist +insert into t2 values (2)| +ERROR 42000: SAVEPOINT x does not exist +set @a:= bug13825_1()| +ERROR 42000: SAVEPOINT x does not exist +update t2 set i = 2| +ERROR 42000: SAVEPOINT x does not exist +set @a:= bug13825_2()| +select * from t1| +i +1 +2 +4 +rollback to savepoint x| +select * from t1| +i +1 +delete from t2| +select * from t1| +i +1 +2 +4 +rollback to savepoint x| +select * from t1| +i +1 +release savepoint x| +set @a:= bug13825_2()| +select * from t1| +i +1 +2 +4 +rollback to savepoint x| +ERROR 42000: SAVEPOINT x does not exist +delete from t1| +commit| +begin| +insert into t1 values (5)| +savepoint x| +insert into t1 values (6)| +call bug13825_0()| +select * from t1| +i +5 +call bug13825_1()| +rollback to savepoint x| +ERROR 42000: SAVEPOINT x does not exist +savepoint x| +insert into t1 values (7)| +call bug13825_2()| +rollback to savepoint x| +select * from t1| +i +5 +7 +delete from t1| +commit| +set autocommit= 1| +select bug13825_3(0)| +bug13825_3(0) +0 +select * from t1| +i +1 +2 +3 +delete from t1| +select bug13825_3(1)| +bug13825_3(1) +1 +select * from t1| +i +1 +3 +delete from t1| +set autocommit= 0| +begin| +insert into t1 values (1)| +set @a:= bug13825_4()| +select * from t1| +i +1 +delete from t1| +commit| +set autocommit= 1| +drop table t2| +create table t2 (i int) engine=innodb| +insert into t1 values (1), (bug13825_5(2)), (3)| +select * from t1| +i +1 +2 +3 +select * from t2| +i +3 +drop function bug13825_0| +drop function bug13825_1| +drop function bug13825_2| +drop function bug13825_3| +drop function bug13825_4| +drop function bug13825_5| +drop procedure bug13825_0| +drop procedure bug13825_1| +drop procedure bug13825_2| +drop table t1, t2| diff --git a/mysql-test/t/sp_trans.test b/mysql-test/t/sp_trans.test index 82e1cd2f1c9..d72eaf5dca0 100644 --- a/mysql-test/t/sp_trans.test +++ b/mysql-test/t/sp_trans.test @@ -176,6 +176,186 @@ drop table t1, t2| # +# BUG#13825 "Triggers: crash if release savepoint". +# Also general test for handling of savepoints in stored routines. +# +# According to SQL standard we should establish new savepoint +# level before executing stored function/trigger and destroy +# this savepoint level after execution. Stored procedures by +# default should be executed using the same savepoint level +# as their caller (to execute stored procedure using new +# savepoint level one should explicitly specify NEW SAVEPOINT +# LEVEL clause in procedure creation statement which MySQL +# does not support yet). +--disable_warnings +drop function if exists bug13825_0| +drop function if exists bug13825_1| +drop function if exists bug13825_2| +drop function if exists bug13825_3| +drop function if exists bug13825_4| +drop function if exists bug13825_5| +drop procedure if exists bug13825_0| +drop procedure if exists bug13825_1| +drop procedure if exists bug13825_2| +drop table if exists t1| +--enable_warnings +create table t1 (i int) engine=innodb| +create table t2 (i int) engine=innodb| +create function bug13825_0() returns int +begin + rollback to savepoint x; + return 1; +end| +create function bug13825_1() returns int +begin + release savepoint x; + return 1; +end| +create function bug13825_2() returns int +begin + insert into t1 values (2); + savepoint x; + insert into t1 values (3); + rollback to savepoint x; + insert into t1 values (4); + return 1; +end| +create procedure bug13825_0() +begin + rollback to savepoint x; +end| +create procedure bug13825_1() +begin + release savepoint x; +end| +create procedure bug13825_2() +begin + savepoint x; +end| +insert into t2 values (1)| +create trigger t2_bi before insert on t2 for each row + rollback to savepoint x| +create trigger t2_bu before update on t2 for each row + release savepoint x| +create trigger t2_bd before delete on t2 for each row +begin + insert into t1 values (2); + savepoint x; + insert into t1 values (3); + rollback to savepoint x; + insert into t1 values (4); +end| +create function bug13825_3(rb int) returns int +begin + insert into t1 values(1); + savepoint x; + insert into t1 values(2); + if rb then + rollback to savepoint x; + end if; + insert into t1 values(3); + return rb; +end| +create function bug13825_4() returns int +begin + savepoint x; + insert into t1 values(2); + rollback to savepoint x; + return 0; +end| +create function bug13825_5(p int) returns int +begin + savepoint x; + insert into t2 values(p); + rollback to savepoint x; + insert into t2 values(p+1); + return p; +end| +set autocommit= 0| +# Test of savepoint level handling for stored functions and triggers +begin | +insert into t1 values (1)| +savepoint x| +--error ER_SP_DOES_NOT_EXIST +set @a:= bug13825_0()| +--error ER_SP_DOES_NOT_EXIST +insert into t2 values (2)| +--error ER_SP_DOES_NOT_EXIST +set @a:= bug13825_1()| +--error ER_SP_DOES_NOT_EXIST +update t2 set i = 2| +set @a:= bug13825_2()| +select * from t1| +rollback to savepoint x| +select * from t1| +delete from t2| +select * from t1| +rollback to savepoint x| +select * from t1| +# Of course savepoints set in function should not be visible from its caller +release savepoint x| +set @a:= bug13825_2()| +select * from t1| +--error ER_SP_DOES_NOT_EXIST +rollback to savepoint x| +delete from t1| +commit| +# Test of savepoint level handling for stored procedures +begin| +insert into t1 values (5)| +savepoint x| +insert into t1 values (6)| +call bug13825_0()| +select * from t1| +call bug13825_1()| +--error ER_SP_DOES_NOT_EXIST +rollback to savepoint x| +savepoint x| +insert into t1 values (7)| +call bug13825_2()| +rollback to savepoint x| +select * from t1| +delete from t1| +commit| +set autocommit= 1| +# Let us test that savepoints work inside of functions +# even in auto-commit mode +select bug13825_3(0)| +select * from t1| +delete from t1| +select bug13825_3(1)| +select * from t1| +delete from t1| +# Curious case: rolling back to savepoint which is set by first +# statement in function should not rollback whole transaction. +set autocommit= 0| +begin| +insert into t1 values (1)| +set @a:= bug13825_4()| +select * from t1| +delete from t1| +commit| +set autocommit= 1| +# Other curious case: savepoint in the middle of statement +drop table t2| +create table t2 (i int) engine=innodb| +insert into t1 values (1), (bug13825_5(2)), (3)| +select * from t1| +select * from t2| +# Cleanup +drop function bug13825_0| +drop function bug13825_1| +drop function bug13825_2| +drop function bug13825_3| +drop function bug13825_4| +drop function bug13825_5| +drop procedure bug13825_0| +drop procedure bug13825_1| +drop procedure bug13825_2| +drop table t1, t2| + + +# # BUG#NNNN: New bug synopsis # #--disable_warnings diff --git a/sql/ha_innodb.cc b/sql/ha_innodb.cc index 61af1afb2be..c092b83215a 100644 --- a/sql/ha_innodb.cc +++ b/sql/ha_innodb.cc @@ -2178,11 +2178,13 @@ innobase_savepoint( DBUG_ENTER("innobase_savepoint"); - if (!(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) { - /* In the autocommit state there is no sense to set a - savepoint: we return immediate success */ - DBUG_RETURN(0); - } + /* + In the autocommit mode there is no sense to set a savepoint + (unless we are in sub-statement), so SQL layer ensures that + this method is never called in such situation. + */ + DBUG_ASSERT(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN) || + thd->in_sub_stmt); trx = check_trx_exists(thd); diff --git a/sql/handler.cc b/sql/handler.cc index 81e94af5dc7..a9ca40902c8 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -1145,10 +1145,10 @@ int ha_update_statistics() int ha_rollback_to_savepoint(THD *thd, SAVEPOINT *sv) { int error=0; - THD_TRANS *trans=&thd->transaction.all; + THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction.stmt : + &thd->transaction.all); handlerton **ht=trans->ht, **end_ht; DBUG_ENTER("ha_rollback_to_savepoint"); - DBUG_ASSERT(thd->transaction.stmt.ht[0] == 0); trans->nht=sv->nht; trans->no_2pc=0; @@ -1176,7 +1176,7 @@ int ha_rollback_to_savepoint(THD *thd, SAVEPOINT *sv) for (; *ht ; ht++) { int err; - if ((err= (*(*ht)->rollback)(thd, 1))) + if ((err= (*(*ht)->rollback)(thd, !thd->in_sub_stmt))) { // cannot happen my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err); error=1; @@ -1196,10 +1196,10 @@ int ha_rollback_to_savepoint(THD *thd, SAVEPOINT *sv) int ha_savepoint(THD *thd, SAVEPOINT *sv) { int error=0; - THD_TRANS *trans=&thd->transaction.all; + THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction.stmt : + &thd->transaction.all); handlerton **ht=trans->ht; DBUG_ENTER("ha_savepoint"); - DBUG_ASSERT(thd->transaction.stmt.ht[0] == 0); #ifdef USING_TRANSACTIONS for (; *ht; ht++) { @@ -1225,9 +1225,10 @@ int ha_savepoint(THD *thd, SAVEPOINT *sv) int ha_release_savepoint(THD *thd, SAVEPOINT *sv) { int error=0; - handlerton **ht=thd->transaction.all.ht, **end_ht; + THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction.stmt : + &thd->transaction.all); + handlerton **ht=trans->ht, **end_ht; DBUG_ENTER("ha_release_savepoint"); - DBUG_ASSERT(thd->transaction.stmt.ht[0] == 0); end_ht=ht+sv->nht; for (; ht < end_ht; ht++) diff --git a/sql/sql_class.cc b/sql/sql_class.cc index fc9df020b6c..849e1f45b03 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -1916,6 +1916,7 @@ void THD::restore_backup_open_tables_state(Open_tables_state *backup) - Value for found_rows() is reset and restored - examined_row_count is added to the total - cuted_fields is added to the total + - new savepoint level is created and destroyed NOTES: Seed for random() is saved for the first! usage of RAND() @@ -1939,6 +1940,7 @@ void THD::reset_sub_statement_state(Sub_statement_state *backup, backup->sent_row_count= sent_row_count; backup->cuted_fields= cuted_fields; backup->client_capabilities= client_capabilities; + backup->savepoints= transaction.savepoints; if (!lex->requires_prelocking() || is_update_query(lex->sql_command)) options&= ~OPTION_BIN_LOG; @@ -1951,6 +1953,7 @@ void THD::reset_sub_statement_state(Sub_statement_state *backup, examined_row_count= 0; sent_row_count= 0; cuted_fields= 0; + transaction.savepoints= 0; #ifndef EMBEDDED_LIBRARY /* Surpress OK packets in case if we will execute statements */ @@ -1961,6 +1964,21 @@ void THD::reset_sub_statement_state(Sub_statement_state *backup, void THD::restore_sub_statement_state(Sub_statement_state *backup) { + /* + To save resources we want to release savepoints which were created + during execution of function or trigger before leaving their savepoint + level. It is enough to release first savepoint set on this level since + all later savepoints will be released automatically. + */ + if (transaction.savepoints) + { + SAVEPOINT *sv; + for (sv= transaction.savepoints; sv->prev; sv= sv->prev) + {} + /* ha_release_savepoint() never returns error. */ + (void)ha_release_savepoint(this, sv); + } + transaction.savepoints= backup->savepoints; options= backup->options; in_sub_stmt= backup->in_sub_stmt; net.no_send_ok= backup->no_send_ok; diff --git a/sql/sql_class.h b/sql/sql_class.h index 7ca168ec518..0cc4aca1812 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1097,6 +1097,7 @@ public: uint in_sub_stmt; bool enable_slow_log, insert_id_used; my_bool no_send_ok; + SAVEPOINT *savepoints; }; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 1882965fd1e..d529279eaed 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -4016,8 +4016,8 @@ end_with_restore_list: break; } case SQLCOM_SAVEPOINT: - if (!(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) || - !opt_using_transactions) + if (!(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN) || + thd->in_sub_stmt) || !opt_using_transactions) send_ok(thd); else { |