A fix and a test case for Bug#12713 "Error in a stored function called from
a SELECT doesn't cause ROLLBACK of statem". The idea of the fix is to ensure that we always commit the current statement at the end of dispatch_command(). In order to not issue redundant disc syncs, an optimization of the two-phase commit protocol is implemented to bypass the two phase commit if the transaction is read-only.
This commit is contained in:
parent
48d326612a
commit
acf9b1f346
742
mysql-test/include/commit.inc
Normal file
742
mysql-test/include/commit.inc
Normal file
@ -0,0 +1,742 @@
|
|||||||
|
## Bug#12713 (Error in a stored function called from a SELECT doesn't cause
|
||||||
|
## ROLLBACK of statem)
|
||||||
|
|
||||||
|
##
|
||||||
|
## Pre-Requisites :
|
||||||
|
## - $engine_type should be set
|
||||||
|
##
|
||||||
|
|
||||||
|
set sql_mode=no_engine_substitution;
|
||||||
|
eval set storage_engine = $engine_type;
|
||||||
|
set autocommit=1;
|
||||||
|
|
||||||
|
--disable_warnings
|
||||||
|
drop table if exists t1;
|
||||||
|
drop table if exists t2;
|
||||||
|
drop table if exists t3;
|
||||||
|
drop function if exists f2;
|
||||||
|
drop procedure if exists bug12713_call;
|
||||||
|
drop procedure if exists bug12713_dump_spvars;
|
||||||
|
drop procedure if exists dummy;
|
||||||
|
--enable_warnings
|
||||||
|
|
||||||
|
create table t1 (a int);
|
||||||
|
create table t2 (a int unique);
|
||||||
|
create table t3 (a int);
|
||||||
|
|
||||||
|
# a workaround for Bug#32633: Can not create any routine if
|
||||||
|
# SQL_MODE=no_engine_substitution
|
||||||
|
|
||||||
|
set sql_mode=default;
|
||||||
|
|
||||||
|
insert into t1 (a) values (1), (2);
|
||||||
|
insert into t3 (a) values (1), (2);
|
||||||
|
|
||||||
|
delimiter |;
|
||||||
|
|
||||||
|
## Cause a failure every time
|
||||||
|
create function f2(x int) returns int
|
||||||
|
begin
|
||||||
|
insert into t2 (a) values (x);
|
||||||
|
insert into t2 (a) values (x);
|
||||||
|
return x;
|
||||||
|
end|
|
||||||
|
|
||||||
|
delimiter ;|
|
||||||
|
|
||||||
|
set autocommit=0;
|
||||||
|
|
||||||
|
flush status;
|
||||||
|
##============================================================================
|
||||||
|
## Design notes
|
||||||
|
##
|
||||||
|
## In each case, statement rollback is expected.
|
||||||
|
## for transactional engines, the rollback should be properly executed
|
||||||
|
## for non transactional engines, the rollback may cause warnings.
|
||||||
|
##
|
||||||
|
## The test pattern is as follows
|
||||||
|
## - insert 1000+N
|
||||||
|
## - statement with a side effect, that fails to insert N twice
|
||||||
|
## - a statement rollback is expected (expecting 1 row 1000+N only) in t2
|
||||||
|
## - a rollback is performed
|
||||||
|
## - expecting a clean table t2.
|
||||||
|
##============================================================================
|
||||||
|
|
||||||
|
insert into t2 (a) values (1001);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
insert into t1 (a) values (f2(1));
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
insert into t2 (a) values (1002);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
insert into t3 (a) select f2(2) from t1;
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
insert into t2 (a) values (1003);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
update t1 set a= a + f2(3);
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
insert into t2 (a) values (1004);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
update t1, t3 set t1.a = 0, t3.a = 0 where (f2(4) = 4) and (t1.a = t3.a);
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
insert into t2 (a) values (1005);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
delete from t1 where (a = f2(5));
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
insert into t2 (a) values (1006);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
delete from t1, t3 using t1, t3 where (f2(6) = 6) ;
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
insert into t2 (a) values (1007);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
replace t1 values (f2(7));
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
insert into t2 (a) values (1008);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
replace into t3 (a) select f2(8) from t1;
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
insert into t2 (a) values (1009);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
select f2(9) from t1 ;
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
insert into t2 (a) values (1010);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
show databases where (f2(10) = 10);
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
insert into t2 (a) values (1011);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
show tables where (f2(11) = 11);
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
insert into t2 (a) values (1012);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
show triggers where (f2(12) = 12);
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
insert into t2 (a) values (1013);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
show table status where (f2(13) = 13);
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
insert into t2 (a) values (1014);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
show open tables where (f2(14) = 14);
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
insert into t2 (a) values (1015);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
show columns in mysql.proc where (f2(15) = 15);
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
insert into t2 (a) values (1016);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
show status where (f2(16) = 16);
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
insert into t2 (a) values (1017);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
show variables where (f2(17) = 17);
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
insert into t2 (a) values (1018);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
show charset where (f2(18) = 18);
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
insert into t2 (a) values (1019);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
show collation where (f2(19) = 19);
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
--echo # We need at least one procedure to make sure the WHERE clause is
|
||||||
|
--echo # evaluated
|
||||||
|
create procedure dummy() begin end;
|
||||||
|
insert into t2 (a) values (1020);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
show procedure status where (f2(20) = 20);
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
drop procedure dummy;
|
||||||
|
|
||||||
|
insert into t2 (a) values (1021);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
show function status where (f2(21) = 21);
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
insert into t2 (a) values (1022);
|
||||||
|
prepare stmt from "insert into t1 (a) values (f2(22))";
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
execute stmt;
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
insert into t2 (a) values (1023);
|
||||||
|
do (f2(23));
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
## Please note :
|
||||||
|
## This will insert a record 1024 in t1 (statement commit)
|
||||||
|
## This will insert a record 24 in t1 (statement commit)
|
||||||
|
## then will rollback the second insert only (24) (statement rollback)
|
||||||
|
## then will rollback the complete transaction (transaction rollback)
|
||||||
|
|
||||||
|
delimiter |;
|
||||||
|
|
||||||
|
create procedure bug12713_call ()
|
||||||
|
begin
|
||||||
|
insert into t2 (a) values (24);
|
||||||
|
insert into t2 (a) values (24);
|
||||||
|
end|
|
||||||
|
|
||||||
|
delimiter ;|
|
||||||
|
|
||||||
|
insert into t2 (a) values (1024);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
call bug12713_call();
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
--echo =======================================================================
|
||||||
|
--echo Testing select_to_file
|
||||||
|
--echo =======================================================================
|
||||||
|
|
||||||
|
insert into t2 (a) values (1025);
|
||||||
|
|
||||||
|
--replace_result $MYSQLTEST_VARDIR ..
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
eval select f2(25) into outfile "$MYSQLTEST_VARDIR/tmp/dml.out" from t1;
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
--remove_file $MYSQLTEST_VARDIR/tmp/dml.out
|
||||||
|
|
||||||
|
insert into t2 (a) values (1026);
|
||||||
|
--replace_result $MYSQLTEST_VARDIR ..
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
eval load data infile "../std_data_ln/words.dat" into table t1 (a) set a:=f2(26);
|
||||||
|
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
--echo =======================================================================
|
||||||
|
--echo Testing select_dumpvar
|
||||||
|
--echo =======================================================================
|
||||||
|
|
||||||
|
insert into t2 (a) values (1027);
|
||||||
|
--error ER_DUP_ENTRY
|
||||||
|
select f2(27) into @foo;
|
||||||
|
select * from t2;
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
--echo =======================================================================
|
||||||
|
--echo Testing Select_fetch_into_spvars
|
||||||
|
--echo =======================================================================
|
||||||
|
|
||||||
|
delimiter |;
|
||||||
|
|
||||||
|
create procedure bug12713_dump_spvars ()
|
||||||
|
begin
|
||||||
|
declare foo int;
|
||||||
|
|
||||||
|
declare continue handler for sqlexception
|
||||||
|
begin
|
||||||
|
select "Exception trapped";
|
||||||
|
end;
|
||||||
|
|
||||||
|
select f2(28) into foo;
|
||||||
|
select * from t2;
|
||||||
|
end|
|
||||||
|
|
||||||
|
delimiter ;|
|
||||||
|
|
||||||
|
insert into t2 (a) values (1028);
|
||||||
|
call bug12713_dump_spvars ();
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
|
||||||
|
--echo =======================================================================
|
||||||
|
--echo Cleanup
|
||||||
|
--echo =======================================================================
|
||||||
|
|
||||||
|
set autocommit=default;
|
||||||
|
|
||||||
|
drop table t1;
|
||||||
|
drop table t2;
|
||||||
|
drop table t3;
|
||||||
|
drop function f2;
|
||||||
|
drop procedure bug12713_call;
|
||||||
|
drop procedure bug12713_dump_spvars;
|
||||||
|
--echo #
|
||||||
|
--echo # Bug#12713 Error in a stored function called from a SELECT doesn't
|
||||||
|
--echo # cause ROLLBACK of statem
|
||||||
|
--echo #
|
||||||
|
--echo # Verify that two-phase commit is not issued for read-only
|
||||||
|
--echo # transactions.
|
||||||
|
--echo #
|
||||||
|
--echo # Verify that two-phase commit is issued for read-write transactions,
|
||||||
|
--echo # even if the change is done inside a stored function called from
|
||||||
|
--echo # SELECT or SHOW statement.
|
||||||
|
--echo #
|
||||||
|
set autocommit=0;
|
||||||
|
--disable_warnings
|
||||||
|
drop table if exists t1;
|
||||||
|
drop table if exists t2;
|
||||||
|
drop function if exists f1;
|
||||||
|
drop procedure if exists p_verify_status_increment;
|
||||||
|
--enable_warnings
|
||||||
|
|
||||||
|
set sql_mode=no_engine_substitution;
|
||||||
|
create table t1 (a int unique);
|
||||||
|
create table t2 (a int) engine=myisam;
|
||||||
|
set sql_mode=default;
|
||||||
|
--echo #
|
||||||
|
--echo # An auxiliary procedure to track Handler_prepare and Handler_commit
|
||||||
|
--echo # statistics.
|
||||||
|
--echo #
|
||||||
|
delimiter |;
|
||||||
|
create procedure
|
||||||
|
p_verify_status_increment(commit_inc_mixed int, prepare_inc_mixed int,
|
||||||
|
commit_inc_row int, prepare_inc_row int)
|
||||||
|
begin
|
||||||
|
declare commit_inc int;
|
||||||
|
declare prepare_inc int;
|
||||||
|
declare old_commit_count int default ifnull(@commit_count, 0);
|
||||||
|
declare old_prepare_count int default ifnull(@prepare_count, 0);
|
||||||
|
declare c_res int;
|
||||||
|
# Use a cursor to have just one access to I_S instead of 2, it is very slow
|
||||||
|
# and amounts for over 90% of test CPU time
|
||||||
|
declare c cursor for
|
||||||
|
select variable_value
|
||||||
|
from information_schema.session_status
|
||||||
|
where variable_name='Handler_commit' or variable_name='Handler_prepare'
|
||||||
|
order by variable_name;
|
||||||
|
|
||||||
|
if @@global.binlog_format = 'ROW' then
|
||||||
|
set commit_inc= commit_inc_row;
|
||||||
|
set prepare_inc= prepare_inc_row;
|
||||||
|
else
|
||||||
|
set commit_inc= commit_inc_mixed;
|
||||||
|
set prepare_inc= prepare_inc_mixed;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
open c;
|
||||||
|
fetch c into c_res;
|
||||||
|
set @commit_count=c_res;
|
||||||
|
fetch c into c_res;
|
||||||
|
set @prepare_count=c_res;
|
||||||
|
close c;
|
||||||
|
|
||||||
|
if old_commit_count + commit_inc <> @commit_count then
|
||||||
|
select concat("Expected commit increment: ", commit_inc,
|
||||||
|
" actual: ", @commit_count - old_commit_count)
|
||||||
|
as 'ERROR';
|
||||||
|
elseif old_prepare_count + prepare_inc <> @prepare_count then
|
||||||
|
select concat("Expected prepare increment: ", prepare_inc,
|
||||||
|
" actual: ", @prepare_count - old_prepare_count)
|
||||||
|
as 'ERROR';
|
||||||
|
else
|
||||||
|
select '' as 'SUCCESS';
|
||||||
|
end if;
|
||||||
|
end|
|
||||||
|
delimiter ;|
|
||||||
|
--echo # Reset Handler_commit and Handler_prepare counters
|
||||||
|
flush status;
|
||||||
|
--echo #
|
||||||
|
--echo # 1. Read-only statement: SELECT
|
||||||
|
--echo #
|
||||||
|
select * from t1;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
|
||||||
|
--echo # 2. Read-write statement: INSERT, insert 1 row.
|
||||||
|
--echo #
|
||||||
|
insert into t1 (a) values (1);
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
|
||||||
|
--echo # 3. Read-write statement: UPDATE, update 1 row.
|
||||||
|
--echo #
|
||||||
|
update t1 set a=2;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
|
||||||
|
--echo # 4. Read-write statement: UPDATE, update 0 rows, 1 row matches WHERE
|
||||||
|
--echo #
|
||||||
|
--echo # Note the wrong Handler_prepare/Handler_commit count is due to
|
||||||
|
--echo # Bug#29157 "UPDATE, changed rows incorrect" and
|
||||||
|
--echo # Bug#Bug #33846 UPDATE word:Wrong 'Changed rows' if InnoDB, unique
|
||||||
|
--echo # key and no rows qualify WHERE
|
||||||
|
--echo #
|
||||||
|
update t1 set a=2;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
|
||||||
|
--echo # 5. Read-write statement: UPDATE, update 0 rows, 0 rows match WHERE
|
||||||
|
--echo #
|
||||||
|
--echo # In mixed replication mode, there is a read-only transaction
|
||||||
|
--echo # in InnoDB and also the statement is written to the binary log.
|
||||||
|
--echo # So we have two commits but no 2pc, since the first engine's
|
||||||
|
--echo # transaction is read-only.
|
||||||
|
--echo # In the row level replication mode, we only have the read-only
|
||||||
|
--echo # transaction in InnoDB and nothing is written to the binary log.
|
||||||
|
--echo #
|
||||||
|
update t1 set a=3 where a=1;
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
|
||||||
|
--echo # 6. Read-write statement: DELETE, delete 0 rows.
|
||||||
|
--echo #
|
||||||
|
delete from t1 where a=1;
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
|
||||||
|
--echo # 7. Read-write statement: DELETE, delete 1 row.
|
||||||
|
--echo #
|
||||||
|
delete from t1 where a=2;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
|
||||||
|
--echo # 8. Read-write statement: unqualified DELETE
|
||||||
|
--echo #
|
||||||
|
--echo # In statement or mixed replication mode, we call
|
||||||
|
--echo # handler::ha_delete_all_rows() and write statement text
|
||||||
|
--echo # to the binary log. This results in two read-write transactions.
|
||||||
|
--echo # In row level replication mode, we do not call
|
||||||
|
--echo # handler::ha_delete_all_rows(), but delete rows one by one.
|
||||||
|
--echo # Since there are no rows, nothing is written to the binary log.
|
||||||
|
--echo # Thus we have just one read-only transaction in InnoDB.
|
||||||
|
delete from t1;
|
||||||
|
call p_verify_status_increment(2, 2, 1, 0);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 1, 0);
|
||||||
|
|
||||||
|
--echo # 9. Read-write statement: REPLACE, change 1 row.
|
||||||
|
--echo #
|
||||||
|
replace t1 set a=1;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
|
||||||
|
--echo # 10. Read-write statement: REPLACE, change 0 rows.
|
||||||
|
--echo #
|
||||||
|
replace t1 set a=1;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
|
||||||
|
--echo # 11. Read-write statement: IODKU, change 1 row.
|
||||||
|
--echo #
|
||||||
|
insert t1 set a=1 on duplicate key update a=a+1;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
select * from t1;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
|
||||||
|
--echo # 12. Read-write statement: IODKU, change 0 rows.
|
||||||
|
--echo #
|
||||||
|
insert t1 set a=2 on duplicate key update a=2;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
|
||||||
|
--echo # 13. Read-write statement: INSERT IGNORE, change 0 rows.
|
||||||
|
--echo #
|
||||||
|
insert ignore t1 set a=2;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
|
||||||
|
--echo # 14. Read-write statement: INSERT IGNORE, change 1 row.
|
||||||
|
--echo #
|
||||||
|
insert ignore t1 set a=1;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
--echo # 15. Read-write statement: UPDATE IGNORE, change 0 rows.
|
||||||
|
--echo #
|
||||||
|
update ignore t1 set a=2 where a=1;
|
||||||
|
call p_verify_status_increment(2, 2, 1, 0);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 1, 0);
|
||||||
|
--echo #
|
||||||
|
--echo # Create a stored function that modifies a
|
||||||
|
--echo # non-transactional table. Demonstrate that changes in
|
||||||
|
--echo # non-transactional tables do not affect the two phase commit
|
||||||
|
--echo # algorithm.
|
||||||
|
--echo #
|
||||||
|
delimiter |;
|
||||||
|
create function f1() returns int
|
||||||
|
begin
|
||||||
|
insert t2 set a=2;
|
||||||
|
return 2;
|
||||||
|
end|
|
||||||
|
delimiter ;|
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
|
||||||
|
--echo # 16. A function changes non-trans-table.
|
||||||
|
--echo #
|
||||||
|
select f1();
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
|
||||||
|
--echo # 17. Read-only statement, a function changes non-trans-table.
|
||||||
|
--echo #
|
||||||
|
select f1() from t1;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
|
||||||
|
--echo # 18. Read-write statement: UPDATE, change 0 (transactional) rows.
|
||||||
|
--echo #
|
||||||
|
select count(*) from t2;
|
||||||
|
update t1 set a=2 where a=f1()+10;
|
||||||
|
select count(*) from t2;
|
||||||
|
call p_verify_status_increment(2, 0, 2, 0);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 0, 2, 0);
|
||||||
|
--echo #
|
||||||
|
--echo # Replace the non-transactional table with a temporary
|
||||||
|
--echo # transactional table. Demonstrate that a change to a temporary
|
||||||
|
--echo # transactional table does not provoke 2-phase commit, although
|
||||||
|
--echo # does trigger a commit and a binlog write (in statement mode).
|
||||||
|
--echo #
|
||||||
|
drop table t2;
|
||||||
|
set sql_mode=no_engine_substitution;
|
||||||
|
create temporary table t2 (a int);
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
set sql_mode=default;
|
||||||
|
--echo # 19. A function changes temp-trans-table.
|
||||||
|
--echo #
|
||||||
|
select f1();
|
||||||
|
--echo # Two commits because a binary log record is written
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
|
||||||
|
--echo # 20. Read-only statement, a function changes non-trans-table.
|
||||||
|
--echo #
|
||||||
|
select f1() from t1;
|
||||||
|
--echo # Two commits because a binary log record is written
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
|
||||||
|
--echo # 21. Read-write statement: UPDATE, change 0 (transactional) rows.
|
||||||
|
--echo #
|
||||||
|
update t1 set a=2 where a=f1()+10;
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
|
||||||
|
--echo # 22. DDL: ALTER TEMPORARY TABLE, should not cause a 2pc
|
||||||
|
--echo #
|
||||||
|
alter table t2 add column b int default 5;
|
||||||
|
--echo # A commit is done internally by ALTER.
|
||||||
|
call p_verify_status_increment(2, 0, 2, 0);
|
||||||
|
commit;
|
||||||
|
--echo # There is nothing left to commit
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
|
||||||
|
--echo # 23. DDL: RENAME TEMPORARY TABLE, does not start a transaction
|
||||||
|
--echo
|
||||||
|
--echo # No test because of Bug#8729 "rename table fails on temporary table"
|
||||||
|
|
||||||
|
--echo # 24. DDL: TRUNCATE TEMPORARY TABLE, does not start a transaction
|
||||||
|
--echo
|
||||||
|
truncate table t2;
|
||||||
|
call p_verify_status_increment(2, 0, 2, 0);
|
||||||
|
commit;
|
||||||
|
--echo # There is nothing left to commit
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
|
||||||
|
--echo # 25. Read-write statement: unqualified DELETE
|
||||||
|
--echo
|
||||||
|
delete from t2;
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
commit;
|
||||||
|
--echo # There is nothing left to commit
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
|
||||||
|
--echo # 25. DDL: DROP TEMPORARY TABLE, does not start a transaction
|
||||||
|
--echo #
|
||||||
|
drop temporary table t2;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
|
||||||
|
--echo # 26. Verify that SET AUTOCOMMIT issues an implicit commit
|
||||||
|
--echo #
|
||||||
|
insert t1 set a=3;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
set autocommit=1;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
rollback;
|
||||||
|
select a from t1 where a=3;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
delete from t1 where a=3;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
set autocommit=0;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
insert t1 set a=3;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
--echo # Sic: not actually changing the value of autocommit
|
||||||
|
set autocommit=0;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
rollback;
|
||||||
|
select a from t1 where a=3;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
|
||||||
|
--echo # 27. Savepoint management
|
||||||
|
--echo #
|
||||||
|
insert t1 set a=3;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
savepoint a;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
insert t1 set a=4;
|
||||||
|
--echo # Sic: a bug. Binlog did not register itself this time.
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
release savepoint a;
|
||||||
|
rollback;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
select a from t1 where a=3;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
|
||||||
|
--echo # 28. Read-write statement: DO
|
||||||
|
--echo #
|
||||||
|
create table t2 (a int);
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
do (select f1() from t1 where a=2);
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
|
||||||
|
--echo # 29. Read-write statement: MULTI-DELETE
|
||||||
|
--echo #
|
||||||
|
delete t1, t2 from t1 join t2 on (t1.a=t2.a) where t1.a=2;
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(4, 4, 4, 4);
|
||||||
|
|
||||||
|
--echo # 30. Read-write statement: INSERT-SELECT, MULTI-UPDATE, REPLACE-SELECT
|
||||||
|
--echo #
|
||||||
|
insert into t2 select a from t1;
|
||||||
|
commit;
|
||||||
|
replace into t2 select a from t1;
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(8, 8, 8, 8);
|
||||||
|
#
|
||||||
|
# Multi-update is one of the few remaining statements that still
|
||||||
|
# locks the tables at prepare step (and hence starts the transaction.
|
||||||
|
# Disable the PS protocol, since in this protocol we get a different
|
||||||
|
# number of commmits (there is an extra commit after prepare
|
||||||
|
#
|
||||||
|
--disable_ps_protocol
|
||||||
|
update t1, t2 set t1.a=4, t2.a=8 where t1.a=t2.a and t1.a=1;
|
||||||
|
--enable_ps_protocol
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(4, 4, 4, 4);
|
||||||
|
|
||||||
|
--echo # 31. DDL: various DDL with transactional tables
|
||||||
|
--echo #
|
||||||
|
--echo # Sic: no table is created.
|
||||||
|
create table if not exists t2 (a int) select 6 union select 7;
|
||||||
|
--echo # Sic: first commits the statement, and then the transaction.
|
||||||
|
call p_verify_status_increment(4, 4, 4, 4);
|
||||||
|
create table t3 select a from t2;
|
||||||
|
call p_verify_status_increment(4, 4, 4, 4);
|
||||||
|
alter table t3 add column (b int);
|
||||||
|
call p_verify_status_increment(2, 0, 2, 0);
|
||||||
|
alter table t3 rename t4;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
rename table t4 to t3;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
truncate table t3;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
create view v1 as select * from t2;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
check table t1;
|
||||||
|
call p_verify_status_increment(3, 0, 3, 0);
|
||||||
|
--echo # Sic: after this bug is fixed, CHECK leaves no pending transaction
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
check table t1, t2, t3;
|
||||||
|
call p_verify_status_increment(6, 0, 6, 0);
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
drop view v1;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
|
||||||
|
--echo #
|
||||||
|
--echo # Cleanup
|
||||||
|
--echo #
|
||||||
|
drop table t1;
|
||||||
|
drop procedure p_verify_status_increment;
|
||||||
|
drop function f1;
|
888
mysql-test/r/commit_1innodb.result
Normal file
888
mysql-test/r/commit_1innodb.result
Normal file
@ -0,0 +1,888 @@
|
|||||||
|
set sql_mode=no_engine_substitution;
|
||||||
|
set storage_engine = InnoDB;
|
||||||
|
set autocommit=1;
|
||||||
|
drop table if exists t1;
|
||||||
|
drop table if exists t2;
|
||||||
|
drop table if exists t3;
|
||||||
|
drop function if exists f2;
|
||||||
|
drop procedure if exists bug12713_call;
|
||||||
|
drop procedure if exists bug12713_dump_spvars;
|
||||||
|
drop procedure if exists dummy;
|
||||||
|
create table t1 (a int);
|
||||||
|
create table t2 (a int unique);
|
||||||
|
create table t3 (a int);
|
||||||
|
set sql_mode=default;
|
||||||
|
insert into t1 (a) values (1), (2);
|
||||||
|
insert into t3 (a) values (1), (2);
|
||||||
|
create function f2(x int) returns int
|
||||||
|
begin
|
||||||
|
insert into t2 (a) values (x);
|
||||||
|
insert into t2 (a) values (x);
|
||||||
|
return x;
|
||||||
|
end|
|
||||||
|
set autocommit=0;
|
||||||
|
flush status;
|
||||||
|
insert into t2 (a) values (1001);
|
||||||
|
insert into t1 (a) values (f2(1));
|
||||||
|
ERROR 23000: Duplicate entry '1' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1001
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
insert into t2 (a) values (1002);
|
||||||
|
insert into t3 (a) select f2(2) from t1;
|
||||||
|
ERROR 23000: Duplicate entry '2' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1002
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
insert into t2 (a) values (1003);
|
||||||
|
update t1 set a= a + f2(3);
|
||||||
|
ERROR 23000: Duplicate entry '3' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1003
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
insert into t2 (a) values (1004);
|
||||||
|
update t1, t3 set t1.a = 0, t3.a = 0 where (f2(4) = 4) and (t1.a = t3.a);
|
||||||
|
ERROR 23000: Duplicate entry '4' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1004
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
insert into t2 (a) values (1005);
|
||||||
|
delete from t1 where (a = f2(5));
|
||||||
|
ERROR 23000: Duplicate entry '5' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1005
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
insert into t2 (a) values (1006);
|
||||||
|
delete from t1, t3 using t1, t3 where (f2(6) = 6) ;
|
||||||
|
ERROR 23000: Duplicate entry '6' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1006
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
insert into t2 (a) values (1007);
|
||||||
|
replace t1 values (f2(7));
|
||||||
|
ERROR 23000: Duplicate entry '7' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1007
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
insert into t2 (a) values (1008);
|
||||||
|
replace into t3 (a) select f2(8) from t1;
|
||||||
|
ERROR 23000: Duplicate entry '8' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1008
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
insert into t2 (a) values (1009);
|
||||||
|
select f2(9) from t1 ;
|
||||||
|
ERROR 23000: Duplicate entry '9' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1009
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
insert into t2 (a) values (1010);
|
||||||
|
show databases where (f2(10) = 10);
|
||||||
|
ERROR 23000: Duplicate entry '10' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1010
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
insert into t2 (a) values (1011);
|
||||||
|
show tables where (f2(11) = 11);
|
||||||
|
ERROR 23000: Duplicate entry '11' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1011
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
insert into t2 (a) values (1012);
|
||||||
|
show triggers where (f2(12) = 12);
|
||||||
|
ERROR 23000: Duplicate entry '12' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1012
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
insert into t2 (a) values (1013);
|
||||||
|
show table status where (f2(13) = 13);
|
||||||
|
ERROR 23000: Duplicate entry '13' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1013
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
insert into t2 (a) values (1014);
|
||||||
|
show open tables where (f2(14) = 14);
|
||||||
|
ERROR 23000: Duplicate entry '14' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1014
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
insert into t2 (a) values (1015);
|
||||||
|
show columns in mysql.proc where (f2(15) = 15);
|
||||||
|
ERROR 23000: Duplicate entry '15' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1015
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
insert into t2 (a) values (1016);
|
||||||
|
show status where (f2(16) = 16);
|
||||||
|
ERROR 23000: Duplicate entry '16' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1016
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
insert into t2 (a) values (1017);
|
||||||
|
show variables where (f2(17) = 17);
|
||||||
|
ERROR 23000: Duplicate entry '17' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1017
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
insert into t2 (a) values (1018);
|
||||||
|
show charset where (f2(18) = 18);
|
||||||
|
ERROR 23000: Duplicate entry '18' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1018
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
insert into t2 (a) values (1019);
|
||||||
|
show collation where (f2(19) = 19);
|
||||||
|
ERROR 23000: Duplicate entry '19' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1019
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
# We need at least one procedure to make sure the WHERE clause is
|
||||||
|
# evaluated
|
||||||
|
create procedure dummy() begin end;
|
||||||
|
insert into t2 (a) values (1020);
|
||||||
|
show procedure status where (f2(20) = 20);
|
||||||
|
ERROR 23000: Duplicate entry '20' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1020
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
drop procedure dummy;
|
||||||
|
insert into t2 (a) values (1021);
|
||||||
|
show function status where (f2(21) = 21);
|
||||||
|
ERROR 23000: Duplicate entry '21' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1021
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
insert into t2 (a) values (1022);
|
||||||
|
prepare stmt from "insert into t1 (a) values (f2(22))";
|
||||||
|
execute stmt;
|
||||||
|
ERROR 23000: Duplicate entry '22' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1022
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
insert into t2 (a) values (1023);
|
||||||
|
do (f2(23));
|
||||||
|
Warnings:
|
||||||
|
Error 1062 Duplicate entry '23' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1023
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
create procedure bug12713_call ()
|
||||||
|
begin
|
||||||
|
insert into t2 (a) values (24);
|
||||||
|
insert into t2 (a) values (24);
|
||||||
|
end|
|
||||||
|
insert into t2 (a) values (1024);
|
||||||
|
call bug12713_call();
|
||||||
|
ERROR 23000: Duplicate entry '24' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
24
|
||||||
|
1024
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
=======================================================================
|
||||||
|
Testing select_to_file
|
||||||
|
=======================================================================
|
||||||
|
insert into t2 (a) values (1025);
|
||||||
|
select f2(25) into outfile "../tmp/dml.out" from t1;
|
||||||
|
ERROR 23000: Duplicate entry '25' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1025
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
insert into t2 (a) values (1026);
|
||||||
|
load data infile "../std_data_ln/words.dat" into table t1 (a) set a:=f2(26);
|
||||||
|
ERROR 23000: Duplicate entry '26' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1026
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
=======================================================================
|
||||||
|
Testing select_dumpvar
|
||||||
|
=======================================================================
|
||||||
|
insert into t2 (a) values (1027);
|
||||||
|
select f2(27) into @foo;
|
||||||
|
ERROR 23000: Duplicate entry '27' for key 'a'
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
1027
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
=======================================================================
|
||||||
|
Testing Select_fetch_into_spvars
|
||||||
|
=======================================================================
|
||||||
|
create procedure bug12713_dump_spvars ()
|
||||||
|
begin
|
||||||
|
declare foo int;
|
||||||
|
declare continue handler for sqlexception
|
||||||
|
begin
|
||||||
|
select "Exception trapped";
|
||||||
|
end;
|
||||||
|
select f2(28) into foo;
|
||||||
|
select * from t2;
|
||||||
|
end|
|
||||||
|
insert into t2 (a) values (1028);
|
||||||
|
call bug12713_dump_spvars ();
|
||||||
|
Exception trapped
|
||||||
|
Exception trapped
|
||||||
|
a
|
||||||
|
1028
|
||||||
|
rollback;
|
||||||
|
select * from t2;
|
||||||
|
a
|
||||||
|
=======================================================================
|
||||||
|
Cleanup
|
||||||
|
=======================================================================
|
||||||
|
set autocommit=default;
|
||||||
|
drop table t1;
|
||||||
|
drop table t2;
|
||||||
|
drop table t3;
|
||||||
|
drop function f2;
|
||||||
|
drop procedure bug12713_call;
|
||||||
|
drop procedure bug12713_dump_spvars;
|
||||||
|
#
|
||||||
|
# Bug#12713 Error in a stored function called from a SELECT doesn't
|
||||||
|
# cause ROLLBACK of statem
|
||||||
|
#
|
||||||
|
# Verify that two-phase commit is not issued for read-only
|
||||||
|
# transactions.
|
||||||
|
#
|
||||||
|
# Verify that two-phase commit is issued for read-write transactions,
|
||||||
|
# even if the change is done inside a stored function called from
|
||||||
|
# SELECT or SHOW statement.
|
||||||
|
#
|
||||||
|
set autocommit=0;
|
||||||
|
drop table if exists t1;
|
||||||
|
drop table if exists t2;
|
||||||
|
drop function if exists f1;
|
||||||
|
drop procedure if exists p_verify_status_increment;
|
||||||
|
set sql_mode=no_engine_substitution;
|
||||||
|
create table t1 (a int unique);
|
||||||
|
create table t2 (a int) engine=myisam;
|
||||||
|
set sql_mode=default;
|
||||||
|
#
|
||||||
|
# An auxiliary procedure to track Handler_prepare and Handler_commit
|
||||||
|
# statistics.
|
||||||
|
#
|
||||||
|
create procedure
|
||||||
|
p_verify_status_increment(commit_inc_mixed int, prepare_inc_mixed int,
|
||||||
|
commit_inc_row int, prepare_inc_row int)
|
||||||
|
begin
|
||||||
|
declare commit_inc int;
|
||||||
|
declare prepare_inc int;
|
||||||
|
declare old_commit_count int default ifnull(@commit_count, 0);
|
||||||
|
declare old_prepare_count int default ifnull(@prepare_count, 0);
|
||||||
|
declare c_res int;
|
||||||
|
# Use a cursor to have just one access to I_S instead of 2, it is very slow
|
||||||
|
# and amounts for over 90% of test CPU time
|
||||||
|
declare c cursor for
|
||||||
|
select variable_value
|
||||||
|
from information_schema.session_status
|
||||||
|
where variable_name='Handler_commit' or variable_name='Handler_prepare'
|
||||||
|
order by variable_name;
|
||||||
|
if @@global.binlog_format = 'ROW' then
|
||||||
|
set commit_inc= commit_inc_row;
|
||||||
|
set prepare_inc= prepare_inc_row;
|
||||||
|
else
|
||||||
|
set commit_inc= commit_inc_mixed;
|
||||||
|
set prepare_inc= prepare_inc_mixed;
|
||||||
|
end if;
|
||||||
|
open c;
|
||||||
|
fetch c into c_res;
|
||||||
|
set @commit_count=c_res;
|
||||||
|
fetch c into c_res;
|
||||||
|
set @prepare_count=c_res;
|
||||||
|
close c;
|
||||||
|
if old_commit_count + commit_inc <> @commit_count then
|
||||||
|
select concat("Expected commit increment: ", commit_inc,
|
||||||
|
" actual: ", @commit_count - old_commit_count)
|
||||||
|
as 'ERROR';
|
||||||
|
elseif old_prepare_count + prepare_inc <> @prepare_count then
|
||||||
|
select concat("Expected prepare increment: ", prepare_inc,
|
||||||
|
" actual: ", @prepare_count - old_prepare_count)
|
||||||
|
as 'ERROR';
|
||||||
|
else
|
||||||
|
select '' as 'SUCCESS';
|
||||||
|
end if;
|
||||||
|
end|
|
||||||
|
# Reset Handler_commit and Handler_prepare counters
|
||||||
|
flush status;
|
||||||
|
#
|
||||||
|
# 1. Read-only statement: SELECT
|
||||||
|
#
|
||||||
|
select * from t1;
|
||||||
|
a
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 2. Read-write statement: INSERT, insert 1 row.
|
||||||
|
#
|
||||||
|
insert into t1 (a) values (1);
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 3. Read-write statement: UPDATE, update 1 row.
|
||||||
|
#
|
||||||
|
update t1 set a=2;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 4. Read-write statement: UPDATE, update 0 rows, 1 row matches WHERE
|
||||||
|
#
|
||||||
|
# Note the wrong Handler_prepare/Handler_commit count is due to
|
||||||
|
# Bug#29157 "UPDATE, changed rows incorrect" and
|
||||||
|
# Bug#Bug #33846 UPDATE word:Wrong 'Changed rows' if InnoDB, unique
|
||||||
|
# key and no rows qualify WHERE
|
||||||
|
#
|
||||||
|
update t1 set a=2;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 5. Read-write statement: UPDATE, update 0 rows, 0 rows match WHERE
|
||||||
|
#
|
||||||
|
# In mixed replication mode, there is a read-only transaction
|
||||||
|
# in InnoDB and also the statement is written to the binary log.
|
||||||
|
# So we have two commits but no 2pc, since the first engine's
|
||||||
|
# transaction is read-only.
|
||||||
|
# In the row level replication mode, we only have the read-only
|
||||||
|
# transaction in InnoDB and nothing is written to the binary log.
|
||||||
|
#
|
||||||
|
update t1 set a=3 where a=1;
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 6. Read-write statement: DELETE, delete 0 rows.
|
||||||
|
#
|
||||||
|
delete from t1 where a=1;
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 7. Read-write statement: DELETE, delete 1 row.
|
||||||
|
#
|
||||||
|
delete from t1 where a=2;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 8. Read-write statement: unqualified DELETE
|
||||||
|
#
|
||||||
|
# In statement or mixed replication mode, we call
|
||||||
|
# handler::ha_delete_all_rows() and write statement text
|
||||||
|
# to the binary log. This results in two read-write transactions.
|
||||||
|
# In row level replication mode, we do not call
|
||||||
|
# handler::ha_delete_all_rows(), but delete rows one by one.
|
||||||
|
# Since there are no rows, nothing is written to the binary log.
|
||||||
|
# Thus we have just one read-only transaction in InnoDB.
|
||||||
|
delete from t1;
|
||||||
|
call p_verify_status_increment(2, 2, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 9. Read-write statement: REPLACE, change 1 row.
|
||||||
|
#
|
||||||
|
replace t1 set a=1;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 10. Read-write statement: REPLACE, change 0 rows.
|
||||||
|
#
|
||||||
|
replace t1 set a=1;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 11. Read-write statement: IODKU, change 1 row.
|
||||||
|
#
|
||||||
|
insert t1 set a=1 on duplicate key update a=a+1;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
select * from t1;
|
||||||
|
a
|
||||||
|
2
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 12. Read-write statement: IODKU, change 0 rows.
|
||||||
|
#
|
||||||
|
insert t1 set a=2 on duplicate key update a=2;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 13. Read-write statement: INSERT IGNORE, change 0 rows.
|
||||||
|
#
|
||||||
|
insert ignore t1 set a=2;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 14. Read-write statement: INSERT IGNORE, change 1 row.
|
||||||
|
#
|
||||||
|
insert ignore t1 set a=1;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 15. Read-write statement: UPDATE IGNORE, change 0 rows.
|
||||||
|
#
|
||||||
|
update ignore t1 set a=2 where a=1;
|
||||||
|
call p_verify_status_increment(2, 2, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
#
|
||||||
|
# Create a stored function that modifies a
|
||||||
|
# non-transactional table. Demonstrate that changes in
|
||||||
|
# non-transactional tables do not affect the two phase commit
|
||||||
|
# algorithm.
|
||||||
|
#
|
||||||
|
create function f1() returns int
|
||||||
|
begin
|
||||||
|
insert t2 set a=2;
|
||||||
|
return 2;
|
||||||
|
end|
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 16. A function changes non-trans-table.
|
||||||
|
#
|
||||||
|
select f1();
|
||||||
|
f1()
|
||||||
|
2
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 17. Read-only statement, a function changes non-trans-table.
|
||||||
|
#
|
||||||
|
select f1() from t1;
|
||||||
|
f1()
|
||||||
|
2
|
||||||
|
2
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 18. Read-write statement: UPDATE, change 0 (transactional) rows.
|
||||||
|
#
|
||||||
|
select count(*) from t2;
|
||||||
|
count(*)
|
||||||
|
3
|
||||||
|
update t1 set a=2 where a=f1()+10;
|
||||||
|
select count(*) from t2;
|
||||||
|
count(*)
|
||||||
|
5
|
||||||
|
call p_verify_status_increment(2, 0, 2, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 0, 2, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
#
|
||||||
|
# Replace the non-transactional table with a temporary
|
||||||
|
# transactional table. Demonstrate that a change to a temporary
|
||||||
|
# transactional table does not provoke 2-phase commit, although
|
||||||
|
# does trigger a commit and a binlog write (in statement mode).
|
||||||
|
#
|
||||||
|
drop table t2;
|
||||||
|
set sql_mode=no_engine_substitution;
|
||||||
|
create temporary table t2 (a int);
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
set sql_mode=default;
|
||||||
|
# 19. A function changes temp-trans-table.
|
||||||
|
#
|
||||||
|
select f1();
|
||||||
|
f1()
|
||||||
|
2
|
||||||
|
# Two commits because a binary log record is written
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 20. Read-only statement, a function changes non-trans-table.
|
||||||
|
#
|
||||||
|
select f1() from t1;
|
||||||
|
f1()
|
||||||
|
2
|
||||||
|
2
|
||||||
|
# Two commits because a binary log record is written
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 21. Read-write statement: UPDATE, change 0 (transactional) rows.
|
||||||
|
#
|
||||||
|
update t1 set a=2 where a=f1()+10;
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 22. DDL: ALTER TEMPORARY TABLE, should not cause a 2pc
|
||||||
|
#
|
||||||
|
alter table t2 add column b int default 5;
|
||||||
|
# A commit is done internally by ALTER.
|
||||||
|
call p_verify_status_increment(2, 0, 2, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
# There is nothing left to commit
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 23. DDL: RENAME TEMPORARY TABLE, does not start a transaction
|
||||||
|
|
||||||
|
# No test because of Bug#8729 "rename table fails on temporary table"
|
||||||
|
# 24. DDL: TRUNCATE TEMPORARY TABLE, does not start a transaction
|
||||||
|
|
||||||
|
truncate table t2;
|
||||||
|
call p_verify_status_increment(2, 0, 2, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
# There is nothing left to commit
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 25. Read-write statement: unqualified DELETE
|
||||||
|
|
||||||
|
delete from t2;
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
# There is nothing left to commit
|
||||||
|
call p_verify_status_increment(2, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 25. DDL: DROP TEMPORARY TABLE, does not start a transaction
|
||||||
|
#
|
||||||
|
drop temporary table t2;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 26. Verify that SET AUTOCOMMIT issues an implicit commit
|
||||||
|
#
|
||||||
|
insert t1 set a=3;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
set autocommit=1;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
rollback;
|
||||||
|
select a from t1 where a=3;
|
||||||
|
a
|
||||||
|
3
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
delete from t1 where a=3;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
set autocommit=0;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
insert t1 set a=3;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# Sic: not actually changing the value of autocommit
|
||||||
|
set autocommit=0;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
rollback;
|
||||||
|
select a from t1 where a=3;
|
||||||
|
a
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 27. Savepoint management
|
||||||
|
#
|
||||||
|
insert t1 set a=3;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
savepoint a;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
insert t1 set a=4;
|
||||||
|
# Sic: a bug. Binlog did not register itself this time.
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
release savepoint a;
|
||||||
|
rollback;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
select a from t1 where a=3;
|
||||||
|
a
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 28. Read-write statement: DO
|
||||||
|
#
|
||||||
|
create table t2 (a int);
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
do (select f1() from t1 where a=2);
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 29. Read-write statement: MULTI-DELETE
|
||||||
|
#
|
||||||
|
delete t1, t2 from t1 join t2 on (t1.a=t2.a) where t1.a=2;
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(4, 4, 4, 4);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 30. Read-write statement: INSERT-SELECT, MULTI-UPDATE, REPLACE-SELECT
|
||||||
|
#
|
||||||
|
insert into t2 select a from t1;
|
||||||
|
commit;
|
||||||
|
replace into t2 select a from t1;
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(8, 8, 8, 8);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
update t1, t2 set t1.a=4, t2.a=8 where t1.a=t2.a and t1.a=1;
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(4, 4, 4, 4);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# 31. DDL: various DDL with transactional tables
|
||||||
|
#
|
||||||
|
# Sic: no table is created.
|
||||||
|
create table if not exists t2 (a int) select 6 union select 7;
|
||||||
|
Warnings:
|
||||||
|
Note 1050 Table 't2' already exists
|
||||||
|
# Sic: first commits the statement, and then the transaction.
|
||||||
|
call p_verify_status_increment(4, 4, 4, 4);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
create table t3 select a from t2;
|
||||||
|
call p_verify_status_increment(4, 4, 4, 4);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
alter table t3 add column (b int);
|
||||||
|
call p_verify_status_increment(2, 0, 2, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
alter table t3 rename t4;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
rename table t4 to t3;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
truncate table t3;
|
||||||
|
call p_verify_status_increment(2, 2, 2, 2);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
create view v1 as select * from t2;
|
||||||
|
call p_verify_status_increment(1, 0, 1, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
check table t1;
|
||||||
|
Table Op Msg_type Msg_text
|
||||||
|
test.t1 check status OK
|
||||||
|
call p_verify_status_increment(3, 0, 3, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
# Sic: after this bug is fixed, CHECK leaves no pending transaction
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
check table t1, t2, t3;
|
||||||
|
Table Op Msg_type Msg_text
|
||||||
|
test.t1 check status OK
|
||||||
|
test.t2 check status OK
|
||||||
|
test.t3 check status OK
|
||||||
|
call p_verify_status_increment(6, 0, 6, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
commit;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
drop view v1;
|
||||||
|
call p_verify_status_increment(0, 0, 0, 0);
|
||||||
|
SUCCESS
|
||||||
|
|
||||||
|
#
|
||||||
|
# Cleanup
|
||||||
|
#
|
||||||
|
drop table t1;
|
||||||
|
drop procedure p_verify_status_increment;
|
||||||
|
drop function f1;
|
@ -373,7 +373,7 @@ master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
|
|||||||
master-bin.000001 # Query # # use `test`; BEGIN
|
master-bin.000001 # Query # # use `test`; BEGIN
|
||||||
master-bin.000001 # Table_map # # table_id: # (test.t2)
|
master-bin.000001 # Table_map # # table_id: # (test.t2)
|
||||||
master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
|
master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
|
||||||
master-bin.000001 # Xid # # COMMIT /* XID */
|
master-bin.000001 # Query # # use `test`; COMMIT
|
||||||
master-bin.000001 # Query # # use `test`; DROP TABLE t2
|
master-bin.000001 # Query # # use `test`; DROP TABLE t2
|
||||||
master-bin.000001 # Table_map # # table_id: # (test.t1)
|
master-bin.000001 # Table_map # # table_id: # (test.t1)
|
||||||
master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
|
master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
|
||||||
@ -384,7 +384,6 @@ master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
|
|||||||
master-bin.000001 # Table_map # # table_id: # (test.t1)
|
master-bin.000001 # Table_map # # table_id: # (test.t1)
|
||||||
master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
|
master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
|
||||||
master-bin.000001 # Query # # use `test`; TRUNCATE table t2
|
master-bin.000001 # Query # # use `test`; TRUNCATE table t2
|
||||||
master-bin.000001 # Xid # # COMMIT /* XID */
|
|
||||||
master-bin.000001 # Table_map # # table_id: # (test.t1)
|
master-bin.000001 # Table_map # # table_id: # (test.t1)
|
||||||
master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
|
master-bin.000001 # Write_rows # # table_id: # flags: STMT_END_F
|
||||||
master-bin.000001 # Query # # use `test`; DROP TABLE `t1` /* generated by server */
|
master-bin.000001 # Query # # use `test`; DROP TABLE `t1` /* generated by server */
|
||||||
|
@ -244,7 +244,6 @@ master-bin.000001 # Query # # use `test`; insert into t2 values (20)
|
|||||||
master-bin.000001 # Query # # use `test`; drop table t1,t2
|
master-bin.000001 # Query # # use `test`; drop table t1,t2
|
||||||
master-bin.000001 # Query # # use `test`; create temporary table ti (a int) engine=innodb
|
master-bin.000001 # Query # # use `test`; create temporary table ti (a int) engine=innodb
|
||||||
master-bin.000001 # Query # # use `test`; insert into ti values(1)
|
master-bin.000001 # Query # # use `test`; insert into ti values(1)
|
||||||
master-bin.000001 # Xid # # COMMIT /* XID */
|
|
||||||
master-bin.000001 # Query # # use `test`; create temporary table t1 (a int) engine=myisam
|
master-bin.000001 # Query # # use `test`; create temporary table t1 (a int) engine=myisam
|
||||||
master-bin.000001 # Query # # use `test`; insert t1 values (1)
|
master-bin.000001 # Query # # use `test`; insert t1 values (1)
|
||||||
master-bin.000001 # Query # # use `test`; create table t0 (n int)
|
master-bin.000001 # Query # # use `test`; create table t0 (n int)
|
||||||
@ -349,11 +348,10 @@ master-bin.000001 # Query # # use `test`; INSERT INTO t1 values (7,7)
|
|||||||
master-bin.000001 # Query # # use `test`; INSERT INTO t1 values (8,8)
|
master-bin.000001 # Query # # use `test`; INSERT INTO t1 values (8,8)
|
||||||
master-bin.000001 # Query # # use `test`; INSERT INTO t1 values (9,9)
|
master-bin.000001 # Query # # use `test`; INSERT INTO t1 values (9,9)
|
||||||
master-bin.000001 # Query # # use `test`; TRUNCATE table t2
|
master-bin.000001 # Query # # use `test`; TRUNCATE table t2
|
||||||
master-bin.000001 # Xid # # COMMIT /* XID */
|
|
||||||
master-bin.000001 # Query # # use `test`; INSERT INTO t1 values (10,10)
|
master-bin.000001 # Query # # use `test`; INSERT INTO t1 values (10,10)
|
||||||
master-bin.000001 # Query # # use `test`; BEGIN
|
master-bin.000001 # Query # # use `test`; BEGIN
|
||||||
master-bin.000001 # Query # # use `test`; INSERT INTO t2 values (100,100)
|
master-bin.000001 # Query # # use `test`; INSERT INTO t2 values (100,100)
|
||||||
master-bin.000001 # Xid # # COMMIT /* XID */
|
master-bin.000001 # Query # # use `test`; COMMIT
|
||||||
master-bin.000001 # Query # # use `test`; DROP TABLE t1,t2
|
master-bin.000001 # Query # # use `test`; DROP TABLE t1,t2
|
||||||
reset master;
|
reset master;
|
||||||
create table t1 (a int) engine=innodb;
|
create table t1 (a int) engine=innodb;
|
||||||
|
@ -21,3 +21,5 @@ rpl_ndb_mix_innodb : Bug #32720 Test rpl_ndb_mix_innodb fails on SPARC a
|
|||||||
# the below testcase have been reworked to avoid the bug, test contains comment, keep bug open
|
# the below testcase have been reworked to avoid the bug, test contains comment, keep bug open
|
||||||
|
|
||||||
#rpl_ndb_dd_advance : Bug#25913 rpl_ndb_dd_advance fails randomly
|
#rpl_ndb_dd_advance : Bug#25913 rpl_ndb_dd_advance fails randomly
|
||||||
|
rpl_ndb_circular : Bug#33849 COMMIT event missing in cluster circular replication.
|
||||||
|
rpl_ndb_circular_simplex : Bug#33849 COMMIT event missing in cluster circular replication.
|
||||||
|
6
mysql-test/t/commit_1innodb.test
Normal file
6
mysql-test/t/commit_1innodb.test
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
-- source include/have_log_bin.inc
|
||||||
|
-- source include/have_innodb.inc
|
||||||
|
|
||||||
|
let $engine_type = InnoDB;
|
||||||
|
|
||||||
|
-- source include/commit.inc
|
@ -283,6 +283,7 @@ static void run_query(THD *thd, char *buf, char *end,
|
|||||||
thd_ndb->m_error_code,
|
thd_ndb->m_error_code,
|
||||||
(int) thd->is_error(), thd->is_slave_error);
|
(int) thd->is_error(), thd->is_slave_error);
|
||||||
}
|
}
|
||||||
|
close_thread_tables(thd);
|
||||||
/*
|
/*
|
||||||
XXX: this code is broken. mysql_parse()/mysql_reset_thd_for_next_command()
|
XXX: this code is broken. mysql_parse()/mysql_reset_thd_for_next_command()
|
||||||
can not be called from within a statement, and
|
can not be called from within a statement, and
|
||||||
|
615
sql/handler.cc
615
sql/handler.cc
@ -575,6 +575,295 @@ void ha_close_connection(THD* thd)
|
|||||||
/* ========================================================================
|
/* ========================================================================
|
||||||
======================= TRANSACTIONS ===================================*/
|
======================= TRANSACTIONS ===================================*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
Transaction handling in the server
|
||||||
|
==================================
|
||||||
|
|
||||||
|
In each client connection, MySQL maintains two transactional
|
||||||
|
states:
|
||||||
|
- a statement transaction,
|
||||||
|
- a standard, also called normal transaction.
|
||||||
|
|
||||||
|
Historical note
|
||||||
|
---------------
|
||||||
|
"Statement transaction" is a non-standard term that comes
|
||||||
|
from the times when MySQL supported BerkeleyDB storage engine.
|
||||||
|
|
||||||
|
First of all, it should be said that in BerkeleyDB auto-commit
|
||||||
|
mode auto-commits operations that are atomic to the storage
|
||||||
|
engine itself, such as a write of a record, and are too
|
||||||
|
high-granular to be atomic from the application perspective
|
||||||
|
(MySQL). One SQL statement could involve many BerkeleyDB
|
||||||
|
auto-committed operations and thus BerkeleyDB auto-commit was of
|
||||||
|
little use to MySQL.
|
||||||
|
|
||||||
|
Secondly, instead of SQL standard savepoints, BerkeleyDB
|
||||||
|
provided the concept of "nested transactions". In a nutshell,
|
||||||
|
transactions could be arbitrarily nested, but when the parent
|
||||||
|
transaction was committed or aborted, all its child (nested)
|
||||||
|
transactions were handled committed or aborted as well.
|
||||||
|
Commit of a nested transaction, in turn, made its changes
|
||||||
|
visible, but not durable: it destroyed the nested transaction,
|
||||||
|
all its changes would become available to the parent and
|
||||||
|
currently active nested transactions of this parent.
|
||||||
|
|
||||||
|
So the mechanism of nested transactions was employed to
|
||||||
|
provide "all or nothing" guarantee of SQL statements
|
||||||
|
required by the standard.
|
||||||
|
A nested transaction would be created at start of each SQL
|
||||||
|
statement, and destroyed (committed or aborted) at statement
|
||||||
|
end. Such nested transaction was internally referred to as
|
||||||
|
a "statement transaction" and gave birth to the term.
|
||||||
|
|
||||||
|
<Historical note ends>
|
||||||
|
|
||||||
|
Since then a statement transaction is started for each statement
|
||||||
|
that accesses transactional tables or uses the binary log. If
|
||||||
|
the statement succeeds, the statement transaction is committed.
|
||||||
|
If the statement fails, the transaction is rolled back. Commits
|
||||||
|
of statement transactions are not durable -- each such
|
||||||
|
transaction is nested in the normal transaction, and if the
|
||||||
|
normal transaction is rolled back, the effects of all enclosed
|
||||||
|
statement transactions are undone as well. Technically,
|
||||||
|
a statement transaction can be viewed as a savepoint which is
|
||||||
|
maintained automatically in order to make effects of one
|
||||||
|
statement atomic.
|
||||||
|
|
||||||
|
The normal transaction is started by the user and is ended
|
||||||
|
usually upon a user request as well. The normal transaction
|
||||||
|
encloses transactions of all statements issued between
|
||||||
|
its beginning and its end.
|
||||||
|
In autocommit mode, the normal transaction is equivalent
|
||||||
|
to the statement transaction.
|
||||||
|
|
||||||
|
Since MySQL supports PSEA (pluggable storage engine
|
||||||
|
architecture), more than one transactional engine can be
|
||||||
|
active at a time. Hence transactions, from the server
|
||||||
|
point of view, are always distributed. In particular,
|
||||||
|
transactional state is maintained independently for each
|
||||||
|
engine. In order to commit a transaction the two phase
|
||||||
|
commit protocol is employed.
|
||||||
|
|
||||||
|
Not all statements are executed in context of a transaction.
|
||||||
|
Administrative and status information statements do not modify
|
||||||
|
engine data, and thus do not start a statement transaction and
|
||||||
|
also have no effect on the normal transaction. Examples of such
|
||||||
|
statements are SHOW STATUS and RESET SLAVE.
|
||||||
|
|
||||||
|
Similarly DDL statements are not transactional,
|
||||||
|
and therefore a transaction is [almost] never started for a DDL
|
||||||
|
statement. The difference between a DDL statement and a purely
|
||||||
|
administrative statement though is that a DDL statement always
|
||||||
|
commits the current transaction before proceeding, if there is
|
||||||
|
any.
|
||||||
|
|
||||||
|
At last, SQL statements that work with non-transactional
|
||||||
|
engines also have no effect on the transaction state of the
|
||||||
|
connection. Even though they are written to the binary log,
|
||||||
|
and the binary log is, overall, transactional, the writes
|
||||||
|
are done in "write-through" mode, directly to the binlog
|
||||||
|
file, followed with a OS cache sync, in other words,
|
||||||
|
bypassing the binlog undo log (translog).
|
||||||
|
They do not commit the current normal transaction.
|
||||||
|
A failure of a statement that uses non-transactional tables
|
||||||
|
would cause a rollback of the statement transaction, but
|
||||||
|
in case there no non-transactional tables are used,
|
||||||
|
no statement transaction is started.
|
||||||
|
|
||||||
|
Data layout
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The server stores its transaction-related data in
|
||||||
|
thd->transaction. This structure has two members of type
|
||||||
|
THD_TRANS. These members correspond to the statement and
|
||||||
|
normal transactions respectively:
|
||||||
|
|
||||||
|
- thd->transaction.stmt contains a list of engines
|
||||||
|
that are participating in the given statement
|
||||||
|
- thd->transaction.all contains a list of engines that
|
||||||
|
have participated in any of the statement transactions started
|
||||||
|
within the context of the normal transaction.
|
||||||
|
Each element of the list contains a pointer to the storage
|
||||||
|
engine, engine-specific transactional data, and engine-specific
|
||||||
|
transaction flags.
|
||||||
|
|
||||||
|
In autocommit mode thd->transaction.all is empty.
|
||||||
|
Instead, data of thd->transaction.stmt is
|
||||||
|
used to commit/rollback the normal transaction.
|
||||||
|
|
||||||
|
The list of registered engines has a few important properties:
|
||||||
|
- no engine is registered in the list twice
|
||||||
|
- engines are present in the list a reverse temporal order --
|
||||||
|
new participants are always added to the beginning of the list.
|
||||||
|
|
||||||
|
Transaction life cycle
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
When a new connection is established, thd->transaction
|
||||||
|
members are initialized to an empty state.
|
||||||
|
If a statement uses any tables, all affected engines
|
||||||
|
are registered in the statement engine list. In
|
||||||
|
non-autocommit mode, the same engines are registered in
|
||||||
|
the normal transaction list.
|
||||||
|
At the end of the statement, the server issues a commit
|
||||||
|
or a roll back for all engines in the statement list.
|
||||||
|
At this point transaction flags of an engine, if any, are
|
||||||
|
propagated from the statement list to the list of the normal
|
||||||
|
transaction.
|
||||||
|
When commit/rollback is finished, the statement list is
|
||||||
|
cleared. It will be filled in again by the next statement,
|
||||||
|
and emptied again at the next statement's end.
|
||||||
|
|
||||||
|
The normal transaction is committed in a similar way
|
||||||
|
(by going over all engines in thd->transaction.all list)
|
||||||
|
but at different times:
|
||||||
|
- upon COMMIT SQL statement is issued by the user
|
||||||
|
- implicitly, by the server, at the beginning of a DDL statement
|
||||||
|
or SET AUTOCOMMIT={0|1} statement.
|
||||||
|
|
||||||
|
The normal transaction can be rolled back as well:
|
||||||
|
- if the user has requested so, by issuing ROLLBACK SQL
|
||||||
|
statement
|
||||||
|
- if one of the storage engines requested a rollback
|
||||||
|
by setting thd->transaction_rollback_request. This may
|
||||||
|
happen in case, e.g., when the transaction in the engine was
|
||||||
|
chosen a victim of the internal deadlock resolution algorithm
|
||||||
|
and rolled back internally. When such a situation happens, there
|
||||||
|
is little the server can do and the only option is to rollback
|
||||||
|
transactions in all other participating engines. In this case
|
||||||
|
the rollback is accompanied by an error sent to the user.
|
||||||
|
|
||||||
|
As follows from the use cases above, the normal transaction
|
||||||
|
is never committed when there is an outstanding statement
|
||||||
|
transaction. In most cases there is no conflict, since
|
||||||
|
commits of the normal transaction are issued by a stand-alone
|
||||||
|
administrative or DDL statement, thus no outstanding statement
|
||||||
|
transaction of the previous statement exists. Besides,
|
||||||
|
all statements that manipulate with the normal transaction
|
||||||
|
are prohibited in stored functions and triggers, therefore
|
||||||
|
no conflicting situation can occur in a sub-statement either.
|
||||||
|
The remaining rare cases when the server explicitly has
|
||||||
|
to commit the statement transaction prior to committing the normal
|
||||||
|
one cover error-handling scenarios (see for example
|
||||||
|
SQLCOM_LOCK_TABLES).
|
||||||
|
|
||||||
|
When committing a statement or a normal transaction, the server
|
||||||
|
either uses the two-phase commit protocol, or issues a commit
|
||||||
|
in each engine independently. The two-phase commit protocol
|
||||||
|
is used only if:
|
||||||
|
- all participating engines support two-phase commit (provide
|
||||||
|
handlerton::prepare PSEA API call) and
|
||||||
|
- transactions in at least two engines modify data (i.e. are
|
||||||
|
not read-only).
|
||||||
|
|
||||||
|
Note that the two phase commit is used for
|
||||||
|
statement transactions, even though they are not durable anyway.
|
||||||
|
This is done to ensure logical consistency of data in a multiple-
|
||||||
|
engine transaction.
|
||||||
|
For example, imagine that some day MySQL supports unique
|
||||||
|
constraint checks deferred till the end of statement. In such
|
||||||
|
case a commit in one of the engines may yield ER_DUP_KEY,
|
||||||
|
and MySQL should be able to gracefully abort statement
|
||||||
|
transactions of other participants.
|
||||||
|
|
||||||
|
After the normal transaction has been committed,
|
||||||
|
thd->transaction.all list is cleared.
|
||||||
|
|
||||||
|
When a connection is closed, the current normal transaction, if
|
||||||
|
any, is rolled back.
|
||||||
|
|
||||||
|
Roles and responsibilities
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
The server has no way to know that an engine participates in
|
||||||
|
the statement and a transaction has been started
|
||||||
|
in it unless the engine says so. Thus, in order to be
|
||||||
|
a part of a transaction, the engine must "register" itself.
|
||||||
|
This is done by invoking trans_register_ha() server call.
|
||||||
|
Normally the engine registers itself whenever handler::external_lock()
|
||||||
|
is called. trans_register_ha() can be invoked many times: if
|
||||||
|
an engine is already registered, the call does nothing.
|
||||||
|
In case autocommit is not set, the engine must register itself
|
||||||
|
twice -- both in the statement list and in the normal transaction
|
||||||
|
list.
|
||||||
|
In which list to register is a parameter of trans_register_ha().
|
||||||
|
|
||||||
|
Note, that although the registration interface in itself is
|
||||||
|
fairly clear, the current usage practice often leads to undesired
|
||||||
|
effects. E.g. since a call to trans_register_ha() in most engines
|
||||||
|
is embedded into implementation of handler::external_lock(), some
|
||||||
|
DDL statements start a transaction (at least from the server
|
||||||
|
point of view) even though they are not expected to. E.g.
|
||||||
|
CREATE TABLE does not start a transaction, since
|
||||||
|
handler::external_lock() is never called during CREATE TABLE. But
|
||||||
|
CREATE TABLE ... SELECT does, since handler::external_lock() is
|
||||||
|
called for the table that is being selected from. This has no
|
||||||
|
practical effects currently, but must be kept in mind
|
||||||
|
nevertheless.
|
||||||
|
|
||||||
|
Once an engine is registered, the server will do the rest
|
||||||
|
of the work.
|
||||||
|
|
||||||
|
During statement execution, whenever any of data-modifying
|
||||||
|
PSEA API methods is used, e.g. handler::write_row() or
|
||||||
|
handler::update_row(), the read-write flag is raised in the
|
||||||
|
statement transaction for the involved engine.
|
||||||
|
Currently All PSEA calls are "traced", and the data can not be
|
||||||
|
changed in a way other than issuing a PSEA call. Important:
|
||||||
|
unless this invariant is preserved the server will not know that
|
||||||
|
a transaction in a given engine is read-write and will not
|
||||||
|
involve the two-phase commit protocol!
|
||||||
|
|
||||||
|
At the end of a statement, server call
|
||||||
|
ha_autocommit_or_rollback() is invoked. This call in turn
|
||||||
|
invokes handlerton::prepare() for every involved engine.
|
||||||
|
Prepare is followed by a call to handlerton::commit_one_phase()
|
||||||
|
If a one-phase commit will suffice, handlerton::prepare() is not
|
||||||
|
invoked and the server only calls handlerton::commit_one_phase().
|
||||||
|
At statement commit, the statement-related read-write engine
|
||||||
|
flag is propagated to the corresponding flag in the normal
|
||||||
|
transaction. When the commit is complete, the list of registered
|
||||||
|
engines is cleared.
|
||||||
|
|
||||||
|
Rollback is handled in a similar fashion.
|
||||||
|
|
||||||
|
Additional notes on DDL and the normal transaction.
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
DDLs and operations with non-transactional engines
|
||||||
|
do not "register" in thd->transaction lists, and thus do not
|
||||||
|
modify the transaction state. Besides, each DDL in
|
||||||
|
MySQL is prefixed with an implicit normal transaction commit
|
||||||
|
(a call to end_active_trans()), and thus leaves nothing
|
||||||
|
to modify.
|
||||||
|
However, as it has been pointed out with CREATE TABLE .. SELECT,
|
||||||
|
some DDL statements can start a *new* transaction.
|
||||||
|
|
||||||
|
Behaviour of the server in this case is currently badly
|
||||||
|
defined.
|
||||||
|
DDL statements use a form of "semantic" logging
|
||||||
|
to maintain atomicity: if CREATE TABLE .. SELECT failed,
|
||||||
|
the newly created table is deleted.
|
||||||
|
In addition, some DDL statements issue interim transaction
|
||||||
|
commits: e.g. ALTER TABLE issues a commit after data is copied
|
||||||
|
from the original table to the internal temporary table. Other
|
||||||
|
statements, e.g. CREATE TABLE ... SELECT do not always commit
|
||||||
|
after itself.
|
||||||
|
And finally there is a group of DDL statements such as
|
||||||
|
RENAME/DROP TABLE that doesn't start a new transaction
|
||||||
|
and doesn't commit.
|
||||||
|
|
||||||
|
This diversity makes it hard to say what will happen if
|
||||||
|
by chance a stored function is invoked during a DDL --
|
||||||
|
whether any modifications it makes will be committed or not
|
||||||
|
is not clear. Fortunately, SQL grammar of few DDLs allows
|
||||||
|
invocation of a stored function.
|
||||||
|
|
||||||
|
A consistent behaviour is perhaps to always commit the normal
|
||||||
|
transaction after all DDLs, just like the statement transaction
|
||||||
|
is always committed at the end of all statements.
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Register a storage engine for a transaction.
|
Register a storage engine for a transaction.
|
||||||
|
|
||||||
@ -592,7 +881,7 @@ void ha_close_connection(THD* thd)
|
|||||||
void trans_register_ha(THD *thd, bool all, handlerton *ht_arg)
|
void trans_register_ha(THD *thd, bool all, handlerton *ht_arg)
|
||||||
{
|
{
|
||||||
THD_TRANS *trans;
|
THD_TRANS *trans;
|
||||||
handlerton **ht;
|
Ha_trx_info *ha_info;
|
||||||
DBUG_ENTER("trans_register_ha");
|
DBUG_ENTER("trans_register_ha");
|
||||||
DBUG_PRINT("enter",("%s", all ? "all" : "stmt"));
|
DBUG_PRINT("enter",("%s", all ? "all" : "stmt"));
|
||||||
|
|
||||||
@ -604,12 +893,13 @@ void trans_register_ha(THD *thd, bool all, handlerton *ht_arg)
|
|||||||
else
|
else
|
||||||
trans= &thd->transaction.stmt;
|
trans= &thd->transaction.stmt;
|
||||||
|
|
||||||
for (ht=trans->ht; *ht; ht++)
|
ha_info= thd->ha_data[ht_arg->slot].ha_info + static_cast<unsigned>(all);
|
||||||
if (*ht == ht_arg)
|
|
||||||
DBUG_VOID_RETURN; /* already registered, return */
|
if (ha_info->is_started())
|
||||||
|
DBUG_VOID_RETURN; /* already registered, return */
|
||||||
|
|
||||||
|
ha_info->register_ha(trans, ht_arg);
|
||||||
|
|
||||||
trans->ht[trans->nht++]=ht_arg;
|
|
||||||
DBUG_ASSERT(*ht == ht_arg);
|
|
||||||
trans->no_2pc|=(ht_arg->prepare==0);
|
trans->no_2pc|=(ht_arg->prepare==0);
|
||||||
if (thd->transaction.xid_state.xid.is_null())
|
if (thd->transaction.xid_state.xid.is_null())
|
||||||
thd->transaction.xid_state.xid.set(thd->query_id);
|
thd->transaction.xid_state.xid.set(thd->query_id);
|
||||||
@ -626,18 +916,19 @@ int ha_prepare(THD *thd)
|
|||||||
{
|
{
|
||||||
int error=0, all=1;
|
int error=0, all=1;
|
||||||
THD_TRANS *trans=all ? &thd->transaction.all : &thd->transaction.stmt;
|
THD_TRANS *trans=all ? &thd->transaction.all : &thd->transaction.stmt;
|
||||||
handlerton **ht=trans->ht;
|
Ha_trx_info *ha_info= trans->ha_list;
|
||||||
DBUG_ENTER("ha_prepare");
|
DBUG_ENTER("ha_prepare");
|
||||||
#ifdef USING_TRANSACTIONS
|
#ifdef USING_TRANSACTIONS
|
||||||
if (trans->nht)
|
if (ha_info)
|
||||||
{
|
{
|
||||||
for (; *ht; ht++)
|
for (; ha_info; ha_info= ha_info->next())
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
|
handlerton *ht= ha_info->ht();
|
||||||
status_var_increment(thd->status_var.ha_prepare_count);
|
status_var_increment(thd->status_var.ha_prepare_count);
|
||||||
if ((*ht)->prepare)
|
if (ht->prepare)
|
||||||
{
|
{
|
||||||
if ((err= (*(*ht)->prepare)(*ht, thd, all)))
|
if ((err= ht->prepare(ht, thd, all)))
|
||||||
{
|
{
|
||||||
my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
|
my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
|
||||||
ha_rollback_trans(thd, all);
|
ha_rollback_trans(thd, all);
|
||||||
@ -649,7 +940,7 @@ int ha_prepare(THD *thd)
|
|||||||
{
|
{
|
||||||
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
|
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
|
||||||
ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA),
|
ER_ILLEGAL_HA, ER(ER_ILLEGAL_HA),
|
||||||
ha_resolve_storage_engine_name(*ht));
|
ha_resolve_storage_engine_name(ht));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -657,6 +948,62 @@ int ha_prepare(THD *thd)
|
|||||||
DBUG_RETURN(error);
|
DBUG_RETURN(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Check if we can skip the two-phase commit.
|
||||||
|
|
||||||
|
A helper function to evaluate if two-phase commit is mandatory.
|
||||||
|
As a side effect, propagates the read-only/read-write flags
|
||||||
|
of the statement transaction to its enclosing normal transaction.
|
||||||
|
|
||||||
|
@retval TRUE we must run a two-phase commit. Returned
|
||||||
|
if we have at least two engines with read-write changes.
|
||||||
|
@retval FALSE Don't need two-phase commit. Even if we have two
|
||||||
|
transactional engines, we can run two independent
|
||||||
|
commits if changes in one of the engines are read-only.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static
|
||||||
|
bool
|
||||||
|
ha_check_and_coalesce_trx_read_only(THD *thd, Ha_trx_info *ha_list,
|
||||||
|
bool all)
|
||||||
|
{
|
||||||
|
/* The number of storage engines that have actual changes. */
|
||||||
|
unsigned rw_ha_count= 0;
|
||||||
|
Ha_trx_info *ha_info;
|
||||||
|
|
||||||
|
for (ha_info= ha_list; ha_info; ha_info= ha_info->next())
|
||||||
|
{
|
||||||
|
if (ha_info->is_trx_read_write())
|
||||||
|
++rw_ha_count;
|
||||||
|
|
||||||
|
if (! all)
|
||||||
|
{
|
||||||
|
Ha_trx_info *ha_info_all= &thd->ha_data[ha_info->ht()->slot].ha_info[1];
|
||||||
|
DBUG_ASSERT(ha_info != ha_info_all);
|
||||||
|
/*
|
||||||
|
Merge read-only/read-write information about statement
|
||||||
|
transaction to its enclosing normal transaction. Do this
|
||||||
|
only if in a real transaction -- that is, if we know
|
||||||
|
that ha_info_all is registered in thd->transaction.all.
|
||||||
|
Since otherwise we only clutter the normal transaction flags.
|
||||||
|
*/
|
||||||
|
if (ha_info_all->is_started()) /* FALSE if autocommit. */
|
||||||
|
ha_info_all->coalesce_trx_with(ha_info);
|
||||||
|
}
|
||||||
|
else if (rw_ha_count > 1)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
It is a normal transaction, so we don't need to merge read/write
|
||||||
|
information up, and the need for two-phase commit has been
|
||||||
|
already established. Break the loop prematurely.
|
||||||
|
*/
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rw_ha_count > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@retval
|
@retval
|
||||||
0 ok
|
0 ok
|
||||||
@ -674,12 +1021,25 @@ int ha_prepare(THD *thd)
|
|||||||
int ha_commit_trans(THD *thd, bool all)
|
int ha_commit_trans(THD *thd, bool all)
|
||||||
{
|
{
|
||||||
int error= 0, cookie= 0;
|
int error= 0, cookie= 0;
|
||||||
|
/*
|
||||||
|
'all' means that this is either an explicit commit issued by
|
||||||
|
user, or an implicit commit issued by a DDL.
|
||||||
|
*/
|
||||||
THD_TRANS *trans= all ? &thd->transaction.all : &thd->transaction.stmt;
|
THD_TRANS *trans= all ? &thd->transaction.all : &thd->transaction.stmt;
|
||||||
bool is_real_trans= all || thd->transaction.all.nht == 0;
|
bool is_real_trans= all || thd->transaction.all.ha_list == 0;
|
||||||
handlerton **ht= trans->ht;
|
Ha_trx_info *ha_info= trans->ha_list;
|
||||||
my_xid xid= thd->transaction.xid_state.xid.get_my_xid();
|
my_xid xid= thd->transaction.xid_state.xid.get_my_xid();
|
||||||
DBUG_ENTER("ha_commit_trans");
|
DBUG_ENTER("ha_commit_trans");
|
||||||
|
|
||||||
|
/*
|
||||||
|
We must not commit the normal transaction if a statement
|
||||||
|
transaction is pending. Otherwise statement transaction
|
||||||
|
flags will not get propagated to its normal transaction's
|
||||||
|
counterpart.
|
||||||
|
*/
|
||||||
|
DBUG_ASSERT(thd->transaction.stmt.ha_list == NULL ||
|
||||||
|
trans == &thd->transaction.stmt);
|
||||||
|
|
||||||
if (thd->in_sub_stmt)
|
if (thd->in_sub_stmt)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@ -701,8 +1061,10 @@ int ha_commit_trans(THD *thd, bool all)
|
|||||||
DBUG_RETURN(2);
|
DBUG_RETURN(2);
|
||||||
}
|
}
|
||||||
#ifdef USING_TRANSACTIONS
|
#ifdef USING_TRANSACTIONS
|
||||||
if (trans->nht)
|
if (ha_info)
|
||||||
{
|
{
|
||||||
|
bool must_2pc;
|
||||||
|
|
||||||
if (is_real_trans && wait_if_global_read_lock(thd, 0, 0))
|
if (is_real_trans && wait_if_global_read_lock(thd, 0, 0))
|
||||||
{
|
{
|
||||||
ha_rollback_trans(thd, all);
|
ha_rollback_trans(thd, all);
|
||||||
@ -727,12 +1089,26 @@ int ha_commit_trans(THD *thd, bool all)
|
|||||||
if (is_real_trans) /* not a statement commit */
|
if (is_real_trans) /* not a statement commit */
|
||||||
thd->stmt_map.close_transient_cursors();
|
thd->stmt_map.close_transient_cursors();
|
||||||
|
|
||||||
if (!trans->no_2pc && trans->nht > 1)
|
must_2pc= ha_check_and_coalesce_trx_read_only(thd, ha_info, all);
|
||||||
|
|
||||||
|
if (!trans->no_2pc && must_2pc)
|
||||||
{
|
{
|
||||||
for (; *ht && !error; ht++)
|
for (; ha_info && !error; ha_info= ha_info->next())
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
if ((err= (*(*ht)->prepare)(*ht, thd, all)))
|
handlerton *ht= ha_info->ht();
|
||||||
|
/*
|
||||||
|
Do not call two-phase commit if this particular
|
||||||
|
transaction is read-only. This allows for simpler
|
||||||
|
implementation in engines that are always read-only.
|
||||||
|
*/
|
||||||
|
if (! ha_info->is_trx_read_write())
|
||||||
|
continue;
|
||||||
|
/*
|
||||||
|
Sic: we know that prepare() is not NULL since otherwise
|
||||||
|
trans->no_2pc would have been set.
|
||||||
|
*/
|
||||||
|
if ((err= ht->prepare(ht, thd, all)))
|
||||||
{
|
{
|
||||||
my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
|
my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
|
||||||
error= 1;
|
error= 1;
|
||||||
@ -770,24 +1146,26 @@ int ha_commit_one_phase(THD *thd, bool all)
|
|||||||
{
|
{
|
||||||
int error=0;
|
int error=0;
|
||||||
THD_TRANS *trans=all ? &thd->transaction.all : &thd->transaction.stmt;
|
THD_TRANS *trans=all ? &thd->transaction.all : &thd->transaction.stmt;
|
||||||
bool is_real_trans=all || thd->transaction.all.nht == 0;
|
bool is_real_trans=all || thd->transaction.all.ha_list == 0;
|
||||||
handlerton **ht=trans->ht;
|
Ha_trx_info *ha_info= trans->ha_list, *ha_info_next;
|
||||||
DBUG_ENTER("ha_commit_one_phase");
|
DBUG_ENTER("ha_commit_one_phase");
|
||||||
#ifdef USING_TRANSACTIONS
|
#ifdef USING_TRANSACTIONS
|
||||||
if (trans->nht)
|
if (ha_info)
|
||||||
{
|
{
|
||||||
for (ht=trans->ht; *ht; ht++)
|
for (; ha_info; ha_info= ha_info_next)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
if ((err= (*(*ht)->commit)(*ht, thd, all)))
|
handlerton *ht= ha_info->ht();
|
||||||
|
if ((err= ht->commit(ht, thd, all)))
|
||||||
{
|
{
|
||||||
my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
|
my_error(ER_ERROR_DURING_COMMIT, MYF(0), err);
|
||||||
error=1;
|
error=1;
|
||||||
}
|
}
|
||||||
status_var_increment(thd->status_var.ha_commit_count);
|
status_var_increment(thd->status_var.ha_commit_count);
|
||||||
*ht= 0;
|
ha_info_next= ha_info->next();
|
||||||
|
ha_info->reset(); /* keep it conveniently zero-filled */
|
||||||
}
|
}
|
||||||
trans->nht=0;
|
trans->ha_list= 0;
|
||||||
trans->no_2pc=0;
|
trans->no_2pc=0;
|
||||||
if (is_real_trans)
|
if (is_real_trans)
|
||||||
thd->transaction.xid_state.xid.null();
|
thd->transaction.xid_state.xid.null();
|
||||||
@ -810,8 +1188,17 @@ int ha_rollback_trans(THD *thd, bool all)
|
|||||||
{
|
{
|
||||||
int error=0;
|
int error=0;
|
||||||
THD_TRANS *trans=all ? &thd->transaction.all : &thd->transaction.stmt;
|
THD_TRANS *trans=all ? &thd->transaction.all : &thd->transaction.stmt;
|
||||||
bool is_real_trans=all || thd->transaction.all.nht == 0;
|
Ha_trx_info *ha_info= trans->ha_list, *ha_info_next;
|
||||||
|
bool is_real_trans=all || thd->transaction.all.ha_list == 0;
|
||||||
DBUG_ENTER("ha_rollback_trans");
|
DBUG_ENTER("ha_rollback_trans");
|
||||||
|
|
||||||
|
/*
|
||||||
|
We must not rollback the normal transaction if a statement
|
||||||
|
transaction is pending.
|
||||||
|
*/
|
||||||
|
DBUG_ASSERT(thd->transaction.stmt.ha_list == NULL ||
|
||||||
|
trans == &thd->transaction.stmt);
|
||||||
|
|
||||||
if (thd->in_sub_stmt)
|
if (thd->in_sub_stmt)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@ -826,24 +1213,26 @@ int ha_rollback_trans(THD *thd, bool all)
|
|||||||
DBUG_RETURN(1);
|
DBUG_RETURN(1);
|
||||||
}
|
}
|
||||||
#ifdef USING_TRANSACTIONS
|
#ifdef USING_TRANSACTIONS
|
||||||
if (trans->nht)
|
if (ha_info)
|
||||||
{
|
{
|
||||||
/* Close all cursors that can not survive ROLLBACK */
|
/* Close all cursors that can not survive ROLLBACK */
|
||||||
if (is_real_trans) /* not a statement commit */
|
if (is_real_trans) /* not a statement commit */
|
||||||
thd->stmt_map.close_transient_cursors();
|
thd->stmt_map.close_transient_cursors();
|
||||||
|
|
||||||
for (handlerton **ht=trans->ht; *ht; ht++)
|
for (; ha_info; ha_info= ha_info_next)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
if ((err= (*(*ht)->rollback)(*ht, thd, all)))
|
handlerton *ht= ha_info->ht();
|
||||||
|
if ((err= ht->rollback(ht, thd, all)))
|
||||||
{ // cannot happen
|
{ // cannot happen
|
||||||
my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
|
my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
|
||||||
error=1;
|
error=1;
|
||||||
}
|
}
|
||||||
status_var_increment(thd->status_var.ha_rollback_count);
|
status_var_increment(thd->status_var.ha_rollback_count);
|
||||||
*ht= 0;
|
ha_info_next= ha_info->next();
|
||||||
|
ha_info->reset(); /* keep it conveniently zero-filled */
|
||||||
}
|
}
|
||||||
trans->nht=0;
|
trans->ha_list= 0;
|
||||||
trans->no_2pc=0;
|
trans->no_2pc=0;
|
||||||
if (is_real_trans)
|
if (is_real_trans)
|
||||||
thd->transaction.xid_state.xid.null();
|
thd->transaction.xid_state.xid.null();
|
||||||
@ -889,17 +1278,19 @@ int ha_autocommit_or_rollback(THD *thd, int error)
|
|||||||
{
|
{
|
||||||
DBUG_ENTER("ha_autocommit_or_rollback");
|
DBUG_ENTER("ha_autocommit_or_rollback");
|
||||||
#ifdef USING_TRANSACTIONS
|
#ifdef USING_TRANSACTIONS
|
||||||
if (thd->transaction.stmt.nht)
|
if (thd->transaction.stmt.ha_list)
|
||||||
{
|
{
|
||||||
if (!error)
|
if (!error)
|
||||||
{
|
{
|
||||||
if (ha_commit_stmt(thd))
|
if (ha_commit_trans(thd, 0))
|
||||||
error=1;
|
error=1;
|
||||||
}
|
}
|
||||||
else if (thd->transaction_rollback_request && !thd->in_sub_stmt)
|
else
|
||||||
(void) ha_rollback(thd);
|
{
|
||||||
else
|
(void) ha_rollback_trans(thd, 0);
|
||||||
(void) ha_rollback_stmt(thd);
|
if (thd->transaction_rollback_request && !thd->in_sub_stmt)
|
||||||
|
(void) ha_rollback(thd);
|
||||||
|
}
|
||||||
|
|
||||||
thd->variables.tx_isolation=thd->session_tx_isolation;
|
thd->variables.tx_isolation=thd->session_tx_isolation;
|
||||||
}
|
}
|
||||||
@ -1246,43 +1637,49 @@ int ha_rollback_to_savepoint(THD *thd, SAVEPOINT *sv)
|
|||||||
int error=0;
|
int error=0;
|
||||||
THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction.stmt :
|
THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction.stmt :
|
||||||
&thd->transaction.all);
|
&thd->transaction.all);
|
||||||
handlerton **ht=trans->ht, **end_ht;
|
Ha_trx_info *ha_info, *ha_info_next;
|
||||||
|
|
||||||
DBUG_ENTER("ha_rollback_to_savepoint");
|
DBUG_ENTER("ha_rollback_to_savepoint");
|
||||||
|
|
||||||
trans->nht=sv->nht;
|
|
||||||
trans->no_2pc=0;
|
trans->no_2pc=0;
|
||||||
end_ht=ht+sv->nht;
|
|
||||||
/*
|
/*
|
||||||
rolling back to savepoint in all storage engines that were part of the
|
rolling back to savepoint in all storage engines that were part of the
|
||||||
transaction when the savepoint was set
|
transaction when the savepoint was set
|
||||||
*/
|
*/
|
||||||
for (; ht < end_ht; ht++)
|
for (ha_info= sv->ha_list; ha_info; ha_info= ha_info->next())
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
DBUG_ASSERT((*ht)->savepoint_set != 0);
|
handlerton *ht= ha_info->ht();
|
||||||
if ((err= (*(*ht)->savepoint_rollback)(*ht, thd, (uchar *)(sv+1)+(*ht)->savepoint_offset)))
|
DBUG_ASSERT(ht);
|
||||||
|
DBUG_ASSERT(ht->savepoint_set != 0);
|
||||||
|
if ((err= ht->savepoint_rollback(ht, thd,
|
||||||
|
(uchar *)(sv+1)+ht->savepoint_offset)))
|
||||||
{ // cannot happen
|
{ // cannot happen
|
||||||
my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
|
my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
|
||||||
error=1;
|
error=1;
|
||||||
}
|
}
|
||||||
status_var_increment(thd->status_var.ha_savepoint_rollback_count);
|
status_var_increment(thd->status_var.ha_savepoint_rollback_count);
|
||||||
trans->no_2pc|=(*ht)->prepare == 0;
|
trans->no_2pc|= ht->prepare == 0;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
rolling back the transaction in all storage engines that were not part of
|
rolling back the transaction in all storage engines that were not part of
|
||||||
the transaction when the savepoint was set
|
the transaction when the savepoint was set
|
||||||
*/
|
*/
|
||||||
for (; *ht ; ht++)
|
for (ha_info= trans->ha_list; ha_info != sv->ha_list;
|
||||||
|
ha_info= ha_info_next)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
if ((err= (*(*ht)->rollback)(*ht, thd, !thd->in_sub_stmt)))
|
handlerton *ht= ha_info->ht();
|
||||||
|
if ((err= ht->rollback(ht, thd, !thd->in_sub_stmt)))
|
||||||
{ // cannot happen
|
{ // cannot happen
|
||||||
my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
|
my_error(ER_ERROR_DURING_ROLLBACK, MYF(0), err);
|
||||||
error=1;
|
error=1;
|
||||||
}
|
}
|
||||||
status_var_increment(thd->status_var.ha_rollback_count);
|
status_var_increment(thd->status_var.ha_rollback_count);
|
||||||
*ht=0; // keep it conveniently zero-filled
|
ha_info_next= ha_info->next();
|
||||||
|
ha_info->reset(); /* keep it conveniently zero-filled */
|
||||||
}
|
}
|
||||||
|
trans->ha_list= sv->ha_list;
|
||||||
DBUG_RETURN(error);
|
DBUG_RETURN(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1297,26 +1694,32 @@ int ha_savepoint(THD *thd, SAVEPOINT *sv)
|
|||||||
int error=0;
|
int error=0;
|
||||||
THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction.stmt :
|
THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction.stmt :
|
||||||
&thd->transaction.all);
|
&thd->transaction.all);
|
||||||
handlerton **ht=trans->ht;
|
Ha_trx_info *ha_info= trans->ha_list;
|
||||||
DBUG_ENTER("ha_savepoint");
|
DBUG_ENTER("ha_savepoint");
|
||||||
#ifdef USING_TRANSACTIONS
|
#ifdef USING_TRANSACTIONS
|
||||||
for (; *ht; ht++)
|
for (; ha_info; ha_info= ha_info->next())
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
if (! (*ht)->savepoint_set)
|
handlerton *ht= ha_info->ht();
|
||||||
|
DBUG_ASSERT(ht);
|
||||||
|
if (! ht->savepoint_set)
|
||||||
{
|
{
|
||||||
my_error(ER_CHECK_NOT_IMPLEMENTED, MYF(0), "SAVEPOINT");
|
my_error(ER_CHECK_NOT_IMPLEMENTED, MYF(0), "SAVEPOINT");
|
||||||
error=1;
|
error=1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if ((err= (*(*ht)->savepoint_set)(*ht, thd, (uchar *)(sv+1)+(*ht)->savepoint_offset)))
|
if ((err= ht->savepoint_set(ht, thd, (uchar *)(sv+1)+ht->savepoint_offset)))
|
||||||
{ // cannot happen
|
{ // cannot happen
|
||||||
my_error(ER_GET_ERRNO, MYF(0), err);
|
my_error(ER_GET_ERRNO, MYF(0), err);
|
||||||
error=1;
|
error=1;
|
||||||
}
|
}
|
||||||
status_var_increment(thd->status_var.ha_savepoint_count);
|
status_var_increment(thd->status_var.ha_savepoint_count);
|
||||||
}
|
}
|
||||||
sv->nht=trans->nht;
|
/*
|
||||||
|
Remember the list of registered storage engines. All new
|
||||||
|
engines are prepended to the beginning of the list.
|
||||||
|
*/
|
||||||
|
sv->ha_list= trans->ha_list;
|
||||||
#endif /* USING_TRANSACTIONS */
|
#endif /* USING_TRANSACTIONS */
|
||||||
DBUG_RETURN(error);
|
DBUG_RETURN(error);
|
||||||
}
|
}
|
||||||
@ -1324,20 +1727,19 @@ int ha_savepoint(THD *thd, SAVEPOINT *sv)
|
|||||||
int ha_release_savepoint(THD *thd, SAVEPOINT *sv)
|
int ha_release_savepoint(THD *thd, SAVEPOINT *sv)
|
||||||
{
|
{
|
||||||
int error=0;
|
int error=0;
|
||||||
THD_TRANS *trans= (thd->in_sub_stmt ? &thd->transaction.stmt :
|
Ha_trx_info *ha_info= sv->ha_list;
|
||||||
&thd->transaction.all);
|
|
||||||
handlerton **ht=trans->ht, **end_ht;
|
|
||||||
DBUG_ENTER("ha_release_savepoint");
|
DBUG_ENTER("ha_release_savepoint");
|
||||||
|
|
||||||
end_ht=ht+sv->nht;
|
for (; ha_info; ha_info= ha_info->next())
|
||||||
for (; ht < end_ht; ht++)
|
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
if (!(*ht)->savepoint_release)
|
handlerton *ht= ha_info->ht();
|
||||||
|
/* Savepoint life time is enclosed into transaction life time. */
|
||||||
|
DBUG_ASSERT(ht);
|
||||||
|
if (!ht->savepoint_release)
|
||||||
continue;
|
continue;
|
||||||
if ((err= (*(*ht)->savepoint_release)(*ht, thd,
|
if ((err= ht->savepoint_release(ht, thd,
|
||||||
(uchar *)(sv+1)+
|
(uchar *)(sv+1) + ht->savepoint_offset)))
|
||||||
(*ht)->savepoint_offset)))
|
|
||||||
{ // cannot happen
|
{ // cannot happen
|
||||||
my_error(ER_GET_ERRNO, MYF(0), err);
|
my_error(ER_GET_ERRNO, MYF(0), err);
|
||||||
error=1;
|
error=1;
|
||||||
@ -2506,6 +2908,36 @@ int handler::ha_check(THD *thd, HA_CHECK_OPT *check_opt)
|
|||||||
return update_frm_version(table);
|
return update_frm_version(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
A helper function to mark a transaction read-write,
|
||||||
|
if it is started.
|
||||||
|
*/
|
||||||
|
|
||||||
|
inline
|
||||||
|
void
|
||||||
|
handler::mark_trx_read_write()
|
||||||
|
{
|
||||||
|
Ha_trx_info *ha_info= &ha_thd()->ha_data[ht->slot].ha_info[0];
|
||||||
|
/*
|
||||||
|
When a storage engine method is called, the transaction must
|
||||||
|
have been started, unless it's a DDL call, for which the
|
||||||
|
storage engine starts the transaction internally, and commits
|
||||||
|
it internally, without registering in the ha_list.
|
||||||
|
Unfortunately here we can't know know for sure if the engine
|
||||||
|
has registered the transaction or not, so we must check.
|
||||||
|
*/
|
||||||
|
if (ha_info->is_started())
|
||||||
|
{
|
||||||
|
DBUG_ASSERT(has_transactions());
|
||||||
|
/*
|
||||||
|
table_share can be NULL in ha_delete_table(). See implementation
|
||||||
|
of standalone function ha_delete_table() in sql_base.cc.
|
||||||
|
*/
|
||||||
|
if (table_share == NULL || table_share->tmp_table == NO_TMP_TABLE)
|
||||||
|
ha_info->set_trx_read_write();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Repair table: public interface.
|
Repair table: public interface.
|
||||||
@ -2516,6 +2948,9 @@ int handler::ha_check(THD *thd, HA_CHECK_OPT *check_opt)
|
|||||||
int handler::ha_repair(THD* thd, HA_CHECK_OPT* check_opt)
|
int handler::ha_repair(THD* thd, HA_CHECK_OPT* check_opt)
|
||||||
{
|
{
|
||||||
int result;
|
int result;
|
||||||
|
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
if ((result= repair(thd, check_opt)))
|
if ((result= repair(thd, check_opt)))
|
||||||
return result;
|
return result;
|
||||||
return update_frm_version(table);
|
return update_frm_version(table);
|
||||||
@ -2532,6 +2967,8 @@ int
|
|||||||
handler::ha_bulk_update_row(const uchar *old_data, uchar *new_data,
|
handler::ha_bulk_update_row(const uchar *old_data, uchar *new_data,
|
||||||
uint *dup_key_found)
|
uint *dup_key_found)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return bulk_update_row(old_data, new_data, dup_key_found);
|
return bulk_update_row(old_data, new_data, dup_key_found);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2545,6 +2982,8 @@ handler::ha_bulk_update_row(const uchar *old_data, uchar *new_data,
|
|||||||
int
|
int
|
||||||
handler::ha_delete_all_rows()
|
handler::ha_delete_all_rows()
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return delete_all_rows();
|
return delete_all_rows();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2558,6 +2997,8 @@ handler::ha_delete_all_rows()
|
|||||||
int
|
int
|
||||||
handler::ha_reset_auto_increment(ulonglong value)
|
handler::ha_reset_auto_increment(ulonglong value)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return reset_auto_increment(value);
|
return reset_auto_increment(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2571,6 +3012,8 @@ handler::ha_reset_auto_increment(ulonglong value)
|
|||||||
int
|
int
|
||||||
handler::ha_backup(THD* thd, HA_CHECK_OPT* check_opt)
|
handler::ha_backup(THD* thd, HA_CHECK_OPT* check_opt)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return backup(thd, check_opt);
|
return backup(thd, check_opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2584,6 +3027,8 @@ handler::ha_backup(THD* thd, HA_CHECK_OPT* check_opt)
|
|||||||
int
|
int
|
||||||
handler::ha_restore(THD* thd, HA_CHECK_OPT* check_opt)
|
handler::ha_restore(THD* thd, HA_CHECK_OPT* check_opt)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return restore(thd, check_opt);
|
return restore(thd, check_opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2597,6 +3042,8 @@ handler::ha_restore(THD* thd, HA_CHECK_OPT* check_opt)
|
|||||||
int
|
int
|
||||||
handler::ha_optimize(THD* thd, HA_CHECK_OPT* check_opt)
|
handler::ha_optimize(THD* thd, HA_CHECK_OPT* check_opt)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return optimize(thd, check_opt);
|
return optimize(thd, check_opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2610,6 +3057,8 @@ handler::ha_optimize(THD* thd, HA_CHECK_OPT* check_opt)
|
|||||||
int
|
int
|
||||||
handler::ha_analyze(THD* thd, HA_CHECK_OPT* check_opt)
|
handler::ha_analyze(THD* thd, HA_CHECK_OPT* check_opt)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return analyze(thd, check_opt);
|
return analyze(thd, check_opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2623,6 +3072,8 @@ handler::ha_analyze(THD* thd, HA_CHECK_OPT* check_opt)
|
|||||||
bool
|
bool
|
||||||
handler::ha_check_and_repair(THD *thd)
|
handler::ha_check_and_repair(THD *thd)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return check_and_repair(thd);
|
return check_and_repair(thd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2636,6 +3087,8 @@ handler::ha_check_and_repair(THD *thd)
|
|||||||
int
|
int
|
||||||
handler::ha_disable_indexes(uint mode)
|
handler::ha_disable_indexes(uint mode)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return disable_indexes(mode);
|
return disable_indexes(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2649,6 +3102,8 @@ handler::ha_disable_indexes(uint mode)
|
|||||||
int
|
int
|
||||||
handler::ha_enable_indexes(uint mode)
|
handler::ha_enable_indexes(uint mode)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return enable_indexes(mode);
|
return enable_indexes(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2662,6 +3117,8 @@ handler::ha_enable_indexes(uint mode)
|
|||||||
int
|
int
|
||||||
handler::ha_discard_or_import_tablespace(my_bool discard)
|
handler::ha_discard_or_import_tablespace(my_bool discard)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return discard_or_import_tablespace(discard);
|
return discard_or_import_tablespace(discard);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2677,6 +3134,8 @@ handler::ha_discard_or_import_tablespace(my_bool discard)
|
|||||||
void
|
void
|
||||||
handler::ha_prepare_for_alter()
|
handler::ha_prepare_for_alter()
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
prepare_for_alter();
|
prepare_for_alter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2690,6 +3149,8 @@ handler::ha_prepare_for_alter()
|
|||||||
int
|
int
|
||||||
handler::ha_rename_table(const char *from, const char *to)
|
handler::ha_rename_table(const char *from, const char *to)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return rename_table(from, to);
|
return rename_table(from, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2703,6 +3164,8 @@ handler::ha_rename_table(const char *from, const char *to)
|
|||||||
int
|
int
|
||||||
handler::ha_delete_table(const char *name)
|
handler::ha_delete_table(const char *name)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return delete_table(name);
|
return delete_table(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2716,6 +3179,8 @@ handler::ha_delete_table(const char *name)
|
|||||||
void
|
void
|
||||||
handler::ha_drop_table(const char *name)
|
handler::ha_drop_table(const char *name)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return drop_table(name);
|
return drop_table(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2729,6 +3194,8 @@ handler::ha_drop_table(const char *name)
|
|||||||
int
|
int
|
||||||
handler::ha_create(const char *name, TABLE *form, HA_CREATE_INFO *info)
|
handler::ha_create(const char *name, TABLE *form, HA_CREATE_INFO *info)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return create(name, form, info);
|
return create(name, form, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2743,6 +3210,8 @@ int
|
|||||||
handler::ha_create_handler_files(const char *name, const char *old_name,
|
handler::ha_create_handler_files(const char *name, const char *old_name,
|
||||||
int action_flag, HA_CREATE_INFO *info)
|
int action_flag, HA_CREATE_INFO *info)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return create_handler_files(name, old_name, action_flag, info);
|
return create_handler_files(name, old_name, action_flag, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2761,6 +3230,8 @@ handler::ha_change_partitions(HA_CREATE_INFO *create_info,
|
|||||||
const uchar *pack_frm_data,
|
const uchar *pack_frm_data,
|
||||||
size_t pack_frm_len)
|
size_t pack_frm_len)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return change_partitions(create_info, path, copied, deleted,
|
return change_partitions(create_info, path, copied, deleted,
|
||||||
pack_frm_data, pack_frm_len);
|
pack_frm_data, pack_frm_len);
|
||||||
}
|
}
|
||||||
@ -2775,6 +3246,8 @@ handler::ha_change_partitions(HA_CREATE_INFO *create_info,
|
|||||||
int
|
int
|
||||||
handler::ha_drop_partitions(const char *path)
|
handler::ha_drop_partitions(const char *path)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return drop_partitions(path);
|
return drop_partitions(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2788,6 +3261,8 @@ handler::ha_drop_partitions(const char *path)
|
|||||||
int
|
int
|
||||||
handler::ha_rename_partitions(const char *path)
|
handler::ha_rename_partitions(const char *path)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return rename_partitions(path);
|
return rename_partitions(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2801,6 +3276,8 @@ handler::ha_rename_partitions(const char *path)
|
|||||||
int
|
int
|
||||||
handler::ha_optimize_partitions(THD *thd)
|
handler::ha_optimize_partitions(THD *thd)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return optimize_partitions(thd);
|
return optimize_partitions(thd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2814,6 +3291,8 @@ handler::ha_optimize_partitions(THD *thd)
|
|||||||
int
|
int
|
||||||
handler::ha_analyze_partitions(THD *thd)
|
handler::ha_analyze_partitions(THD *thd)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return analyze_partitions(thd);
|
return analyze_partitions(thd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2827,6 +3306,8 @@ handler::ha_analyze_partitions(THD *thd)
|
|||||||
int
|
int
|
||||||
handler::ha_check_partitions(THD *thd)
|
handler::ha_check_partitions(THD *thd)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return check_partitions(thd);
|
return check_partitions(thd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2840,6 +3321,8 @@ handler::ha_check_partitions(THD *thd)
|
|||||||
int
|
int
|
||||||
handler::ha_repair_partitions(THD *thd)
|
handler::ha_repair_partitions(THD *thd)
|
||||||
{
|
{
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
return repair_partitions(thd);
|
return repair_partitions(thd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2866,7 +3349,7 @@ int ha_enable_transaction(THD *thd, bool on)
|
|||||||
is an optimization hint that storage engine is free to ignore.
|
is an optimization hint that storage engine is free to ignore.
|
||||||
So, let's commit an open transaction (if any) now.
|
So, let's commit an open transaction (if any) now.
|
||||||
*/
|
*/
|
||||||
if (!(error= ha_commit_stmt(thd)))
|
if (!(error= ha_commit_trans(thd, 0)))
|
||||||
error= end_trans(thd, COMMIT);
|
error= end_trans(thd, COMMIT);
|
||||||
}
|
}
|
||||||
DBUG_RETURN(error);
|
DBUG_RETURN(error);
|
||||||
@ -4042,6 +4525,9 @@ int handler::ha_write_row(uchar *buf)
|
|||||||
{
|
{
|
||||||
int error;
|
int error;
|
||||||
DBUG_ENTER("handler::ha_write_row");
|
DBUG_ENTER("handler::ha_write_row");
|
||||||
|
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
if (unlikely(error= write_row(buf)))
|
if (unlikely(error= write_row(buf)))
|
||||||
DBUG_RETURN(error);
|
DBUG_RETURN(error);
|
||||||
if (unlikely(error= binlog_log_row<Write_rows_log_event>(table, 0, buf)))
|
if (unlikely(error= binlog_log_row<Write_rows_log_event>(table, 0, buf)))
|
||||||
@ -4060,6 +4546,8 @@ int handler::ha_update_row(const uchar *old_data, uchar *new_data)
|
|||||||
*/
|
*/
|
||||||
DBUG_ASSERT(new_data == table->record[0]);
|
DBUG_ASSERT(new_data == table->record[0]);
|
||||||
|
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
if (unlikely(error= update_row(old_data, new_data)))
|
if (unlikely(error= update_row(old_data, new_data)))
|
||||||
return error;
|
return error;
|
||||||
if (unlikely(error= binlog_log_row<Update_rows_log_event>(table, old_data, new_data)))
|
if (unlikely(error= binlog_log_row<Update_rows_log_event>(table, old_data, new_data)))
|
||||||
@ -4070,6 +4558,9 @@ int handler::ha_update_row(const uchar *old_data, uchar *new_data)
|
|||||||
int handler::ha_delete_row(const uchar *buf)
|
int handler::ha_delete_row(const uchar *buf)
|
||||||
{
|
{
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
|
mark_trx_read_write();
|
||||||
|
|
||||||
if (unlikely(error= delete_row(buf)))
|
if (unlikely(error= delete_row(buf)))
|
||||||
return error;
|
return error;
|
||||||
if (unlikely(error= binlog_log_row<Delete_rows_log_event>(table, buf, 0)))
|
if (unlikely(error= binlog_log_row<Delete_rows_log_event>(table, buf, 0)))
|
||||||
|
122
sql/handler.h
122
sql/handler.h
@ -721,14 +721,14 @@ struct handlerton
|
|||||||
#define HTON_SUPPORT_LOG_TABLES (1 << 7) //Engine supports log tables
|
#define HTON_SUPPORT_LOG_TABLES (1 << 7) //Engine supports log tables
|
||||||
#define HTON_NO_PARTITION (1 << 8) //You can not partition these tables
|
#define HTON_NO_PARTITION (1 << 8) //You can not partition these tables
|
||||||
|
|
||||||
typedef struct st_thd_trans
|
class Ha_trx_info;
|
||||||
|
|
||||||
|
struct THD_TRANS
|
||||||
{
|
{
|
||||||
/* number of entries in the ht[] */
|
|
||||||
uint nht;
|
|
||||||
/* true is not all entries in the ht[] support 2pc */
|
/* true is not all entries in the ht[] support 2pc */
|
||||||
bool no_2pc;
|
bool no_2pc;
|
||||||
/* storage engines that registered themselves for this transaction */
|
/* storage engines that registered in this transaction */
|
||||||
handlerton *ht[MAX_HA];
|
Ha_trx_info *ha_list;
|
||||||
/*
|
/*
|
||||||
The purpose of this flag is to keep track of non-transactional
|
The purpose of this flag is to keep track of non-transactional
|
||||||
tables that were modified in scope of:
|
tables that were modified in scope of:
|
||||||
@ -758,7 +758,106 @@ typedef struct st_thd_trans
|
|||||||
saved value.
|
saved value.
|
||||||
*/
|
*/
|
||||||
bool modified_non_trans_table;
|
bool modified_non_trans_table;
|
||||||
} THD_TRANS;
|
|
||||||
|
void reset() { no_2pc= FALSE; modified_non_trans_table= FALSE; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Either statement transaction or normal transaction - related
|
||||||
|
thread-specific storage engine data.
|
||||||
|
|
||||||
|
If a storage engine participates in a statement/transaction,
|
||||||
|
an instance of this class is present in
|
||||||
|
thd->transaction.{stmt|all}.ha_list. The addition to
|
||||||
|
{stmt|all}.ha_list is made by trans_register_ha().
|
||||||
|
|
||||||
|
When it's time to commit or rollback, each element of ha_list
|
||||||
|
is used to access storage engine's prepare()/commit()/rollback()
|
||||||
|
methods, and also to evaluate if a full two phase commit is
|
||||||
|
necessary.
|
||||||
|
|
||||||
|
@sa General description of transaction handling in handler.cc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Ha_trx_info
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** Register this storage engine in the given transaction context. */
|
||||||
|
void register_ha(THD_TRANS *trans, handlerton *ht_arg)
|
||||||
|
{
|
||||||
|
DBUG_ASSERT(m_flags == 0);
|
||||||
|
DBUG_ASSERT(m_ht == NULL);
|
||||||
|
DBUG_ASSERT(m_next == NULL);
|
||||||
|
|
||||||
|
m_ht= ht_arg;
|
||||||
|
m_flags= (int) TRX_READ_ONLY; /* Assume read-only at start. */
|
||||||
|
|
||||||
|
m_next= trans->ha_list;
|
||||||
|
trans->ha_list= this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clear, prepare for reuse. */
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
m_next= NULL;
|
||||||
|
m_ht= NULL;
|
||||||
|
m_flags= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ha_trx_info() { reset(); }
|
||||||
|
|
||||||
|
void set_trx_read_write()
|
||||||
|
{
|
||||||
|
DBUG_ASSERT(is_started());
|
||||||
|
m_flags|= (int) TRX_READ_WRITE;
|
||||||
|
}
|
||||||
|
bool is_trx_read_write() const
|
||||||
|
{
|
||||||
|
DBUG_ASSERT(is_started());
|
||||||
|
return m_flags & (int) TRX_READ_WRITE;
|
||||||
|
}
|
||||||
|
bool is_started() const { return m_ht != NULL; }
|
||||||
|
/** Mark this transaction read-write if the argument is read-write. */
|
||||||
|
void coalesce_trx_with(const Ha_trx_info *stmt_trx)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Must be called only after the transaction has been started.
|
||||||
|
Can be called many times, e.g. when we have many
|
||||||
|
read-write statements in a transaction.
|
||||||
|
*/
|
||||||
|
DBUG_ASSERT(is_started());
|
||||||
|
if (stmt_trx->is_trx_read_write())
|
||||||
|
set_trx_read_write();
|
||||||
|
}
|
||||||
|
Ha_trx_info *next() const
|
||||||
|
{
|
||||||
|
DBUG_ASSERT(is_started());
|
||||||
|
return m_next;
|
||||||
|
}
|
||||||
|
handlerton *ht() const
|
||||||
|
{
|
||||||
|
DBUG_ASSERT(is_started());
|
||||||
|
return m_ht;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
enum { TRX_READ_ONLY= 0, TRX_READ_WRITE= 1 };
|
||||||
|
/** Auxiliary, used for ha_list management */
|
||||||
|
Ha_trx_info *m_next;
|
||||||
|
/**
|
||||||
|
Although a given Ha_trx_info instance is currently always used
|
||||||
|
for the same storage engine, 'ht' is not-NULL only when the
|
||||||
|
corresponding storage is a part of a transaction.
|
||||||
|
*/
|
||||||
|
handlerton *m_ht;
|
||||||
|
/**
|
||||||
|
Transaction flags related to this engine.
|
||||||
|
Not-null only if this instance is a part of transaction.
|
||||||
|
May assume a combination of enum values above.
|
||||||
|
*/
|
||||||
|
uchar m_flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
enum enum_tx_isolation { ISO_READ_UNCOMMITTED, ISO_READ_COMMITTED,
|
enum enum_tx_isolation { ISO_READ_UNCOMMITTED, ISO_READ_COMMITTED,
|
||||||
ISO_REPEATABLE_READ, ISO_SERIALIZABLE};
|
ISO_REPEATABLE_READ, ISO_SERIALIZABLE};
|
||||||
@ -1640,7 +1739,14 @@ protected:
|
|||||||
provide useful functionality.
|
provide useful functionality.
|
||||||
*/
|
*/
|
||||||
virtual int rename_table(const char *from, const char *to);
|
virtual int rename_table(const char *from, const char *to);
|
||||||
|
/**
|
||||||
|
Delete a table in the engine. Called for base as well as temporary
|
||||||
|
tables.
|
||||||
|
*/
|
||||||
virtual int delete_table(const char *name);
|
virtual int delete_table(const char *name);
|
||||||
|
private:
|
||||||
|
/* Private helpers */
|
||||||
|
inline void mark_trx_read_write();
|
||||||
private:
|
private:
|
||||||
/*
|
/*
|
||||||
Low-level primitives for storage engines. These should be
|
Low-level primitives for storage engines. These should be
|
||||||
@ -1821,9 +1927,7 @@ extern TYPELIB tx_isolation_typelib;
|
|||||||
extern TYPELIB myisam_stats_method_typelib;
|
extern TYPELIB myisam_stats_method_typelib;
|
||||||
extern ulong total_ha, total_ha_2pc;
|
extern ulong total_ha, total_ha_2pc;
|
||||||
|
|
||||||
/* Wrapper functions */
|
/* Wrapper functions */
|
||||||
#define ha_commit_stmt(thd) (ha_commit_trans((thd), FALSE))
|
|
||||||
#define ha_rollback_stmt(thd) (ha_rollback_trans((thd), FALSE))
|
|
||||||
#define ha_commit(thd) (ha_commit_trans((thd), TRUE))
|
#define ha_commit(thd) (ha_commit_trans((thd), TRUE))
|
||||||
#define ha_rollback(thd) (ha_rollback_trans((thd), TRUE))
|
#define ha_rollback(thd) (ha_rollback_trans((thd), TRUE))
|
||||||
|
|
||||||
|
10
sql/log.cc
10
sql/log.cc
@ -3332,6 +3332,16 @@ THD::binlog_start_trans_and_stmt()
|
|||||||
if (options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))
|
if (options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))
|
||||||
trans_register_ha(this, TRUE, binlog_hton);
|
trans_register_ha(this, TRUE, binlog_hton);
|
||||||
trans_register_ha(this, FALSE, binlog_hton);
|
trans_register_ha(this, FALSE, binlog_hton);
|
||||||
|
/*
|
||||||
|
Mark statement transaction as read/write. We never start
|
||||||
|
a binary log transaction and keep it read-only,
|
||||||
|
therefore it's best to mark the transaction read/write just
|
||||||
|
at the same time we start it.
|
||||||
|
Not necessary to mark the normal transaction read/write
|
||||||
|
since the statement-level flag will be propagated automatically
|
||||||
|
inside ha_commit_trans.
|
||||||
|
*/
|
||||||
|
ha_data[binlog_hton->slot].ha_info[0].set_trx_read_write();
|
||||||
}
|
}
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
}
|
||||||
|
@ -2898,7 +2898,7 @@ int Format_description_log_event::do_apply_event(Relay_log_info const *rli)
|
|||||||
original place when it comes to us; we'll know this by checking
|
original place when it comes to us; we'll know this by checking
|
||||||
log_pos ("artificial" events have log_pos == 0).
|
log_pos ("artificial" events have log_pos == 0).
|
||||||
*/
|
*/
|
||||||
if (!artificial_event && created && thd->transaction.all.nht)
|
if (!artificial_event && created && thd->transaction.all.ha_list)
|
||||||
{
|
{
|
||||||
/* This is not an error (XA is safe), just an information */
|
/* This is not an error (XA is safe), just an information */
|
||||||
rli->report(INFORMATION_LEVEL, 0,
|
rli->report(INFORMATION_LEVEL, 0,
|
||||||
|
@ -62,6 +62,26 @@ int injector::transaction::commit()
|
|||||||
{
|
{
|
||||||
DBUG_ENTER("injector::transaction::commit()");
|
DBUG_ENTER("injector::transaction::commit()");
|
||||||
m_thd->binlog_flush_pending_rows_event(true);
|
m_thd->binlog_flush_pending_rows_event(true);
|
||||||
|
/*
|
||||||
|
Cluster replication does not preserve statement or
|
||||||
|
transaction boundaries of the master. Instead, a new
|
||||||
|
transaction on replication slave is started when a new GCI
|
||||||
|
(global checkpoint identifier) is issued, and is committed
|
||||||
|
when the last event of the check point has been received and
|
||||||
|
processed. This ensures consistency of each cluster in
|
||||||
|
cluster replication, and there is no requirement for stronger
|
||||||
|
consistency: MySQL replication is asynchronous with other
|
||||||
|
engines as well.
|
||||||
|
|
||||||
|
A practical consequence of that is that row level replication
|
||||||
|
stream passed through the injector thread never contains
|
||||||
|
COMMIT events.
|
||||||
|
Here we should preserve the server invariant that there is no
|
||||||
|
outstanding statement transaction when the normal transaction
|
||||||
|
is committed by committing the statement transaction
|
||||||
|
explicitly.
|
||||||
|
*/
|
||||||
|
ha_autocommit_or_rollback(m_thd, 0);
|
||||||
end_trans(m_thd, COMMIT);
|
end_trans(m_thd, COMMIT);
|
||||||
DBUG_RETURN(0);
|
DBUG_RETURN(0);
|
||||||
}
|
}
|
||||||
|
20
sql/sp.cc
20
sql/sp.cc
@ -665,8 +665,16 @@ sp_returns_type(THD *thd, String &result, sp_head *sp)
|
|||||||
(TYPE_ENUM_PROCEDURE or TYPE_ENUM_FUNCTION).
|
(TYPE_ENUM_PROCEDURE or TYPE_ENUM_FUNCTION).
|
||||||
@param sp Stored routine object to store.
|
@param sp Stored routine object to store.
|
||||||
|
|
||||||
@return Error code. SP_OK is returned on success. Other SP_ constants are
|
@note Opens and closes the thread tables. Therefore assumes
|
||||||
used to indicate about errors.
|
that there are no locked tables in this thread at the time of
|
||||||
|
invocation.
|
||||||
|
Unlike some other DDL statements, *does* close the tables
|
||||||
|
in the end, since the call to this function is normally
|
||||||
|
followed by an implicit grant (sp_grant_privileges())
|
||||||
|
and this subsequent call opens and closes mysql.procs_priv.
|
||||||
|
|
||||||
|
@return Error code. SP_OK is returned on success. Other
|
||||||
|
SP_ constants are used to indicate about errors.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
int
|
int
|
||||||
@ -1223,7 +1231,13 @@ done:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Drop all routines in database 'db' */
|
/**
|
||||||
|
Drop all routines in database 'db'
|
||||||
|
|
||||||
|
@note Close the thread tables, the calling code might want to
|
||||||
|
delete from other system tables afterwards.
|
||||||
|
*/
|
||||||
|
|
||||||
int
|
int
|
||||||
sp_drop_db_routines(THD *thd, char *db)
|
sp_drop_db_routines(THD *thd, char *db)
|
||||||
{
|
{
|
||||||
|
@ -2700,6 +2700,7 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp,
|
|||||||
m_lex->unit.cleanup();
|
m_lex->unit.cleanup();
|
||||||
|
|
||||||
thd_proc_info(thd, "closing tables");
|
thd_proc_info(thd, "closing tables");
|
||||||
|
/* Here we also commit or rollback the current statement. */
|
||||||
close_thread_tables(thd);
|
close_thread_tables(thd);
|
||||||
thd_proc_info(thd, 0);
|
thd_proc_info(thd, 0);
|
||||||
|
|
||||||
|
@ -1324,29 +1324,45 @@ void close_thread_tables(THD *thd)
|
|||||||
Mark all temporary tables used by this statement as free for reuse.
|
Mark all temporary tables used by this statement as free for reuse.
|
||||||
*/
|
*/
|
||||||
mark_temp_tables_as_free_for_reuse(thd);
|
mark_temp_tables_as_free_for_reuse(thd);
|
||||||
|
/*
|
||||||
|
Let us commit transaction for statement. Since in 5.0 we only have
|
||||||
|
one statement transaction and don't allow several nested statement
|
||||||
|
transactions this call will do nothing if we are inside of stored
|
||||||
|
function or trigger (i.e. statement transaction is already active and
|
||||||
|
does not belong to statement for which we do close_thread_tables()).
|
||||||
|
TODO: This should be fixed in later releases.
|
||||||
|
*/
|
||||||
|
if (!(thd->state_flags & Open_tables_state::BACKUPS_AVAIL))
|
||||||
|
{
|
||||||
|
thd->main_da.can_overwrite_status= TRUE;
|
||||||
|
ha_autocommit_or_rollback(thd, thd->is_error());
|
||||||
|
thd->main_da.can_overwrite_status= FALSE;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reset transaction state, but only if we're not inside a
|
||||||
|
sub-statement of a prelocked statement.
|
||||||
|
*/
|
||||||
|
if (! prelocked_mode || thd->lex->requires_prelocking())
|
||||||
|
thd->transaction.stmt.reset();
|
||||||
|
}
|
||||||
|
|
||||||
if (thd->locked_tables || prelocked_mode)
|
if (thd->locked_tables || prelocked_mode)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
Let us commit transaction for statement. Since in 5.0 we only have
|
|
||||||
one statement transaction and don't allow several nested statement
|
|
||||||
transactions this call will do nothing if we are inside of stored
|
|
||||||
function or trigger (i.e. statement transaction is already active and
|
|
||||||
does not belong to statement for which we do close_thread_tables()).
|
|
||||||
TODO: This should be fixed in later releases.
|
|
||||||
*/
|
|
||||||
ha_commit_stmt(thd);
|
|
||||||
|
|
||||||
/* Ensure we are calling ha_reset() for all used tables */
|
/* Ensure we are calling ha_reset() for all used tables */
|
||||||
mark_used_tables_as_free_for_reuse(thd, thd->open_tables);
|
mark_used_tables_as_free_for_reuse(thd, thd->open_tables);
|
||||||
|
|
||||||
/* We are under simple LOCK TABLES so should not do anything else. */
|
/*
|
||||||
|
We are under simple LOCK TABLES or we're inside a sub-statement
|
||||||
|
of a prelocked statement, so should not do anything else.
|
||||||
|
*/
|
||||||
if (!prelocked_mode || !thd->lex->requires_prelocking())
|
if (!prelocked_mode || !thd->lex->requires_prelocking())
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
We are in prelocked mode, so we have to leave it now with doing
|
We are in the top-level statement of a prelocked statement,
|
||||||
implicit UNLOCK TABLES if need.
|
so we have to leave the prelocked mode now with doing implicit
|
||||||
|
UNLOCK TABLES if needed.
|
||||||
*/
|
*/
|
||||||
DBUG_PRINT("info",("thd->prelocked_mode= NON_PRELOCKED"));
|
DBUG_PRINT("info",("thd->prelocked_mode= NON_PRELOCKED"));
|
||||||
thd->prelocked_mode= NON_PRELOCKED;
|
thd->prelocked_mode= NON_PRELOCKED;
|
||||||
@ -1374,19 +1390,6 @@ void close_thread_tables(THD *thd)
|
|||||||
mysql_unlock_tables(thd, thd->lock);
|
mysql_unlock_tables(thd, thd->lock);
|
||||||
thd->lock=0;
|
thd->lock=0;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
assume handlers auto-commit (if some doesn't - transaction handling
|
|
||||||
in MySQL should be redesigned to support it; it's a big change,
|
|
||||||
and it's not worth it - better to commit explicitly only writing
|
|
||||||
transactions, read-only ones should better take care of themselves.
|
|
||||||
saves some work in 2pc too)
|
|
||||||
see also sql_parse.cc - dispatch_command()
|
|
||||||
*/
|
|
||||||
if (!(thd->state_flags & Open_tables_state::BACKUPS_AVAIL))
|
|
||||||
bzero(&thd->transaction.stmt, sizeof(thd->transaction.stmt));
|
|
||||||
if (!thd->active_transaction())
|
|
||||||
thd->transaction.xid_state.xid.null();
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Note that we need to hold LOCK_open while changing the
|
Note that we need to hold LOCK_open while changing the
|
||||||
open_tables list. Another thread may work on it.
|
open_tables list. Another thread may work on it.
|
||||||
@ -5059,10 +5062,7 @@ int decide_logging_format(THD *thd, TABLE_LIST *tables)
|
|||||||
DBUG_PRINT("info", ("error: %d", error));
|
DBUG_PRINT("info", ("error: %d", error));
|
||||||
|
|
||||||
if (error)
|
if (error)
|
||||||
{
|
|
||||||
ha_rollback_stmt(thd);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
We switch to row-based format if we are in mixed mode and one of
|
We switch to row-based format if we are in mixed mode and one of
|
||||||
@ -5216,7 +5216,6 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen)
|
|||||||
table->table->query_id= thd->query_id;
|
table->table->query_id= thd->query_id;
|
||||||
if (check_lock_and_start_stmt(thd, table->table, table->lock_type))
|
if (check_lock_and_start_stmt(thd, table->table, table->lock_type))
|
||||||
{
|
{
|
||||||
ha_rollback_stmt(thd);
|
|
||||||
mysql_unlock_tables(thd, thd->locked_tables);
|
mysql_unlock_tables(thd, thd->locked_tables);
|
||||||
thd->locked_tables= 0;
|
thd->locked_tables= 0;
|
||||||
thd->options&= ~(OPTION_TABLE_LOCK);
|
thd->options&= ~(OPTION_TABLE_LOCK);
|
||||||
@ -5251,7 +5250,6 @@ int lock_tables(THD *thd, TABLE_LIST *tables, uint count, bool *need_reopen)
|
|||||||
if (!table->placeholder() &&
|
if (!table->placeholder() &&
|
||||||
check_lock_and_start_stmt(thd, table->table, table->lock_type))
|
check_lock_and_start_stmt(thd, table->table, table->lock_type))
|
||||||
{
|
{
|
||||||
ha_rollback_stmt(thd);
|
|
||||||
DBUG_RETURN(-1);
|
DBUG_RETURN(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -264,7 +264,7 @@ const char *set_thd_proc_info(THD *thd, const char *info,
|
|||||||
extern "C"
|
extern "C"
|
||||||
void **thd_ha_data(const THD *thd, const struct handlerton *hton)
|
void **thd_ha_data(const THD *thd, const struct handlerton *hton)
|
||||||
{
|
{
|
||||||
return (void **) thd->ha_data + hton->slot;
|
return (void **) &thd->ha_data[hton->slot].ha_ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
@ -2513,7 +2513,7 @@ bool select_dumpvar::send_data(List<Item> &items)
|
|||||||
suv->update();
|
suv->update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DBUG_RETURN(0);
|
DBUG_RETURN(thd->is_error());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool select_dumpvar::send_eof()
|
bool select_dumpvar::send_eof()
|
||||||
|
@ -686,7 +686,8 @@ private:
|
|||||||
struct st_savepoint {
|
struct st_savepoint {
|
||||||
struct st_savepoint *prev;
|
struct st_savepoint *prev;
|
||||||
char *name;
|
char *name;
|
||||||
uint length, nht;
|
uint length;
|
||||||
|
Ha_trx_info *ha_list;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum xa_states {XA_NOTR=0, XA_ACTIVE, XA_IDLE, XA_PREPARED};
|
enum xa_states {XA_NOTR=0, XA_ACTIVE, XA_IDLE, XA_PREPARED};
|
||||||
@ -1092,6 +1093,33 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Storage engine specific thread local data.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct Ha_data
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
Storage engine specific thread local data.
|
||||||
|
Lifetime: one user connection.
|
||||||
|
*/
|
||||||
|
void *ha_ptr;
|
||||||
|
/**
|
||||||
|
0: Life time: one statement within a transaction. If @@autocommit is
|
||||||
|
on, also represents the entire transaction.
|
||||||
|
@sa trans_register_ha()
|
||||||
|
|
||||||
|
1: Life time: one transaction within a connection.
|
||||||
|
If the storage engine does not participate in a transaction,
|
||||||
|
this should not be used.
|
||||||
|
@sa trans_register_ha()
|
||||||
|
*/
|
||||||
|
Ha_trx_info ha_info[2];
|
||||||
|
|
||||||
|
Ha_data() :ha_ptr(NULL) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@class THD
|
@class THD
|
||||||
For each client connection we create a separate thread with THD serving as
|
For each client connection we create a separate thread with THD serving as
|
||||||
@ -1231,7 +1259,7 @@ public:
|
|||||||
uint in_sub_stmt;
|
uint in_sub_stmt;
|
||||||
|
|
||||||
/* container for handler's private per-connection data */
|
/* container for handler's private per-connection data */
|
||||||
void *ha_data[MAX_HA];
|
Ha_data ha_data[MAX_HA];
|
||||||
|
|
||||||
#ifndef MYSQL_CLIENT
|
#ifndef MYSQL_CLIENT
|
||||||
int binlog_setup_trx_data();
|
int binlog_setup_trx_data();
|
||||||
|
@ -322,9 +322,10 @@ Sensitive_cursor::post_open(THD *thd)
|
|||||||
|
|
||||||
close_at_commit= FALSE; /* reset in case we're reusing the cursor */
|
close_at_commit= FALSE; /* reset in case we're reusing the cursor */
|
||||||
info= &ht_info[0];
|
info= &ht_info[0];
|
||||||
for (handlerton **pht= thd->transaction.stmt.ht; *pht; pht++)
|
for (Ha_trx_info *ha_trx_info= thd->transaction.stmt.ha_list;
|
||||||
|
ha_trx_info; ha_trx_info= ha_trx_info->next())
|
||||||
{
|
{
|
||||||
handlerton *ht= *pht;
|
handlerton *ht= ha_trx_info->ht();
|
||||||
close_at_commit|= test(ht->flags & HTON_CLOSE_CURSORS_AT_COMMIT);
|
close_at_commit|= test(ht->flags & HTON_CLOSE_CURSORS_AT_COMMIT);
|
||||||
if (ht->create_cursor_read_view)
|
if (ht->create_cursor_read_view)
|
||||||
{
|
{
|
||||||
|
@ -24,6 +24,14 @@
|
|||||||
#include "sp_head.h"
|
#include "sp_head.h"
|
||||||
#include "sql_trigger.h"
|
#include "sql_trigger.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
Implement DELETE SQL word.
|
||||||
|
|
||||||
|
@note Like implementations of other DDL/DML in MySQL, this function
|
||||||
|
relies on the caller to close the thread tables. This is done in the
|
||||||
|
end of dispatch_command().
|
||||||
|
*/
|
||||||
|
|
||||||
bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
|
bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
|
||||||
SQL_LIST *order, ha_rows limit, ulonglong options,
|
SQL_LIST *order, ha_rows limit, ulonglong options,
|
||||||
bool reset_auto_increment)
|
bool reset_auto_increment)
|
||||||
@ -380,17 +388,6 @@ cleanup:
|
|||||||
}
|
}
|
||||||
DBUG_ASSERT(transactional_table || !deleted || thd->transaction.stmt.modified_non_trans_table);
|
DBUG_ASSERT(transactional_table || !deleted || thd->transaction.stmt.modified_non_trans_table);
|
||||||
free_underlaid_joins(thd, select_lex);
|
free_underlaid_joins(thd, select_lex);
|
||||||
if (transactional_table)
|
|
||||||
{
|
|
||||||
if (ha_autocommit_or_rollback(thd,error >= 0))
|
|
||||||
error=1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (thd->lock)
|
|
||||||
{
|
|
||||||
mysql_unlock_tables(thd, thd->lock);
|
|
||||||
thd->lock=0;
|
|
||||||
}
|
|
||||||
if (error < 0 || (thd->lex->ignore && !thd->is_fatal_error))
|
if (error < 0 || (thd->lex->ignore && !thd->is_fatal_error))
|
||||||
{
|
{
|
||||||
thd->row_count_func= deleted;
|
thd->row_count_func= deleted;
|
||||||
@ -751,11 +748,9 @@ void multi_delete::abort()
|
|||||||
The same if all tables are transactional, regardless of where we are.
|
The same if all tables are transactional, regardless of where we are.
|
||||||
In all other cases do attempt deletes ...
|
In all other cases do attempt deletes ...
|
||||||
*/
|
*/
|
||||||
if ((table_being_deleted == delete_tables &&
|
if (do_delete && normal_tables &&
|
||||||
table_being_deleted->table->file->has_transactions()) ||
|
(table_being_deleted != delete_tables ||
|
||||||
!normal_tables)
|
!table_being_deleted->table->file->has_transactions()))
|
||||||
ha_rollback_stmt(thd);
|
|
||||||
else if (do_delete)
|
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
We have to execute the recorded do_deletes() and write info into the
|
We have to execute the recorded do_deletes() and write info into the
|
||||||
@ -921,11 +916,6 @@ bool multi_delete::send_eof()
|
|||||||
if (local_error != 0)
|
if (local_error != 0)
|
||||||
error_handled= TRUE; // to force early leave from ::send_error()
|
error_handled= TRUE; // to force early leave from ::send_error()
|
||||||
|
|
||||||
/* Commit or rollback the current SQL statement */
|
|
||||||
if (transactional_tables)
|
|
||||||
if (ha_autocommit_or_rollback(thd,local_error > 0))
|
|
||||||
local_error=1;
|
|
||||||
|
|
||||||
if (!local_error)
|
if (!local_error)
|
||||||
{
|
{
|
||||||
thd->row_count_func= deleted;
|
thd->row_count_func= deleted;
|
||||||
@ -1055,6 +1045,12 @@ trunc_by_del:
|
|||||||
error= mysql_delete(thd, table_list, (COND*) 0, (SQL_LIST*) 0,
|
error= mysql_delete(thd, table_list, (COND*) 0, (SQL_LIST*) 0,
|
||||||
HA_POS_ERROR, LL(0), TRUE);
|
HA_POS_ERROR, LL(0), TRUE);
|
||||||
ha_enable_transaction(thd, TRUE);
|
ha_enable_transaction(thd, TRUE);
|
||||||
|
/*
|
||||||
|
Safety, in case the engine ignored ha_enable_transaction(FALSE)
|
||||||
|
above. Also clears thd->transaction.*.
|
||||||
|
*/
|
||||||
|
error= ha_autocommit_or_rollback(thd, error);
|
||||||
|
ha_commit(thd);
|
||||||
thd->options= save_options;
|
thd->options= save_options;
|
||||||
thd->current_stmt_binlog_row_based= save_binlog_row_based;
|
thd->current_stmt_binlog_row_based= save_binlog_row_based;
|
||||||
DBUG_RETURN(error);
|
DBUG_RETURN(error);
|
||||||
|
@ -28,7 +28,17 @@ bool mysql_do(THD *thd, List<Item> &values)
|
|||||||
while ((value = li++))
|
while ((value = li++))
|
||||||
value->val_int();
|
value->val_int();
|
||||||
free_underlaid_joins(thd, &thd->lex->select_lex);
|
free_underlaid_joins(thd, &thd->lex->select_lex);
|
||||||
thd->clear_error(); // DO always is OK
|
|
||||||
|
if (thd->is_error())
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Rollback the effect of the statement, since next instruction
|
||||||
|
will clear the error and the rollback in the end of
|
||||||
|
dispatch_command() won't work.
|
||||||
|
*/
|
||||||
|
ha_autocommit_or_rollback(thd, thd->is_error());
|
||||||
|
thd->clear_error(); // DO always is OK
|
||||||
|
}
|
||||||
send_ok(thd);
|
send_ok(thd);
|
||||||
DBUG_RETURN(FALSE);
|
DBUG_RETURN(FALSE);
|
||||||
}
|
}
|
||||||
|
@ -541,6 +541,10 @@ bool open_and_lock_for_insert_delayed(THD *thd, TABLE_LIST *table_list)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
INSERT statement implementation
|
INSERT statement implementation
|
||||||
|
|
||||||
|
@note Like implementations of other DDL/DML in MySQL, this function
|
||||||
|
relies on the caller to close the thread tables. This is done in the
|
||||||
|
end of dispatch_command().
|
||||||
*/
|
*/
|
||||||
|
|
||||||
bool mysql_insert(THD *thd,TABLE_LIST *table_list,
|
bool mysql_insert(THD *thd,TABLE_LIST *table_list,
|
||||||
@ -893,12 +897,9 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
|
|||||||
}
|
}
|
||||||
DBUG_ASSERT(transactional_table || !changed ||
|
DBUG_ASSERT(transactional_table || !changed ||
|
||||||
thd->transaction.stmt.modified_non_trans_table);
|
thd->transaction.stmt.modified_non_trans_table);
|
||||||
if (transactional_table)
|
|
||||||
error=ha_autocommit_or_rollback(thd,error);
|
|
||||||
|
|
||||||
if (thd->lock)
|
if (thd->lock)
|
||||||
{
|
{
|
||||||
mysql_unlock_tables(thd, thd->lock);
|
|
||||||
/*
|
/*
|
||||||
Invalidate the table in the query cache if something changed
|
Invalidate the table in the query cache if something changed
|
||||||
after unlocking when changes become fisible.
|
after unlocking when changes become fisible.
|
||||||
@ -909,7 +910,6 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
|
|||||||
{
|
{
|
||||||
query_cache_invalidate3(thd, table_list, 1);
|
query_cache_invalidate3(thd, table_list, 1);
|
||||||
}
|
}
|
||||||
thd->lock=0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
thd_proc_info(thd, "end");
|
thd_proc_info(thd, "end");
|
||||||
@ -2445,7 +2445,7 @@ err:
|
|||||||
first call to ha_*_row() instead. Remove code that are used to
|
first call to ha_*_row() instead. Remove code that are used to
|
||||||
cover for the case outlined above.
|
cover for the case outlined above.
|
||||||
*/
|
*/
|
||||||
ha_rollback_stmt(thd);
|
ha_autocommit_or_rollback(thd, 1);
|
||||||
|
|
||||||
#ifndef __WIN__
|
#ifndef __WIN__
|
||||||
end:
|
end:
|
||||||
@ -3139,18 +3139,6 @@ bool select_insert::send_eof()
|
|||||||
thd->query, thd->query_length,
|
thd->query, thd->query_length,
|
||||||
trans_table, FALSE, killed_status);
|
trans_table, FALSE, killed_status);
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
We will call ha_autocommit_or_rollback() also for
|
|
||||||
non-transactional tables under row-based replication: there might
|
|
||||||
be events in the binary logs transaction, and we need to write
|
|
||||||
them to the binary log.
|
|
||||||
*/
|
|
||||||
if (trans_table || thd->current_stmt_binlog_row_based)
|
|
||||||
{
|
|
||||||
int error2= ha_autocommit_or_rollback(thd, error);
|
|
||||||
if (error2 && !error)
|
|
||||||
error= error2;
|
|
||||||
}
|
|
||||||
table->file->ha_release_auto_increment();
|
table->file->ha_release_auto_increment();
|
||||||
|
|
||||||
if (error)
|
if (error)
|
||||||
@ -3228,7 +3216,6 @@ void select_insert::abort() {
|
|||||||
table->file->ha_release_auto_increment();
|
table->file->ha_release_auto_increment();
|
||||||
}
|
}
|
||||||
|
|
||||||
ha_rollback_stmt(thd);
|
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3667,7 +3654,10 @@ bool select_create::send_eof()
|
|||||||
nevertheless.
|
nevertheless.
|
||||||
*/
|
*/
|
||||||
if (!table->s->tmp_table)
|
if (!table->s->tmp_table)
|
||||||
ha_commit(thd); // Can fail, but we proceed anyway
|
{
|
||||||
|
ha_autocommit_or_rollback(thd, 0);
|
||||||
|
end_active_trans(thd);
|
||||||
|
}
|
||||||
|
|
||||||
table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
|
table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
|
||||||
table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
|
table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
|
||||||
@ -3691,12 +3681,9 @@ void select_create::abort()
|
|||||||
by removing the table, even for non-transactional tables.
|
by removing the table, even for non-transactional tables.
|
||||||
*/
|
*/
|
||||||
tmp_disable_binlog(thd);
|
tmp_disable_binlog(thd);
|
||||||
select_insert::abort();
|
|
||||||
reenable_binlog(thd);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
We roll back the statement, including truncating the transaction
|
In select_insert::abort() we roll back the statement, including
|
||||||
cache of the binary log, if the statement failed.
|
truncating the transaction cache of the binary log.
|
||||||
|
|
||||||
We roll back the statement prior to deleting the table and prior
|
We roll back the statement prior to deleting the table and prior
|
||||||
to releasing the lock on the table, since there might be potential
|
to releasing the lock on the table, since there might be potential
|
||||||
@ -3707,8 +3694,9 @@ void select_create::abort()
|
|||||||
of the table succeeded or not, since we need to reset the binary
|
of the table succeeded or not, since we need to reset the binary
|
||||||
log state.
|
log state.
|
||||||
*/
|
*/
|
||||||
if (thd->current_stmt_binlog_row_based)
|
select_insert::abort();
|
||||||
ha_rollback_stmt(thd);
|
reenable_binlog(thd);
|
||||||
|
|
||||||
|
|
||||||
if (m_plock)
|
if (m_plock)
|
||||||
{
|
{
|
||||||
|
@ -470,9 +470,6 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif /*!EMBEDDED_LIBRARY*/
|
#endif /*!EMBEDDED_LIBRARY*/
|
||||||
if (transactional_table)
|
|
||||||
ha_autocommit_or_rollback(thd,error);
|
|
||||||
|
|
||||||
error= -1; // Error on read
|
error= -1; // Error on read
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
@ -510,8 +507,6 @@ bool mysql_load(THD *thd,sql_exchange *ex,TABLE_LIST *table_list,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif /*!EMBEDDED_LIBRARY*/
|
#endif /*!EMBEDDED_LIBRARY*/
|
||||||
if (transactional_table)
|
|
||||||
error=ha_autocommit_or_rollback(thd,error);
|
|
||||||
|
|
||||||
/* ok to client sent only after binlog write and engine commit */
|
/* ok to client sent only after binlog write and engine commit */
|
||||||
send_ok(thd, info.copied + info.deleted, 0L, name);
|
send_ok(thd, info.copied + info.deleted, 0L, name);
|
||||||
@ -519,11 +514,6 @@ err:
|
|||||||
DBUG_ASSERT(transactional_table || !(info.copied || info.deleted) ||
|
DBUG_ASSERT(transactional_table || !(info.copied || info.deleted) ||
|
||||||
thd->transaction.stmt.modified_non_trans_table);
|
thd->transaction.stmt.modified_non_trans_table);
|
||||||
table->file->ha_release_auto_increment();
|
table->file->ha_release_auto_increment();
|
||||||
if (thd->lock)
|
|
||||||
{
|
|
||||||
mysql_unlock_tables(thd, thd->lock);
|
|
||||||
thd->lock=0;
|
|
||||||
}
|
|
||||||
table->auto_increment_field_not_null= FALSE;
|
table->auto_increment_field_not_null= FALSE;
|
||||||
thd->abort_on_warning= 0;
|
thd->abort_on_warning= 0;
|
||||||
DBUG_RETURN(error);
|
DBUG_RETURN(error);
|
||||||
|
@ -1465,21 +1465,13 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
thd_proc_info(thd, "closing tables");
|
/* If commit fails, we should be able to reset the OK status. */
|
||||||
/* Free tables */
|
thd->main_da.can_overwrite_status= TRUE;
|
||||||
close_thread_tables(thd);
|
ha_autocommit_or_rollback(thd, thd->is_error());
|
||||||
|
thd->main_da.can_overwrite_status= FALSE;
|
||||||
|
|
||||||
|
thd->transaction.stmt.reset();
|
||||||
|
|
||||||
/*
|
|
||||||
assume handlers auto-commit (if some doesn't - transaction handling
|
|
||||||
in MySQL should be redesigned to support it; it's a big change,
|
|
||||||
and it's not worth it - better to commit explicitly only writing
|
|
||||||
transactions, read-only ones should better take care of themselves.
|
|
||||||
saves some work in 2pc too)
|
|
||||||
see also sql_base.cc - close_thread_tables()
|
|
||||||
*/
|
|
||||||
bzero(&thd->transaction.stmt, sizeof(thd->transaction.stmt));
|
|
||||||
if (!thd->active_transaction())
|
|
||||||
thd->transaction.xid_state.xid.null();
|
|
||||||
|
|
||||||
/* report error issued during command execution */
|
/* report error issued during command execution */
|
||||||
if (thd->killed_errno())
|
if (thd->killed_errno())
|
||||||
@ -1496,6 +1488,10 @@ bool dispatch_command(enum enum_server_command command, THD *thd,
|
|||||||
net_end_statement(thd);
|
net_end_statement(thd);
|
||||||
query_cache_end_of_result(thd);
|
query_cache_end_of_result(thd);
|
||||||
|
|
||||||
|
thd->proc_info= "closing tables";
|
||||||
|
/* Free tables */
|
||||||
|
close_thread_tables(thd);
|
||||||
|
|
||||||
log_slow_statement(thd);
|
log_slow_statement(thd);
|
||||||
|
|
||||||
thd_proc_info(thd, "cleaning up");
|
thd_proc_info(thd, "cleaning up");
|
||||||
@ -3011,10 +3007,8 @@ end_with_restore_list:
|
|||||||
/* INSERT ... SELECT should invalidate only the very first table */
|
/* INSERT ... SELECT should invalidate only the very first table */
|
||||||
TABLE_LIST *save_table= first_table->next_local;
|
TABLE_LIST *save_table= first_table->next_local;
|
||||||
first_table->next_local= 0;
|
first_table->next_local= 0;
|
||||||
mysql_unlock_tables(thd, thd->lock);
|
|
||||||
query_cache_invalidate3(thd, first_table, 1);
|
query_cache_invalidate3(thd, first_table, 1);
|
||||||
first_table->next_local= save_table;
|
first_table->next_local= save_table;
|
||||||
thd->lock=0;
|
|
||||||
}
|
}
|
||||||
delete sel_result;
|
delete sel_result;
|
||||||
}
|
}
|
||||||
@ -3985,7 +3979,6 @@ end_with_restore_list:
|
|||||||
push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
|
push_warning(thd, MYSQL_ERROR::WARN_LEVEL_WARN,
|
||||||
ER_PROC_AUTO_GRANT_FAIL,
|
ER_PROC_AUTO_GRANT_FAIL,
|
||||||
ER(ER_PROC_AUTO_GRANT_FAIL));
|
ER(ER_PROC_AUTO_GRANT_FAIL));
|
||||||
close_thread_tables(thd);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
|
@ -3968,31 +3968,35 @@ static int fast_end_partition(THD *thd, ulonglong copied,
|
|||||||
bool written_bin_log)
|
bool written_bin_log)
|
||||||
{
|
{
|
||||||
int error;
|
int error;
|
||||||
|
char tmp_name[80];
|
||||||
DBUG_ENTER("fast_end_partition");
|
DBUG_ENTER("fast_end_partition");
|
||||||
|
|
||||||
thd->proc_info="end";
|
thd->proc_info="end";
|
||||||
|
|
||||||
if (!is_empty)
|
if (!is_empty)
|
||||||
query_cache_invalidate3(thd, table_list, 0);
|
query_cache_invalidate3(thd, table_list, 0);
|
||||||
error= ha_commit_stmt(thd);
|
|
||||||
if (ha_commit(thd))
|
error= ha_autocommit_or_rollback(thd, 0);
|
||||||
|
if (end_active_trans(thd))
|
||||||
error= 1;
|
error= 1;
|
||||||
if (!error || is_empty)
|
|
||||||
|
if (error)
|
||||||
{
|
{
|
||||||
char tmp_name[80];
|
/* If error during commit, no need to rollback, it's done. */
|
||||||
if ((!is_empty) && (!written_bin_log) &&
|
table->file->print_error(error, MYF(0));
|
||||||
(!thd->lex->no_write_to_binlog))
|
DBUG_RETURN(TRUE);
|
||||||
write_bin_log(thd, FALSE, thd->query, thd->query_length);
|
|
||||||
close_thread_tables(thd);
|
|
||||||
my_snprintf(tmp_name, sizeof(tmp_name), ER(ER_INSERT_INFO),
|
|
||||||
(ulong) (copied + deleted),
|
|
||||||
(ulong) deleted,
|
|
||||||
(ulong) 0);
|
|
||||||
send_ok(thd, (ha_rows) (copied+deleted),0L,tmp_name);
|
|
||||||
DBUG_RETURN(FALSE);
|
|
||||||
}
|
}
|
||||||
table->file->print_error(error, MYF(0));
|
|
||||||
close_thread_tables(thd);
|
if ((!is_empty) && (!written_bin_log) &&
|
||||||
DBUG_RETURN(TRUE);
|
(!thd->lex->no_write_to_binlog))
|
||||||
|
write_bin_log(thd, FALSE, thd->query, thd->query_length);
|
||||||
|
|
||||||
|
my_snprintf(tmp_name, sizeof(tmp_name), ER(ER_INSERT_INFO),
|
||||||
|
(ulong) (copied + deleted),
|
||||||
|
(ulong) deleted,
|
||||||
|
(ulong) 0);
|
||||||
|
send_ok(thd, (ha_rows) (copied+deleted),0L, tmp_name);
|
||||||
|
DBUG_RETURN(FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -4131,6 +4131,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
|
|||||||
switch ((*prepare_func)(thd, table, check_opt)) {
|
switch ((*prepare_func)(thd, table, check_opt)) {
|
||||||
case 1: // error, message written to net
|
case 1: // error, message written to net
|
||||||
ha_autocommit_or_rollback(thd, 1);
|
ha_autocommit_or_rollback(thd, 1);
|
||||||
|
end_trans(thd, ROLLBACK);
|
||||||
close_thread_tables(thd);
|
close_thread_tables(thd);
|
||||||
DBUG_PRINT("admin", ("simple error, admin next table"));
|
DBUG_PRINT("admin", ("simple error, admin next table"));
|
||||||
continue;
|
continue;
|
||||||
@ -4189,6 +4190,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
|
|||||||
table_name);
|
table_name);
|
||||||
protocol->store(buff, length, system_charset_info);
|
protocol->store(buff, length, system_charset_info);
|
||||||
ha_autocommit_or_rollback(thd, 0);
|
ha_autocommit_or_rollback(thd, 0);
|
||||||
|
end_trans(thd, COMMIT);
|
||||||
close_thread_tables(thd);
|
close_thread_tables(thd);
|
||||||
lex->reset_query_tables_list(FALSE);
|
lex->reset_query_tables_list(FALSE);
|
||||||
table->table=0; // For query cache
|
table->table=0; // For query cache
|
||||||
@ -4461,6 +4463,7 @@ send_result_message:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ha_autocommit_or_rollback(thd, 0);
|
ha_autocommit_or_rollback(thd, 0);
|
||||||
|
end_trans(thd, COMMIT);
|
||||||
close_thread_tables(thd);
|
close_thread_tables(thd);
|
||||||
table->table=0; // For query cache
|
table->table=0; // For query cache
|
||||||
if (protocol->write())
|
if (protocol->write())
|
||||||
@ -4470,8 +4473,9 @@ send_result_message:
|
|||||||
send_eof(thd);
|
send_eof(thd);
|
||||||
DBUG_RETURN(FALSE);
|
DBUG_RETURN(FALSE);
|
||||||
|
|
||||||
err:
|
err:
|
||||||
ha_autocommit_or_rollback(thd, 1);
|
ha_autocommit_or_rollback(thd, 1);
|
||||||
|
end_trans(thd, ROLLBACK);
|
||||||
close_thread_tables(thd); // Shouldn't be needed
|
close_thread_tables(thd); // Shouldn't be needed
|
||||||
if (table)
|
if (table)
|
||||||
table->table=0;
|
table->table=0;
|
||||||
@ -4994,8 +4998,8 @@ mysql_discard_or_import_tablespace(THD *thd,
|
|||||||
query_cache_invalidate3(thd, table_list, 0);
|
query_cache_invalidate3(thd, table_list, 0);
|
||||||
|
|
||||||
/* The ALTER TABLE is always in its own transaction */
|
/* The ALTER TABLE is always in its own transaction */
|
||||||
error = ha_commit_stmt(thd);
|
error = ha_autocommit_or_rollback(thd, 0);
|
||||||
if (ha_commit(thd))
|
if (end_active_trans(thd))
|
||||||
error=1;
|
error=1;
|
||||||
if (error)
|
if (error)
|
||||||
goto err;
|
goto err;
|
||||||
@ -5003,7 +5007,6 @@ mysql_discard_or_import_tablespace(THD *thd,
|
|||||||
|
|
||||||
err:
|
err:
|
||||||
ha_autocommit_or_rollback(thd, error);
|
ha_autocommit_or_rollback(thd, error);
|
||||||
close_thread_tables(thd);
|
|
||||||
thd->tablespace_op=FALSE;
|
thd->tablespace_op=FALSE;
|
||||||
|
|
||||||
if (error == 0)
|
if (error == 0)
|
||||||
@ -6526,8 +6529,8 @@ view_err:
|
|||||||
VOID(pthread_mutex_unlock(&LOCK_open));
|
VOID(pthread_mutex_unlock(&LOCK_open));
|
||||||
alter_table_manage_keys(table, table->file->indexes_are_disabled(),
|
alter_table_manage_keys(table, table->file->indexes_are_disabled(),
|
||||||
alter_info->keys_onoff);
|
alter_info->keys_onoff);
|
||||||
error= ha_commit_stmt(thd);
|
error= ha_autocommit_or_rollback(thd, 0);
|
||||||
if (ha_commit(thd))
|
if (end_active_trans(thd))
|
||||||
error= 1;
|
error= 1;
|
||||||
}
|
}
|
||||||
thd->count_cuted_fields= CHECK_FIELD_IGNORE;
|
thd->count_cuted_fields= CHECK_FIELD_IGNORE;
|
||||||
@ -6615,7 +6618,7 @@ view_err:
|
|||||||
|
|
||||||
/* Need to commit before a table is unlocked (NDB requirement). */
|
/* Need to commit before a table is unlocked (NDB requirement). */
|
||||||
DBUG_PRINT("info", ("Committing before unlocking table"));
|
DBUG_PRINT("info", ("Committing before unlocking table"));
|
||||||
if (ha_commit_stmt(thd) || ha_commit(thd))
|
if (ha_autocommit_or_rollback(thd, 0) || end_active_trans(thd))
|
||||||
goto err1;
|
goto err1;
|
||||||
committed= 1;
|
committed= 1;
|
||||||
}
|
}
|
||||||
@ -7116,9 +7119,9 @@ copy_data_between_tables(TABLE *from,TABLE *to,
|
|||||||
Ensure that the new table is saved properly to disk so that we
|
Ensure that the new table is saved properly to disk so that we
|
||||||
can do a rename
|
can do a rename
|
||||||
*/
|
*/
|
||||||
if (ha_commit_stmt(thd))
|
if (ha_autocommit_or_rollback(thd, 0))
|
||||||
error=1;
|
error=1;
|
||||||
if (ha_commit(thd))
|
if (end_active_trans(thd))
|
||||||
error=1;
|
error=1;
|
||||||
|
|
||||||
err:
|
err:
|
||||||
|
@ -382,6 +382,14 @@ static udf_func *add_udf(LEX_STRING *name, Item_result ret, char *dl,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a user defined function.
|
||||||
|
|
||||||
|
@note Like implementations of other DDL/DML in MySQL, this function
|
||||||
|
relies on the caller to close the thread tables. This is done in the
|
||||||
|
end of dispatch_command().
|
||||||
|
*/
|
||||||
|
|
||||||
int mysql_create_function(THD *thd,udf_func *udf)
|
int mysql_create_function(THD *thd,udf_func *udf)
|
||||||
{
|
{
|
||||||
int error;
|
int error;
|
||||||
@ -489,7 +497,6 @@ int mysql_create_function(THD *thd,udf_func *udf)
|
|||||||
table->field[3]->store((longlong) u_d->type, TRUE);
|
table->field[3]->store((longlong) u_d->type, TRUE);
|
||||||
error = table->file->ha_write_row(table->record[0]);
|
error = table->file->ha_write_row(table->record[0]);
|
||||||
|
|
||||||
close_thread_tables(thd);
|
|
||||||
if (error)
|
if (error)
|
||||||
{
|
{
|
||||||
my_error(ER_ERROR_ON_WRITE, MYF(0), "mysql.func", error);
|
my_error(ER_ERROR_ON_WRITE, MYF(0), "mysql.func", error);
|
||||||
|
@ -803,17 +803,6 @@ int mysql_update(THD *thd,
|
|||||||
}
|
}
|
||||||
DBUG_ASSERT(transactional_table || !updated || thd->transaction.stmt.modified_non_trans_table);
|
DBUG_ASSERT(transactional_table || !updated || thd->transaction.stmt.modified_non_trans_table);
|
||||||
free_underlaid_joins(thd, select_lex);
|
free_underlaid_joins(thd, select_lex);
|
||||||
if (transactional_table)
|
|
||||||
{
|
|
||||||
if (ha_autocommit_or_rollback(thd, error >= 0))
|
|
||||||
error=1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (thd->lock)
|
|
||||||
{
|
|
||||||
mysql_unlock_tables(thd, thd->lock);
|
|
||||||
thd->lock=0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If LAST_INSERT_ID(X) was used, report X */
|
/* If LAST_INSERT_ID(X) was used, report X */
|
||||||
id= thd->arg_of_last_insert_id_function ?
|
id= thd->arg_of_last_insert_id_function ?
|
||||||
@ -1716,13 +1705,8 @@ void multi_update::abort()
|
|||||||
If not attempt to do remaining updates.
|
If not attempt to do remaining updates.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (trans_safe)
|
if (! trans_safe)
|
||||||
{
|
{
|
||||||
DBUG_ASSERT(transactional_tables);
|
|
||||||
(void) ha_autocommit_or_rollback(thd, 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DBUG_ASSERT(thd->transaction.stmt.modified_non_trans_table);
|
DBUG_ASSERT(thd->transaction.stmt.modified_non_trans_table);
|
||||||
if (do_update && table_count > 1)
|
if (do_update && table_count > 1)
|
||||||
{
|
{
|
||||||
@ -1754,11 +1738,6 @@ void multi_update::abort()
|
|||||||
thd->transaction.all.modified_non_trans_table= TRUE;
|
thd->transaction.all.modified_non_trans_table= TRUE;
|
||||||
}
|
}
|
||||||
DBUG_ASSERT(trans_safe || !updated || thd->transaction.stmt.modified_non_trans_table);
|
DBUG_ASSERT(trans_safe || !updated || thd->transaction.stmt.modified_non_trans_table);
|
||||||
|
|
||||||
if (transactional_tables)
|
|
||||||
{
|
|
||||||
(void) ha_autocommit_or_rollback(thd, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1996,12 +1975,6 @@ bool multi_update::send_eof()
|
|||||||
if (local_error != 0)
|
if (local_error != 0)
|
||||||
error_handled= TRUE; // to force early leave from ::send_error()
|
error_handled= TRUE; // to force early leave from ::send_error()
|
||||||
|
|
||||||
if (transactional_tables)
|
|
||||||
{
|
|
||||||
if (ha_autocommit_or_rollback(thd, local_error != 0))
|
|
||||||
local_error=1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (local_error > 0) // if the above log write did not fail ...
|
if (local_error > 0) // if the above log write did not fail ...
|
||||||
{
|
{
|
||||||
/* Safety: If we haven't got an error before (can happen in do_updates) */
|
/* Safety: If we haven't got an error before (can happen in do_updates) */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user