diff options
author | Monty <monty@mariadb.org> | 2020-10-15 02:25:57 +0300 |
---|---|---|
committer | Monty <monty@mariadb.org> | 2021-02-16 16:12:38 +0200 |
commit | e5c75afa9db89da46b8ebc10f65dea572e773b82 (patch) | |
tree | 82a536c999c22eb67646bbab9d1b51901b310615 | |
parent | 48544c4c1bf8f8b1373c2a76b45cfe27599cc92b (diff) | |
download | mariadb-git-e5c75afa9db89da46b8ebc10f65dea572e773b82.tar.gz |
MDEV-23842 Atomic RENAME TABLE
- Major rewrite of ddl_log.cc and ddl_log.h
- ddl_log.cc described in the beginning how the recovery works.
- ddl_log.log has unique signature and is dynamic. It's easy to
add more information to the header and other ddl blocks while still
being able to execute old ddl entries.
- IO_SIZE for ddl blocks is now dynamic. Can be changed without affecting
recovery of old logs.
- Code is more modular and is now usable outside of partition handling.
- Renamed log file to dll_recovery.log and added option --log-ddl-recovery
to allow one to specify the path & filename.
- Added ddl_log_entry_phase[], number of phases for each DDL action,
which allowed me to greatly simply set_global_from_ddl_log_entry()
- Changed how strings are stored in log entries, which allows us to
store much more information in a log entry.
- ddl log is now always created at start and deleted on normal shutdown.
This simplices things notable.
- Added probes debug_crash_here() and debug_simulate_error() to simply
crash testing and allow crash after a given number of times a probe
is executed. See comments in debug_sync.cc and rename_table.test for
how this can be used.
- Reverting failed table and view renames is done trough the ddl log.
This ensures that the ddl log is tested also outside of recovery.
- Added helper function 'handler::needs_lower_case_filenames()'
- Extend binary log with Q_XID events. ddl log handling is using this
to check if a ddl log entry was logged to the binary log (if yes,
it will be deleted from the log during ddl_log_close_binlogged_events()
mysqltest.cc changes:
- --die will now replace $variables with their values
- $error will contain the error of the last failed statement
storage engine changes:
- maria_rename() was changed to be more robust against crashes during
rename.
43 files changed, 3376 insertions, 765 deletions
diff --git a/client/mysqltest.cc b/client/mysqltest.cc index 5b9ee343bfe..69c3e2b7b4d 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -8089,9 +8089,10 @@ void handle_error(struct st_command *command, const char *err_sqlstate, DYNAMIC_STRING *ds) { int i; - DBUG_ENTER("handle_error"); + var_set_int("$errno", err_errno); + command->used_replace= 1; if (command->require_file) { @@ -9687,10 +9688,28 @@ int main(int argc, char **argv) report_or_die("Parsing is already enabled"); break; case Q_DIE: + { + char message[160]; + const char *msg; + DYNAMIC_STRING ds_echo; + + if (command->first_argument[0]) + { + /* Evaluate variables in the message */ + init_dynamic_string(&ds_echo, "", command->query_len, 256); + do_eval(&ds_echo, command->first_argument, command->end, FALSE); + strmake(message, ds_echo.str, MY_MIN(sizeof(message)-1, + ds_echo.length)); + dynstr_free(&ds_echo); + msg= message; + } + else + msg= "Explicit --die command executed"; + /* Abort test with error code and error message */ - die("%s", command->first_argument[0] ? command->first_argument : - "Explicit --die command executed"); + die("%s", msg); break; + } case Q_EXIT: /* Stop processing any more commands */ abort_flag= 1; diff --git a/libmysqld/lib_sql.cc b/libmysqld/lib_sql.cc index 9d47847e459..6956a088bcf 100644 --- a/libmysqld/lib_sql.cc +++ b/libmysqld/lib_sql.cc @@ -640,7 +640,11 @@ int init_embedded_server(int argc, char **argv, char **groups) } } - ddl_log_execute_recovery(); + if (ddl_log_execute_recovery() > 0) + { + mysql_server_end(); + return 1; + } mysql_embedded_init= 1; return 0; } diff --git a/mysql-test/main/mysqld--help.result b/mysql-test/main/mysqld--help.result index 7671dfeef59..bafb10094de 100644 --- a/mysql-test/main/mysqld--help.result +++ b/mysql-test/main/mysqld--help.result @@ -467,6 +467,9 @@ The following specify which files/extra groups are read (specified before remain ALWAYS use row-based binary logging, the security issues do not exist and the binary logging cannot break, so you can safely set this to TRUE + --log-ddl-recovery=name + Path to file used for recovery of DDL statements after a + crash --log-disabled-statements=name Don't log certain types of statements to general log. Any combination of: slave, sp @@ -1568,6 +1571,7 @@ log-bin-compress FALSE log-bin-compress-min-len 256 log-bin-index (No default value) log-bin-trust-function-creators FALSE +log-ddl-recovery ddl_recovery.log log-disabled-statements sp log-error log-isam myisam.log diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 726712dbd2d..9ab2debd9c0 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -173,6 +173,7 @@ our $opt_vs_config = $ENV{'MTR_VS_CONFIG'}; my @DEFAULT_SUITES= qw( main- archive- + atomic- binlog- binlog_encryption- client- @@ -4625,6 +4626,7 @@ sub extract_warning_lines ($$) { qr/Slave SQL thread retried transaction/, qr/Slave \(additional info\)/, qr/Incorrect information in file/, + qr/Simulating error for/, qr/Slave I\/O: Get master SERVER_ID failed with error:.*/, qr/Slave I\/O: Get master clock failed with error:.*/, qr/Slave I\/O: Get master COLLATION_SERVER failed with error:.*/, diff --git a/mysql-test/suite/atomic/rename_case.result b/mysql-test/suite/atomic/rename_case.result new file mode 100644 index 00000000000..4b58c555cf2 --- /dev/null +++ b/mysql-test/suite/atomic/rename_case.result @@ -0,0 +1,52 @@ +create database test2; +# +# Testing rename error in different places +# +create table t1 (a int); +create table T2 (b int); +create table t3 (c int); +create table T4 (d int); +insert into t1 values(1); +insert into T2 values(2); +insert into t3 values(3); +insert into T4 values(4); +create temporary table tmp1 (a int); +create temporary table tmp2 (b int); +create temporary table tmp3 (c int); +create temporary table tmp4 (d int); +insert into tmp1 values(11); +insert into tmp2 values(22); +insert into tmp3 values(33); +insert into tmp4 values(44); +rename table t3 to T4, t1 to t5, T2 to t1, t5 to T2; +ERROR 42S01: Table 'T4' already exists +rename table t1 to t5, t3 to T4, T2 to t1, t5 to T2; +ERROR 42S01: Table 'T4' already exists +rename table t1 to t5, T2 to t1, t3 to T4, t5 to T2; +ERROR 42S01: Table 'T4' already exists +rename table t1 to t5, T2 to t1, t5 to T2, t3 to T4; +ERROR 42S01: Table 'T4' already exists +# Try failed rename using two databases +rename table test.t1 to test2.t5, test.T2 to test.t1, t5 to test.T2; +ERROR 42S02: Table 'test.t5' doesn't exist +select t1.a+T2.b+t3.c+T4.d from t1,T2,t3,T4; +t1.a+T2.b+t3.c+T4.d +10 +select * from t5; +ERROR 42S02: Table 'test.t5' doesn't exist +T2.MYD +T2.MYI +T2.frm +T4.MYD +T4.MYI +T4.frm +db.opt +t1.MYD +t1.MYI +t1.frm +t3.MYD +t3.MYI +t3.frm +# Cleanup +drop table t1,T2,t3,T4; +drop database test2; diff --git a/mysql-test/suite/atomic/rename_case.test b/mysql-test/suite/atomic/rename_case.test new file mode 100644 index 00000000000..17151094ca2 --- /dev/null +++ b/mysql-test/suite/atomic/rename_case.test @@ -0,0 +1,54 @@ +# +# This tests tries to cover renames with tables in different cases to ensure +# that lower case table names works +# + +create database test2; +let $mysqld_datadir= `select @@datadir`; + +--echo # +--echo # Testing rename error in different places +--echo # + +create table t1 (a int); +create table T2 (b int); +create table t3 (c int); +create table T4 (d int); + +insert into t1 values(1); +insert into T2 values(2); +insert into t3 values(3); +insert into T4 values(4); + +create temporary table tmp1 (a int); +create temporary table tmp2 (b int); +create temporary table tmp3 (c int); +create temporary table tmp4 (d int); + +insert into tmp1 values(11); +insert into tmp2 values(22); +insert into tmp3 values(33); +insert into tmp4 values(44); + +--error ER_TABLE_EXISTS_ERROR +rename table t3 to T4, t1 to t5, T2 to t1, t5 to T2; +--error ER_TABLE_EXISTS_ERROR +rename table t1 to t5, t3 to T4, T2 to t1, t5 to T2; +--error ER_TABLE_EXISTS_ERROR +rename table t1 to t5, T2 to t1, t3 to T4, t5 to T2; +--error ER_TABLE_EXISTS_ERROR +rename table t1 to t5, T2 to t1, t5 to T2, t3 to T4; + +--echo # Try failed rename using two databases +--error ER_NO_SUCH_TABLE +rename table test.t1 to test2.t5, test.T2 to test.t1, t5 to test.T2; + +select t1.a+T2.b+t3.c+T4.d from t1,T2,t3,T4; +--error ER_NO_SUCH_TABLE +select * from t5; + +--list_files $mysqld_datadir/test + +--echo # Cleanup +drop table t1,T2,t3,T4; +drop database test2; diff --git a/mysql-test/suite/atomic/rename_combinations.result b/mysql-test/suite/atomic/rename_combinations.result new file mode 100644 index 00000000000..c7536ae8c7e --- /dev/null +++ b/mysql-test/suite/atomic/rename_combinations.result @@ -0,0 +1,157 @@ +create database test2; +# +# Testing rename error in different places +# +create table t1 (a int); +create table t2 (b int); +create table t3 (c int); +create table t4 (d int); +insert into t1 values(1); +insert into t2 values(2); +insert into t3 values(3); +insert into t4 values(4); +create temporary table tmp1 (a int); +create temporary table tmp2 (b int); +create temporary table tmp3 (c int); +create temporary table tmp4 (d int); +insert into tmp1 values(11); +insert into tmp2 values(22); +insert into tmp3 values(33); +insert into tmp4 values(44); +rename table t3 to t4, t1 to t5, t2 to t1, t5 to t2; +ERROR 42S01: Table 't4' already exists +rename table t1 to t5, t3 to t4, t2 to t1, t5 to t2; +ERROR 42S01: Table 't4' already exists +rename table t1 to t5, t2 to t1, t3 to t4, t5 to t2; +ERROR 42S01: Table 't4' already exists +rename table t1 to t5, t2 to t1, t5 to t2, t3 to t4; +ERROR 42S01: Table 't4' already exists +# Try failed rename using two databases +rename table test.t1 to test2.t5, test.t2 to test.t1, t5 to test.t2; +ERROR 42S02: Table 'test.t5' doesn't exist +select t1.a+t2.b+t3.c+t4.d from t1,t2,t3,t4; +t1.a+t2.b+t3.c+t4.d +10 +select * from t5; +ERROR 42S02: Table 'test.t5' doesn't exist +# +# Testing rename error in different places with temporary tables +# +rename table tmp3 to tmp4, tmp1 to t5, tmp2 to tmp1, t5 to tmp1; +ERROR 42S01: Table 'tmp4' already exists +rename table tmp1 to t5, tmp3 to tmp4, tmp2 to tmp1, t5 to tmp1; +ERROR 42S01: Table 'tmp4' already exists +rename table tmp1 to t5, tmp2 to tmp1, tmp3 to tmp4, t5 to tmp1; +ERROR 42S01: Table 'tmp4' already exists +rename table tmp1 to t5, tmp2 to tmp1, t5 to tmp1, tmp3 to tmp4; +ERROR 42S01: Table 'tmp1' already exists +select tmp1.a+tmp2.b+tmp3.c+tmp4.d from tmp1,tmp2,tmp3,tmp4; +tmp1.a+tmp2.b+tmp3.c+tmp4.d +110 +select * from t5; +ERROR 42S02: Table 'test.t5' doesn't exist +# +# Testing combinations of rename normal and temporary tables +# +rename table t1 to t5, t2 to t1, t5 to t2, tmp3 to tmp4, tmp1 to t5, tmp2 to tmp1, t5 to tmp1; +ERROR 42S01: Table 'tmp4' already exists +rename table t1 to t5, t2 to t1, t5 to t2, tmp1 to t5, tmp3 to tmp4, tmp2 to tmp1, t5 to tmp1; +ERROR 42S01: Table 'tmp4' already exists +rename table t1 to t5, t2 to t1, t5 to t2, tmp1 to t5, tmp2 to tmp1, tmp3 to tmp4, t5 to tmp1; +ERROR 42S01: Table 'tmp4' already exists +rename table t1 to t5, t2 to t1, t5 to t2, tmp1 to t5, tmp2 to tmp1, t5 to tmp1, t3 to t4; +ERROR 42S01: Table 'tmp1' already exists +rename table t1 to t5, tmp2 to tmp5, t2 to t1, tmp2 to tmp1, t5 to t2, tmp5 to tmp1, t8 to t9; +ERROR 42S02: Table 'test.tmp2' doesn't exist +select t1.a+t2.b+t3.c+t4.d from t1,t2,t3,t4; +t1.a+t2.b+t3.c+t4.d +10 +select tmp1.a+tmp2.b+tmp3.c+tmp4.d from tmp1,tmp2,tmp3,tmp4; +tmp1.a+tmp2.b+tmp3.c+tmp4.d +110 +drop table tmp1,tmp2,tmp3,tmp4; +# +# Similar tests with triggers +# +create trigger t1_trg before insert on t1 for each row +begin +if isnull(new.a) then +set new.a:= 10; +end if; +end| +create trigger t2_trg before insert on t2 for each row +begin +if isnull(new.b) then +set new.b:= 100; +end if; +end| +create trigger t3_trg before insert on t3 for each row +begin +if isnull(new.c) then +set new.c:= 1000; +end if; +end| +rename table t3 to t4, t1 to t5, t2 to t1, t5 to t2; +ERROR 42S01: Table 't4' already exists +rename table t1 to t5, t3 to t4, t2 to t1, t5 to t2; +ERROR 42S01: Table 't4' already exists +rename table t1 to t5, t2 to t1, t3 to t4, t5 to t2; +ERROR 42S01: Table 't4' already exists +rename table t1 to t5, t2 to t1, t5 to t2, t3 to t4; +ERROR 42S01: Table 't4' already exists +# Test of move table between databases +rename table t4 to test2.t5, t2 to t4, test2.t5 to t2, t1 to test2.t6; +ERROR HY000: Trigger in wrong schema +show triggers; +Trigger Event Table Statement Timing Created sql_mode Definer character_set_client collation_connection Database Collation +t1_trg INSERT t1 begin +if isnull(new.a) then +set new.a:= 10; +end if; +end BEFORE # STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION root@localhost latin1 latin1_swedish_ci latin1_swedish_ci +t2_trg INSERT t2 begin +if isnull(new.b) then +set new.b:= 100; +end if; +end BEFORE # STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION root@localhost latin1 latin1_swedish_ci latin1_swedish_ci +t3_trg INSERT t3 begin +if isnull(new.c) then +set new.c:= 1000; +end if; +end BEFORE # STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION root@localhost latin1 latin1_swedish_ci latin1_swedish_ci +select t1.a+t2.b+t3.c+t4.d from t1,t2,t3,t4; +t1.a+t2.b+t3.c+t4.d +10 +insert into t1 values(null); +insert into t2 values(null); +insert into t3 values(null); +select (select sum(t1.a) from t1)+ (select sum(t2.b) from t2) + (select sum(t3.c) from t3)+ (select sum(t4.d) from t4); +(select sum(t1.a) from t1)+ (select sum(t2.b) from t2) + (select sum(t3.c) from t3)+ (select sum(t4.d) from t4) +1120 +drop trigger t1_trg; +drop trigger t2_trg; +drop trigger t3_trg; +# +# Test with views +# +create view v1 as select * from t1; +create view v2 as select * from t2; +create view v3 as select * from t3; +create view v4 as select * from t4; +rename table v3 to v4, v1 to t5, v2 to v1, t5 to v2; +ERROR 42S01: Table 'v4' already exists +rename table v1 to t5, v3 to v4, v2 to v1, t5 to v2; +ERROR 42S01: Table 'v4' already exists +rename table v1 to t5, v2 to v1, v3 to v4, t5 to v2; +ERROR 42S01: Table 'v4' already exists +rename table v1 to t5, v2 to v1, t5 to v2, v3 to v4; +ERROR 42S01: Table 'v4' already exists +# Try failed rename using two databases +rename table test.v1 to test.v5, test.v2 to test.v1, test.v3 to test2.v2, non_existing_view to another_non_existing_view; +ERROR HY000: Changing schema from 'test' to 'test2' is not allowed +select (select sum(v1.a) from v1)+ (select sum(v2.b) from v2) + (select sum(v3.c) from v3)+ (select sum(v4.d) from v4); +(select sum(v1.a) from v1)+ (select sum(v2.b) from v2) + (select sum(v3.c) from v3)+ (select sum(v4.d) from v4) +1120 +drop view v1,v2,v3,v4; +drop table t1, t2, t3, t4; +drop database test2; diff --git a/mysql-test/suite/atomic/rename_combinations.test b/mysql-test/suite/atomic/rename_combinations.test new file mode 100644 index 00000000000..b0b5074e56e --- /dev/null +++ b/mysql-test/suite/atomic/rename_combinations.test @@ -0,0 +1,171 @@ +# +# This tests tries cover most of the recovery cases in +# DDL_LOG_RENAME_TABLE_ACTION and do_rename() +# This test does not intend to crash the server +# It's a complement to main.rename +# + +create database test2; + +--echo # +--echo # Testing rename error in different places +--echo # + +create table t1 (a int); +create table t2 (b int); +create table t3 (c int); +create table t4 (d int); + +insert into t1 values(1); +insert into t2 values(2); +insert into t3 values(3); +insert into t4 values(4); + +create temporary table tmp1 (a int); +create temporary table tmp2 (b int); +create temporary table tmp3 (c int); +create temporary table tmp4 (d int); + +insert into tmp1 values(11); +insert into tmp2 values(22); +insert into tmp3 values(33); +insert into tmp4 values(44); + +--error ER_TABLE_EXISTS_ERROR +rename table t3 to t4, t1 to t5, t2 to t1, t5 to t2; +--error ER_TABLE_EXISTS_ERROR +rename table t1 to t5, t3 to t4, t2 to t1, t5 to t2; +--error ER_TABLE_EXISTS_ERROR +rename table t1 to t5, t2 to t1, t3 to t4, t5 to t2; +--error ER_TABLE_EXISTS_ERROR +rename table t1 to t5, t2 to t1, t5 to t2, t3 to t4; + +--echo # Try failed rename using two databases +--error ER_NO_SUCH_TABLE +rename table test.t1 to test2.t5, test.t2 to test.t1, t5 to test.t2; + +select t1.a+t2.b+t3.c+t4.d from t1,t2,t3,t4; +--error ER_NO_SUCH_TABLE +select * from t5; + +--echo # +--echo # Testing rename error in different places with temporary tables +--echo # + +--error ER_TABLE_EXISTS_ERROR +rename table tmp3 to tmp4, tmp1 to t5, tmp2 to tmp1, t5 to tmp1; +--error ER_TABLE_EXISTS_ERROR +rename table tmp1 to t5, tmp3 to tmp4, tmp2 to tmp1, t5 to tmp1; +--error ER_TABLE_EXISTS_ERROR +rename table tmp1 to t5, tmp2 to tmp1, tmp3 to tmp4, t5 to tmp1; +--error ER_TABLE_EXISTS_ERROR +rename table tmp1 to t5, tmp2 to tmp1, t5 to tmp1, tmp3 to tmp4; + +select tmp1.a+tmp2.b+tmp3.c+tmp4.d from tmp1,tmp2,tmp3,tmp4; +--error ER_NO_SUCH_TABLE +select * from t5; + +--echo # +--echo # Testing combinations of rename normal and temporary tables +--echo # + +--error ER_TABLE_EXISTS_ERROR +rename table t1 to t5, t2 to t1, t5 to t2, tmp3 to tmp4, tmp1 to t5, tmp2 to tmp1, t5 to tmp1; +--error ER_TABLE_EXISTS_ERROR +rename table t1 to t5, t2 to t1, t5 to t2, tmp1 to t5, tmp3 to tmp4, tmp2 to tmp1, t5 to tmp1; +--error ER_TABLE_EXISTS_ERROR +rename table t1 to t5, t2 to t1, t5 to t2, tmp1 to t5, tmp2 to tmp1, tmp3 to tmp4, t5 to tmp1; +--error ER_TABLE_EXISTS_ERROR +rename table t1 to t5, t2 to t1, t5 to t2, tmp1 to t5, tmp2 to tmp1, t5 to tmp1, t3 to t4; + +--error ER_NO_SUCH_TABLE +rename table t1 to t5, tmp2 to tmp5, t2 to t1, tmp2 to tmp1, t5 to t2, tmp5 to tmp1, t8 to t9; + +select t1.a+t2.b+t3.c+t4.d from t1,t2,t3,t4; +select tmp1.a+tmp2.b+tmp3.c+tmp4.d from tmp1,tmp2,tmp3,tmp4; + +drop table tmp1,tmp2,tmp3,tmp4; + +--echo # +--echo # Similar tests with triggers +--echo # + +delimiter |; +create trigger t1_trg before insert on t1 for each row +begin + if isnull(new.a) then + set new.a:= 10; + end if; +end| +create trigger t2_trg before insert on t2 for each row +begin + if isnull(new.b) then + set new.b:= 100; + end if; +end| +create trigger t3_trg before insert on t3 for each row +begin + if isnull(new.c) then + set new.c:= 1000; + end if; +end| + +delimiter ;| + +--error ER_TABLE_EXISTS_ERROR +rename table t3 to t4, t1 to t5, t2 to t1, t5 to t2; +--error ER_TABLE_EXISTS_ERROR +rename table t1 to t5, t3 to t4, t2 to t1, t5 to t2; +--error ER_TABLE_EXISTS_ERROR +rename table t1 to t5, t2 to t1, t3 to t4, t5 to t2; +--error ER_TABLE_EXISTS_ERROR +rename table t1 to t5, t2 to t1, t5 to t2, t3 to t4; + +--echo # Test of move table between databases +--error ER_TRG_IN_WRONG_SCHEMA +rename table t4 to test2.t5, t2 to t4, test2.t5 to t2, t1 to test2.t6; + +--replace_column 6 # +show triggers; + +select t1.a+t2.b+t3.c+t4.d from t1,t2,t3,t4; +insert into t1 values(null); +insert into t2 values(null); +insert into t3 values(null); +select (select sum(t1.a) from t1)+ (select sum(t2.b) from t2) + (select sum(t3.c) from t3)+ (select sum(t4.d) from t4); + +drop trigger t1_trg; +drop trigger t2_trg; +drop trigger t3_trg; + +--echo # +--echo # Test with views +--echo # + +create view v1 as select * from t1; +create view v2 as select * from t2; +create view v3 as select * from t3; +create view v4 as select * from t4; + +--error ER_TABLE_EXISTS_ERROR +rename table v3 to v4, v1 to t5, v2 to v1, t5 to v2; +--error ER_TABLE_EXISTS_ERROR +rename table v1 to t5, v3 to v4, v2 to v1, t5 to v2; +--error ER_TABLE_EXISTS_ERROR +rename table v1 to t5, v2 to v1, v3 to v4, t5 to v2; +--error ER_TABLE_EXISTS_ERROR +rename table v1 to t5, v2 to v1, t5 to v2, v3 to v4; + +--echo # Try failed rename using two databases +--error ER_FORBID_SCHEMA_CHANGE +rename table test.v1 to test.v5, test.v2 to test.v1, test.v3 to test2.v2, non_existing_view to another_non_existing_view; + +select (select sum(v1.a) from v1)+ (select sum(v2.b) from v2) + (select sum(v3.c) from v3)+ (select sum(v4.d) from v4); + +drop view v1,v2,v3,v4; + +# +# Clean up +# +drop table t1, t2, t3, t4; +drop database test2; diff --git a/mysql-test/suite/atomic/rename_table.result b/mysql-test/suite/atomic/rename_table.result new file mode 100644 index 00000000000..dc98cc816b2 --- /dev/null +++ b/mysql-test/suite/atomic/rename_table.result @@ -0,0 +1,150 @@ +"engine: myisam crash point: definition_file_after_create position: 1" +"engine: myisam crash point: definition_file_after_create position: 2" +"engine: myisam crash point: definition_file_after_create position: 3" +"engine: myisam crash point: definition_file_after_create position: 4" +"engine: myisam crash point: definition_file_after_create position: 5" +"engine: myisam crash point: ddl_log_rename_before_rename_table position: 1" +"engine: myisam crash point: ddl_log_rename_before_rename_table position: 2" +"engine: myisam crash point: ddl_log_rename_before_rename_table position: 3" +"engine: myisam crash point: ddl_log_rename_before_rename_table position: 4" +"engine: myisam crash point: ddl_log_rename_before_rename_table position: 5" +"engine: myisam crash point: ddl_log_rename_before_phase_trigger position: 1" +"engine: myisam crash point: ddl_log_rename_before_phase_trigger position: 2" +"engine: myisam crash point: ddl_log_rename_before_phase_trigger position: 3" +"engine: myisam crash point: ddl_log_rename_before_phase_trigger position: 4" +"engine: myisam crash point: ddl_log_rename_before_phase_trigger position: 5" +"engine: myisam crash point: ddl_log_rename_before_rename_trigger position: 1" +"engine: myisam crash point: ddl_log_rename_before_rename_trigger position: 2" +"engine: myisam crash point: ddl_log_rename_before_rename_trigger position: 3" +"engine: myisam crash point: ddl_log_rename_before_rename_trigger position: 4" +"engine: myisam crash point: ddl_log_rename_before_rename_trigger position: 5" +"engine: myisam crash point: ddl_log_rename_before_stat_tables position: 1" +"engine: myisam crash point: ddl_log_rename_before_stat_tables position: 2" +"engine: myisam crash point: ddl_log_rename_before_stat_tables position: 3" +"engine: myisam crash point: ddl_log_rename_before_stat_tables position: 4" +"engine: myisam crash point: ddl_log_rename_before_stat_tables position: 5" +"engine: myisam crash point: ddl_log_rename_after_stat_tables position: 1" +"engine: myisam crash point: ddl_log_rename_after_stat_tables position: 2" +"engine: myisam crash point: ddl_log_rename_after_stat_tables position: 3" +"engine: myisam crash point: ddl_log_rename_after_stat_tables position: 4" +"engine: myisam crash point: ddl_log_rename_after_stat_tables position: 5" +"engine: aria crash point: definition_file_after_create position: 1" +"engine: aria crash point: definition_file_after_create position: 2" +"engine: aria crash point: definition_file_after_create position: 3" +"engine: aria crash point: definition_file_after_create position: 4" +"engine: aria crash point: definition_file_after_create position: 5" +"engine: aria crash point: ddl_log_rename_before_rename_table position: 1" +"engine: aria crash point: ddl_log_rename_before_rename_table position: 2" +"engine: aria crash point: ddl_log_rename_before_rename_table position: 3" +"engine: aria crash point: ddl_log_rename_before_rename_table position: 4" +"engine: aria crash point: ddl_log_rename_before_rename_table position: 5" +"engine: aria crash point: ddl_log_rename_before_phase_trigger position: 1" +"engine: aria crash point: ddl_log_rename_before_phase_trigger position: 2" +"engine: aria crash point: ddl_log_rename_before_phase_trigger position: 3" +"engine: aria crash point: ddl_log_rename_before_phase_trigger position: 4" +"engine: aria crash point: ddl_log_rename_before_phase_trigger position: 5" +"engine: aria crash point: ddl_log_rename_before_rename_trigger position: 1" +"engine: aria crash point: ddl_log_rename_before_rename_trigger position: 2" +"engine: aria crash point: ddl_log_rename_before_rename_trigger position: 3" +"engine: aria crash point: ddl_log_rename_before_rename_trigger position: 4" +"engine: aria crash point: ddl_log_rename_before_rename_trigger position: 5" +"engine: aria crash point: ddl_log_rename_before_stat_tables position: 1" +"engine: aria crash point: ddl_log_rename_before_stat_tables position: 2" +"engine: aria crash point: ddl_log_rename_before_stat_tables position: 3" +"engine: aria crash point: ddl_log_rename_before_stat_tables position: 4" +"engine: aria crash point: ddl_log_rename_before_stat_tables position: 5" +"engine: aria crash point: ddl_log_rename_after_stat_tables position: 1" +"engine: aria crash point: ddl_log_rename_after_stat_tables position: 2" +"engine: aria crash point: ddl_log_rename_after_stat_tables position: 3" +"engine: aria crash point: ddl_log_rename_after_stat_tables position: 4" +"engine: aria crash point: ddl_log_rename_after_stat_tables position: 5" +"engine: aria_notrans crash point: definition_file_after_create position: 1" +"engine: aria_notrans crash point: definition_file_after_create position: 2" +"engine: aria_notrans crash point: definition_file_after_create position: 3" +"engine: aria_notrans crash point: definition_file_after_create position: 4" +"engine: aria_notrans crash point: definition_file_after_create position: 5" +"engine: aria_notrans crash point: ddl_log_rename_before_rename_table position: 1" +"engine: aria_notrans crash point: ddl_log_rename_before_rename_table position: 2" +"engine: aria_notrans crash point: ddl_log_rename_before_rename_table position: 3" +"engine: aria_notrans crash point: ddl_log_rename_before_rename_table position: 4" +"engine: aria_notrans crash point: ddl_log_rename_before_rename_table position: 5" +"engine: aria_notrans crash point: ddl_log_rename_before_phase_trigger position: 1" +"engine: aria_notrans crash point: ddl_log_rename_before_phase_trigger position: 2" +"engine: aria_notrans crash point: ddl_log_rename_before_phase_trigger position: 3" +"engine: aria_notrans crash point: ddl_log_rename_before_phase_trigger position: 4" +"engine: aria_notrans crash point: ddl_log_rename_before_phase_trigger position: 5" +"engine: aria_notrans crash point: ddl_log_rename_before_rename_trigger position: 1" +"engine: aria_notrans crash point: ddl_log_rename_before_rename_trigger position: 2" +"engine: aria_notrans crash point: ddl_log_rename_before_rename_trigger position: 3" +"engine: aria_notrans crash point: ddl_log_rename_before_rename_trigger position: 4" +"engine: aria_notrans crash point: ddl_log_rename_before_rename_trigger position: 5" +"engine: aria_notrans crash point: ddl_log_rename_before_stat_tables position: 1" +"engine: aria_notrans crash point: ddl_log_rename_before_stat_tables position: 2" +"engine: aria_notrans crash point: ddl_log_rename_before_stat_tables position: 3" +"engine: aria_notrans crash point: ddl_log_rename_before_stat_tables position: 4" +"engine: aria_notrans crash point: ddl_log_rename_before_stat_tables position: 5" +"engine: aria_notrans crash point: ddl_log_rename_after_stat_tables position: 1" +"engine: aria_notrans crash point: ddl_log_rename_after_stat_tables position: 2" +"engine: aria_notrans crash point: ddl_log_rename_after_stat_tables position: 3" +"engine: aria_notrans crash point: ddl_log_rename_after_stat_tables position: 4" +"engine: aria_notrans crash point: ddl_log_rename_after_stat_tables position: 5" +"engine: innodb crash point: definition_file_after_create position: 1" +"engine: innodb crash point: definition_file_after_create position: 2" +"engine: innodb crash point: definition_file_after_create position: 3" +"engine: innodb crash point: definition_file_after_create position: 4" +"engine: innodb crash point: definition_file_after_create position: 5" +"engine: innodb crash point: ddl_log_rename_before_rename_table position: 1" +"engine: innodb crash point: ddl_log_rename_before_rename_table position: 2" +"engine: innodb crash point: ddl_log_rename_before_rename_table position: 3" +"engine: innodb crash point: ddl_log_rename_before_rename_table position: 4" +"engine: innodb crash point: ddl_log_rename_before_rename_table position: 5" +"engine: innodb crash point: ddl_log_rename_before_phase_trigger position: 1" +"engine: innodb crash point: ddl_log_rename_before_phase_trigger position: 2" +"engine: innodb crash point: ddl_log_rename_before_phase_trigger position: 3" +"engine: innodb crash point: ddl_log_rename_before_phase_trigger position: 4" +"engine: innodb crash point: ddl_log_rename_before_phase_trigger position: 5" +"engine: innodb crash point: ddl_log_rename_before_rename_trigger position: 1" +"engine: innodb crash point: ddl_log_rename_before_rename_trigger position: 2" +"engine: innodb crash point: ddl_log_rename_before_rename_trigger position: 3" +"engine: innodb crash point: ddl_log_rename_before_rename_trigger position: 4" +"engine: innodb crash point: ddl_log_rename_before_rename_trigger position: 5" +"engine: innodb crash point: ddl_log_rename_before_stat_tables position: 1" +"engine: innodb crash point: ddl_log_rename_before_stat_tables position: 2" +"engine: innodb crash point: ddl_log_rename_before_stat_tables position: 3" +"engine: innodb crash point: ddl_log_rename_before_stat_tables position: 4" +"engine: innodb crash point: ddl_log_rename_before_stat_tables position: 5" +"engine: innodb crash point: ddl_log_rename_after_stat_tables position: 1" +"engine: innodb crash point: ddl_log_rename_after_stat_tables position: 2" +"engine: innodb crash point: ddl_log_rename_after_stat_tables position: 3" +"engine: innodb crash point: ddl_log_rename_after_stat_tables position: 4" +"engine: innodb crash point: ddl_log_rename_after_stat_tables position: 5" +"engine: csv crash point: definition_file_after_create position: 1" +"engine: csv crash point: definition_file_after_create position: 2" +"engine: csv crash point: definition_file_after_create position: 3" +"engine: csv crash point: definition_file_after_create position: 4" +"engine: csv crash point: definition_file_after_create position: 5" +"engine: csv crash point: ddl_log_rename_before_rename_table position: 1" +"engine: csv crash point: ddl_log_rename_before_rename_table position: 2" +"engine: csv crash point: ddl_log_rename_before_rename_table position: 3" +"engine: csv crash point: ddl_log_rename_before_rename_table position: 4" +"engine: csv crash point: ddl_log_rename_before_rename_table position: 5" +"engine: csv crash point: ddl_log_rename_before_phase_trigger position: 1" +"engine: csv crash point: ddl_log_rename_before_phase_trigger position: 2" +"engine: csv crash point: ddl_log_rename_before_phase_trigger position: 3" +"engine: csv crash point: ddl_log_rename_before_phase_trigger position: 4" +"engine: csv crash point: ddl_log_rename_before_phase_trigger position: 5" +"engine: csv crash point: ddl_log_rename_before_rename_trigger position: 1" +"engine: csv crash point: ddl_log_rename_before_rename_trigger position: 2" +"engine: csv crash point: ddl_log_rename_before_rename_trigger position: 3" +"engine: csv crash point: ddl_log_rename_before_rename_trigger position: 4" +"engine: csv crash point: ddl_log_rename_before_rename_trigger position: 5" +"engine: csv crash point: ddl_log_rename_before_stat_tables position: 1" +"engine: csv crash point: ddl_log_rename_before_stat_tables position: 2" +"engine: csv crash point: ddl_log_rename_before_stat_tables position: 3" +"engine: csv crash point: ddl_log_rename_before_stat_tables position: 4" +"engine: csv crash point: ddl_log_rename_before_stat_tables position: 5" +"engine: csv crash point: ddl_log_rename_after_stat_tables position: 1" +"engine: csv crash point: ddl_log_rename_after_stat_tables position: 2" +"engine: csv crash point: ddl_log_rename_after_stat_tables position: 3" +"engine: csv crash point: ddl_log_rename_after_stat_tables position: 4" +"engine: csv crash point: ddl_log_rename_after_stat_tables position: 5" diff --git a/mysql-test/suite/atomic/rename_table.test b/mysql-test/suite/atomic/rename_table.test new file mode 100644 index 00000000000..c128d548aef --- /dev/null +++ b/mysql-test/suite/atomic/rename_table.test @@ -0,0 +1,153 @@ +--source include/have_debug.inc +--source include/have_innodb.inc +--source include/have_csv.inc +--source include/not_valgrind.inc +--source include/not_embedded.inc + +# +# Testing of atomic rename with forced crashes in a lot of different places +# + +let $engine_count=5; +let $engines='myisam','aria','aria_notrans','innodb','csv'; + +let $crash_count=6; +let $crash_points='definition_file_after_create','ddl_log_rename_before_rename_table','ddl_log_rename_before_phase_trigger','ddl_log_rename_before_rename_trigger','ddl_log_rename_before_stat_tables','ddl_log_rename_after_stat_tables'; + +# Number of renames in the tested statement +let $renames=5; + +let $old_debug=`select @@debug_dbug`; + +let $e=0; +--disable_query_log +while ($e < $engine_count) +{ + inc $e; + let $engine=`select ELT($e, $engines)`; + let $default_engine=$engine; + let $extra_option=; + + if ($engine == "aria") + { + let $extra_option=transactional=1; + } + if ($engine == "aria_notrans") + { + let $default_engine="aria"; + let $extra_option=transactional=0; + } + + --eval set @@default_storage_engine=$default_engine + --eval create table t1 (a int not null) $extra_option; + --eval create table t2 (b int not null) $extra_option; + --eval create table t3 (c int not null) $extra_option; + --eval create table t4 (d int not null) $extra_option; + insert into t1 values(1); + insert into t2 values(2); + insert into t3 values(3); + insert into t4 values(4); + + delimiter |; + create trigger t1_trg before insert on t1 for each row + begin + if isnull(new.a) then + set new.a:= 1000; + end if; + end| + create trigger t2_trg before insert on t2 for each row + begin + if isnull(new.b) then + set new.b:= 2000; + end if; + end| + create trigger t3_trg before insert on t3 for each row + begin + if isnull(new.c) then + set new.c:= 4000; + end if; + end| + create trigger t4_trg before insert on t4 for each row + begin + if isnull(new.d) then + set new.d:= 8000; + end if; + end| + delimiter ;| + + let $c=0; + while ($c < $crash_count) + { + inc $c; + let $crash=`select ELT($c, $crash_points)`; + let $r=0; + while ($r < $renames) + { + inc $r; + echo "engine: $engine crash point: $crash position: $r"; + flush tables; + + --exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect + --disable_reconnect + --eval set @@debug_dbug="+d,$crash",@debug_crash_counter=$r + let $errno=0; + --error 0,2013 + rename table t1 to t5, t2 to t1, t5 to t2, t4 to t5, t3 to t4; + let $error=$errno; + --enable_reconnect + --source include/wait_until_connected_again.inc + --disable_query_log + --eval set @@debug_dbug="$old_debug" + + if ($error == 0) + { + echo "No crash!"; + # No crash, rename things back + rename table t4 to t3, t5 to t4, t2 to t5, t1 to t2, t5 to t1; + } + + # Ensure that the tables are back to original + let $res=`select t1.a+t2.b+t3.c+t4.d from t1,t2,t3,t4`; + if ($res != 10) + { + die "Got result $res when 10 was expected"; + } + + # Ensure that triggers work + insert into t1 values(null); + insert into t2 values(null); + insert into t3 values(null); + insert into t4 values(null); + let $res=`select (select sum(t1.a) from t1)+ (select sum(t2.b) from t2) + (select sum(t3.c) from t3)+ (select sum(t4.d) from t4)`; + if ($res != 15010) + { + die "Got result $res when 15010 was expected"; + } + # Restore tables + delete from t1 where a > 100; + delete from t2 where b > 100; + delete from t3 where c > 100; + delete from t4 where d > 100; + } + } + + # Last test, check that rename really worked + rename table t1 to t5, t2 to t1, t5 to t2, t4 to t5, t3 to t4; + insert into t1 values(null); + insert into t2 values(null); + insert into t5 values(null); + insert into t4 values(null); + let $res=`select (select sum(t1.b) from t1)+ (select sum(t2.a) from t2) + (select sum(t4.c) from t4)+ (select sum(t5.d) from t5)`; + if ($res != 15010) + { + die "Got result $res when 15010 was expected"; + } + let $res=`select (select count(*)=2 from t1) + (select count(*)=2 from t2) + (select count(*)=2 from t4)+ (select count(*)=2 from t5)`; + if ($res != 4) + { + die "Got result $res when 4 was expected"; + } + + drop table t1,t2,t4,t5; +} +--enable_query_log diff --git a/mysql-test/suite/atomic/rename_table_binlog.result b/mysql-test/suite/atomic/rename_table_binlog.result new file mode 100644 index 00000000000..9795b6ef06e --- /dev/null +++ b/mysql-test/suite/atomic/rename_table_binlog.result @@ -0,0 +1,147 @@ +RESET MASTER; +"engine: myisam crash point: ddl_log_rename_before_binlog position: 1" +"engine: myisam crash point: ddl_log_rename_after_binlog position: 1" +"engine: aria crash point: ddl_log_rename_before_binlog position: 1" +"engine: aria crash point: ddl_log_rename_after_binlog position: 1" +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000002 # Gtid # # BEGIN GTID #-#-# +master-bin.000002 # Query # # use `test`; insert into t1 values(null) +master-bin.000002 # Query # # COMMIT +master-bin.000002 # Gtid # # BEGIN GTID #-#-# +master-bin.000002 # Query # # use `test`; insert into t2 values(null) +master-bin.000002 # Query # # COMMIT +master-bin.000002 # Gtid # # BEGIN GTID #-#-# +master-bin.000002 # Query # # use `test`; insert into t3 values(null) +master-bin.000002 # Query # # COMMIT +master-bin.000002 # Gtid # # BEGIN GTID #-#-# +master-bin.000002 # Query # # use `test`; insert into t4 values(null) +master-bin.000002 # Query # # COMMIT +master-bin.000002 # Gtid # # BEGIN GTID #-#-# +master-bin.000002 # Query # # use `test`; delete from t1 where a > 100 +master-bin.000002 # Query # # COMMIT +master-bin.000002 # Gtid # # BEGIN GTID #-#-# +master-bin.000002 # Query # # use `test`; delete from t2 where b > 100 +master-bin.000002 # Query # # COMMIT +master-bin.000002 # Gtid # # BEGIN GTID #-#-# +master-bin.000002 # Query # # use `test`; delete from t3 where c > 100 +master-bin.000002 # Query # # COMMIT +master-bin.000002 # Gtid # # BEGIN GTID #-#-# +master-bin.000002 # Query # # use `test`; delete from t4 where d > 100 +master-bin.000002 # Query # # COMMIT +master-bin.000002 # Gtid # # GTID #-#-# +master-bin.000002 # Query # # use `test`; flush tables +master-bin.000002 # Gtid # # GTID #-#-# +master-bin.000002 # Query # # use `test`; rename table t1 to t5, t2 to t1, t5 to t2, t4 to t5, t3 to t4 +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000003 # Gtid # # BEGIN GTID #-#-# +master-bin.000003 # Query # # use `test`; insert into t1 values(null) +master-bin.000003 # Query # # COMMIT +master-bin.000003 # Gtid # # BEGIN GTID #-#-# +master-bin.000003 # Query # # use `test`; insert into t2 values(null) +master-bin.000003 # Query # # COMMIT +master-bin.000003 # Gtid # # BEGIN GTID #-#-# +master-bin.000003 # Query # # use `test`; insert into t5 values(null) +master-bin.000003 # Query # # COMMIT +master-bin.000003 # Gtid # # BEGIN GTID #-#-# +master-bin.000003 # Query # # use `test`; insert into t4 values(null) +master-bin.000003 # Query # # COMMIT +master-bin.000003 # Gtid # # GTID #-#-# +master-bin.000003 # Query # # use `test`; DROP TABLE `t1`,`t2`,`t4`,`t5` /* generated by server */ +master-bin.000003 # Gtid # # GTID #-#-# +master-bin.000003 # Query # # use `test`; create table t1 (a int not null) transactional=1 +master-bin.000003 # Gtid # # GTID #-#-# +master-bin.000003 # Query # # use `test`; create table t2 (b int not null) transactional=1 +master-bin.000003 # Gtid # # GTID #-#-# +master-bin.000003 # Query # # use `test`; create table t3 (c int not null) transactional=1 +master-bin.000003 # Gtid # # GTID #-#-# +master-bin.000003 # Query # # use `test`; create table t4 (d int not null) transactional=1 +master-bin.000003 # Gtid # # BEGIN GTID #-#-# +master-bin.000003 # Query # # use `test`; insert into t1 values(1) +master-bin.000003 # Query # # COMMIT +master-bin.000003 # Gtid # # BEGIN GTID #-#-# +master-bin.000003 # Query # # use `test`; insert into t2 values(2) +master-bin.000003 # Query # # COMMIT +master-bin.000003 # Gtid # # BEGIN GTID #-#-# +master-bin.000003 # Query # # use `test`; insert into t3 values(3) +master-bin.000003 # Query # # COMMIT +master-bin.000003 # Gtid # # BEGIN GTID #-#-# +master-bin.000003 # Query # # use `test`; insert into t4 values(4) +master-bin.000003 # Query # # COMMIT +master-bin.000003 # Gtid # # GTID #-#-# +master-bin.000003 # Query # # use `test`; CREATE DEFINER=`root`@`localhost` trigger t1_trg before insert on t1 for each row +begin +if isnull(new.a) then +set new.a:= 1000; +end if; +end +master-bin.000003 # Gtid # # GTID #-#-# +master-bin.000003 # Query # # use `test`; CREATE DEFINER=`root`@`localhost` trigger t2_trg before insert on t2 for each row +begin +if isnull(new.b) then +set new.b:= 2000; +end if; +end +master-bin.000003 # Gtid # # GTID #-#-# +master-bin.000003 # Query # # use `test`; CREATE DEFINER=`root`@`localhost` trigger t3_trg before insert on t3 for each row +begin +if isnull(new.c) then +set new.c:= 4000; +end if; +end +master-bin.000003 # Gtid # # GTID #-#-# +master-bin.000003 # Query # # use `test`; CREATE DEFINER=`root`@`localhost` trigger t4_trg before insert on t4 for each row +begin +if isnull(new.d) then +set new.d:= 8000; +end if; +end +master-bin.000003 # Gtid # # GTID #-#-# +master-bin.000003 # Query # # use `test`; flush tables +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000004 # Gtid # # BEGIN GTID #-#-# +master-bin.000004 # Query # # use `test`; insert into t1 values(null) +master-bin.000004 # Query # # COMMIT +master-bin.000004 # Gtid # # BEGIN GTID #-#-# +master-bin.000004 # Query # # use `test`; insert into t2 values(null) +master-bin.000004 # Query # # COMMIT +master-bin.000004 # Gtid # # BEGIN GTID #-#-# +master-bin.000004 # Query # # use `test`; insert into t3 values(null) +master-bin.000004 # Query # # COMMIT +master-bin.000004 # Gtid # # BEGIN GTID #-#-# +master-bin.000004 # Query # # use `test`; insert into t4 values(null) +master-bin.000004 # Query # # COMMIT +master-bin.000004 # Gtid # # BEGIN GTID #-#-# +master-bin.000004 # Query # # use `test`; delete from t1 where a > 100 +master-bin.000004 # Query # # COMMIT +master-bin.000004 # Gtid # # BEGIN GTID #-#-# +master-bin.000004 # Query # # use `test`; delete from t2 where b > 100 +master-bin.000004 # Query # # COMMIT +master-bin.000004 # Gtid # # BEGIN GTID #-#-# +master-bin.000004 # Query # # use `test`; delete from t3 where c > 100 +master-bin.000004 # Query # # COMMIT +master-bin.000004 # Gtid # # BEGIN GTID #-#-# +master-bin.000004 # Query # # use `test`; delete from t4 where d > 100 +master-bin.000004 # Query # # COMMIT +master-bin.000004 # Gtid # # GTID #-#-# +master-bin.000004 # Query # # use `test`; flush tables +master-bin.000004 # Gtid # # GTID #-#-# +master-bin.000004 # Query # # use `test`; rename table t1 to t5, t2 to t1, t5 to t2, t4 to t5, t3 to t4 +include/show_binlog_events.inc +Log_name Pos Event_type Server_id End_log_pos Info +master-bin.000005 # Gtid # # BEGIN GTID #-#-# +master-bin.000005 # Query # # use `test`; insert into t1 values(null) +master-bin.000005 # Query # # COMMIT +master-bin.000005 # Gtid # # BEGIN GTID #-#-# +master-bin.000005 # Query # # use `test`; insert into t2 values(null) +master-bin.000005 # Query # # COMMIT +master-bin.000005 # Gtid # # BEGIN GTID #-#-# +master-bin.000005 # Query # # use `test`; insert into t5 values(null) +master-bin.000005 # Query # # COMMIT +master-bin.000005 # Gtid # # BEGIN GTID #-#-# +master-bin.000005 # Query # # use `test`; insert into t4 values(null) +master-bin.000005 # Query # # COMMIT +master-bin.000005 # Gtid # # GTID #-#-# +master-bin.000005 # Query # # use `test`; DROP TABLE `t1`,`t2`,`t4`,`t5` /* generated by server */ diff --git a/mysql-test/suite/atomic/rename_table_binlog.test b/mysql-test/suite/atomic/rename_table_binlog.test new file mode 100644 index 00000000000..49878cf272e --- /dev/null +++ b/mysql-test/suite/atomic/rename_table_binlog.test @@ -0,0 +1,168 @@ +--source include/have_debug.inc +--source include/have_innodb.inc +--source include/have_csv.inc +--source include/have_log_bin.inc +--source include/not_valgrind.inc + +# +# Testing of atomic rename with binlogging +# - First crash is before binlog is written, in which case the rename should +# be reverted +# - Second crash is after binlog is written, in which case the rename should hold +# + +RESET MASTER; + +let $engine_count=2; +let $engines='myisam', 'aria'; + +let $crash_count=2; +let $crash_points='ddl_log_rename_before_binlog','ddl_log_rename_after_binlog'; +let $crash_positions= 1; + +let $old_debug=`select @@debug_dbug`; + +let $e=0; +--disable_query_log +while ($e < $engine_count) +{ + inc $e; + let $engine=`select ELT($e, $engines)`; + let $default_engine=$engine; + let $extra_option=; + + if ($engine == "aria") + { + let $extra_option=transactional=1; + } + if ($engine == "aria_notrans") + { + let $default_engine="aria"; + let $extra_option=transactional=0; + } + + --eval set @@default_storage_engine=$default_engine + --eval create table t1 (a int not null) $extra_option; + --eval create table t2 (b int not null) $extra_option; + --eval create table t3 (c int not null) $extra_option; + --eval create table t4 (d int not null) $extra_option; + insert into t1 values(1); + insert into t2 values(2); + insert into t3 values(3); + insert into t4 values(4); + + delimiter |; + create trigger t1_trg before insert on t1 for each row + begin + if isnull(new.a) then + set new.a:= 1000; + end if; + end| + create trigger t2_trg before insert on t2 for each row + begin + if isnull(new.b) then + set new.b:= 2000; + end if; + end| + create trigger t3_trg before insert on t3 for each row + begin + if isnull(new.c) then + set new.c:= 4000; + end if; + end| + create trigger t4_trg before insert on t4 for each row + begin + if isnull(new.d) then + set new.d:= 8000; + end if; + end| + delimiter ;| + + let $c=0; + while ($c < $crash_count) + { + inc $c; + let $crash=`select ELT($c, $crash_points)`; + let $r=0; + while ($r < $crash_positions) + { + inc $r; + echo "engine: $engine crash point: $crash position: $r"; + flush tables; + + --exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect + --disable_reconnect + --eval set @@debug_dbug="+d,$crash",@debug_crash_counter=1 + let $errno=0; + --error 0,2013 + rename table t1 to t5, t2 to t1, t5 to t2, t4 to t5, t3 to t4; + let $error=$errno; + --enable_reconnect + --source include/wait_until_connected_again.inc + --disable_query_log + --eval set @@debug_dbug="$old_debug" + + if ($error == 0) + { + echo "No crash!"; + # No crash, rename things back + rename table t4 to t3, t5 to t4, t2 to t5, t1 to t2, t5 to t1; + } + if ($c == 1) + { + # Check that the tables are back to original + let $res=`select t1.a+t2.b+t3.c+t4.d from t1,t2,t3,t4`; + if ($res != 10) + { + die "Got result $res when 10 was expected"; + } + + # Ensure that triggers work + insert into t1 values(null); + insert into t2 values(null); + insert into t3 values(null); + insert into t4 values(null); + let $res=`select (select sum(t1.a) from t1)+ (select sum(t2.b) from t2) + (select sum(t3.c) from t3)+ (select sum(t4.d) from t4)`; + if ($res != 15010) + { + die "Got result $res when 15010 was expected"; + } + # Restore tables + delete from t1 where a > 100; + delete from t2 where b > 100; + delete from t3 where c > 100; + delete from t4 where d > 100; + } + if ($c == 2) + { + # Check that rename succeded + insert into t1 values(null); + insert into t2 values(null); + insert into t5 values(null); + insert into t4 values(null); + let $res=`select (select sum(t1.b) from t1)+ (select sum(t2.a) from t2) + (select sum(t4.c) from t4)+ (select sum(t5.d) from t5)`; + if ($res != 15010) + { + die "Got result $res when 15010 was expected"; + } + let $res=`select (select count(*)=2 from t1) + (select count(*)=2 from t2) + (select count(*)=2 from t4)+ (select count(*)=2 from t5)`; + if ($res != 4) + { + die "Got result $res when 4 was expected"; + } + } + } + } + drop table t1,t2,t4,t5; +} +--enable_query_log + +# Show the binlogs that holds the renames +--let $binlog_file=master-bin.000002 +--source include/show_binlog_events.inc +--let $binlog_file=master-bin.000003 +--source include/show_binlog_events.inc +--let $binlog_file=master-bin.000004 +--source include/show_binlog_events.inc +--let $binlog_file=master-bin.000005 +--source include/show_binlog_events.inc diff --git a/mysql-test/suite/atomic/rename_trigger.result b/mysql-test/suite/atomic/rename_trigger.result new file mode 100644 index 00000000000..d06debdaf8e --- /dev/null +++ b/mysql-test/suite/atomic/rename_trigger.result @@ -0,0 +1,150 @@ +"engine: myisam crash point: ddl_log_rename_after_failed_rename_trigger position: 1" +"engine: myisam crash point: ddl_log_rename_after_failed_rename_trigger position: 2" +"engine: myisam crash point: ddl_log_rename_after_failed_rename_trigger position: 3" +"engine: myisam crash point: ddl_log_rename_after_failed_rename_trigger position: 4" +"engine: myisam crash point: ddl_log_rename_after_failed_rename_trigger position: 5" +"engine: myisam crash point: ddl_log_rename_after_failed_rename_trigger position: 6" +"engine: myisam crash point: ddl_log_rename_after_failed_rename_trigger position: 7" +"engine: myisam crash point: ddl_log_rename_after_failed_rename_trigger position: 8" +"engine: myisam crash point: ddl_log_rename_after_failed_rename_trigger position: 9" +"engine: myisam crash point: ddl_log_rename_after_failed_rename_trigger position: 10" +"engine: myisam crash point: ddl_log_rename_after_revert_rename_table position: 1" +"engine: myisam crash point: ddl_log_rename_after_revert_rename_table position: 2" +"engine: myisam crash point: ddl_log_rename_after_revert_rename_table position: 3" +"engine: myisam crash point: ddl_log_rename_after_revert_rename_table position: 4" +"engine: myisam crash point: ddl_log_rename_after_revert_rename_table position: 5" +"engine: myisam crash point: ddl_log_rename_after_revert_rename_table position: 6" +"engine: myisam crash point: ddl_log_rename_after_revert_rename_table position: 7" +"engine: myisam crash point: ddl_log_rename_after_revert_rename_table position: 8" +"engine: myisam crash point: ddl_log_rename_after_revert_rename_table position: 9" +"engine: myisam crash point: ddl_log_rename_after_revert_rename_table position: 10" +"engine: myisam crash point: ddl_log_rename_after_disable_entry position: 1" +"engine: myisam crash point: ddl_log_rename_after_disable_entry position: 2" +"engine: myisam crash point: ddl_log_rename_after_disable_entry position: 3" +"engine: myisam crash point: ddl_log_rename_after_disable_entry position: 4" +"engine: myisam crash point: ddl_log_rename_after_disable_entry position: 5" +"engine: myisam crash point: ddl_log_rename_after_disable_entry position: 6" +"engine: myisam crash point: ddl_log_rename_after_disable_entry position: 7" +"engine: myisam crash point: ddl_log_rename_after_disable_entry position: 8" +"engine: myisam crash point: ddl_log_rename_after_disable_entry position: 9" +"engine: myisam crash point: ddl_log_rename_after_disable_entry position: 10" +"engine: aria crash point: ddl_log_rename_after_failed_rename_trigger position: 1" +"engine: aria crash point: ddl_log_rename_after_failed_rename_trigger position: 2" +"engine: aria crash point: ddl_log_rename_after_failed_rename_trigger position: 3" +"engine: aria crash point: ddl_log_rename_after_failed_rename_trigger position: 4" +"engine: aria crash point: ddl_log_rename_after_failed_rename_trigger position: 5" +"engine: aria crash point: ddl_log_rename_after_failed_rename_trigger position: 6" +"engine: aria crash point: ddl_log_rename_after_failed_rename_trigger position: 7" +"engine: aria crash point: ddl_log_rename_after_failed_rename_trigger position: 8" +"engine: aria crash point: ddl_log_rename_after_failed_rename_trigger position: 9" +"engine: aria crash point: ddl_log_rename_after_failed_rename_trigger position: 10" +"engine: aria crash point: ddl_log_rename_after_revert_rename_table position: 1" +"engine: aria crash point: ddl_log_rename_after_revert_rename_table position: 2" +"engine: aria crash point: ddl_log_rename_after_revert_rename_table position: 3" +"engine: aria crash point: ddl_log_rename_after_revert_rename_table position: 4" +"engine: aria crash point: ddl_log_rename_after_revert_rename_table position: 5" +"engine: aria crash point: ddl_log_rename_after_revert_rename_table position: 6" +"engine: aria crash point: ddl_log_rename_after_revert_rename_table position: 7" +"engine: aria crash point: ddl_log_rename_after_revert_rename_table position: 8" +"engine: aria crash point: ddl_log_rename_after_revert_rename_table position: 9" +"engine: aria crash point: ddl_log_rename_after_revert_rename_table position: 10" +"engine: aria crash point: ddl_log_rename_after_disable_entry position: 1" +"engine: aria crash point: ddl_log_rename_after_disable_entry position: 2" +"engine: aria crash point: ddl_log_rename_after_disable_entry position: 3" +"engine: aria crash point: ddl_log_rename_after_disable_entry position: 4" +"engine: aria crash point: ddl_log_rename_after_disable_entry position: 5" +"engine: aria crash point: ddl_log_rename_after_disable_entry position: 6" +"engine: aria crash point: ddl_log_rename_after_disable_entry position: 7" +"engine: aria crash point: ddl_log_rename_after_disable_entry position: 8" +"engine: aria crash point: ddl_log_rename_after_disable_entry position: 9" +"engine: aria crash point: ddl_log_rename_after_disable_entry position: 10" +"engine: aria_notrans crash point: ddl_log_rename_after_failed_rename_trigger position: 1" +"engine: aria_notrans crash point: ddl_log_rename_after_failed_rename_trigger position: 2" +"engine: aria_notrans crash point: ddl_log_rename_after_failed_rename_trigger position: 3" +"engine: aria_notrans crash point: ddl_log_rename_after_failed_rename_trigger position: 4" +"engine: aria_notrans crash point: ddl_log_rename_after_failed_rename_trigger position: 5" +"engine: aria_notrans crash point: ddl_log_rename_after_failed_rename_trigger position: 6" +"engine: aria_notrans crash point: ddl_log_rename_after_failed_rename_trigger position: 7" +"engine: aria_notrans crash point: ddl_log_rename_after_failed_rename_trigger position: 8" +"engine: aria_notrans crash point: ddl_log_rename_after_failed_rename_trigger position: 9" +"engine: aria_notrans crash point: ddl_log_rename_after_failed_rename_trigger position: 10" +"engine: aria_notrans crash point: ddl_log_rename_after_revert_rename_table position: 1" +"engine: aria_notrans crash point: ddl_log_rename_after_revert_rename_table position: 2" +"engine: aria_notrans crash point: ddl_log_rename_after_revert_rename_table position: 3" +"engine: aria_notrans crash point: ddl_log_rename_after_revert_rename_table position: 4" +"engine: aria_notrans crash point: ddl_log_rename_after_revert_rename_table position: 5" +"engine: aria_notrans crash point: ddl_log_rename_after_revert_rename_table position: 6" +"engine: aria_notrans crash point: ddl_log_rename_after_revert_rename_table position: 7" +"engine: aria_notrans crash point: ddl_log_rename_after_revert_rename_table position: 8" +"engine: aria_notrans crash point: ddl_log_rename_after_revert_rename_table position: 9" +"engine: aria_notrans crash point: ddl_log_rename_after_revert_rename_table position: 10" +"engine: aria_notrans crash point: ddl_log_rename_after_disable_entry position: 1" +"engine: aria_notrans crash point: ddl_log_rename_after_disable_entry position: 2" +"engine: aria_notrans crash point: ddl_log_rename_after_disable_entry position: 3" +"engine: aria_notrans crash point: ddl_log_rename_after_disable_entry position: 4" +"engine: aria_notrans crash point: ddl_log_rename_after_disable_entry position: 5" +"engine: aria_notrans crash point: ddl_log_rename_after_disable_entry position: 6" +"engine: aria_notrans crash point: ddl_log_rename_after_disable_entry position: 7" +"engine: aria_notrans crash point: ddl_log_rename_after_disable_entry position: 8" +"engine: aria_notrans crash point: ddl_log_rename_after_disable_entry position: 9" +"engine: aria_notrans crash point: ddl_log_rename_after_disable_entry position: 10" +"engine: innodb crash point: ddl_log_rename_after_failed_rename_trigger position: 1" +"engine: innodb crash point: ddl_log_rename_after_failed_rename_trigger position: 2" +"engine: innodb crash point: ddl_log_rename_after_failed_rename_trigger position: 3" +"engine: innodb crash point: ddl_log_rename_after_failed_rename_trigger position: 4" +"engine: innodb crash point: ddl_log_rename_after_failed_rename_trigger position: 5" +"engine: innodb crash point: ddl_log_rename_after_failed_rename_trigger position: 6" +"engine: innodb crash point: ddl_log_rename_after_failed_rename_trigger position: 7" +"engine: innodb crash point: ddl_log_rename_after_failed_rename_trigger position: 8" +"engine: innodb crash point: ddl_log_rename_after_failed_rename_trigger position: 9" +"engine: innodb crash point: ddl_log_rename_after_failed_rename_trigger position: 10" +"engine: innodb crash point: ddl_log_rename_after_revert_rename_table position: 1" +"engine: innodb crash point: ddl_log_rename_after_revert_rename_table position: 2" +"engine: innodb crash point: ddl_log_rename_after_revert_rename_table position: 3" +"engine: innodb crash point: ddl_log_rename_after_revert_rename_table position: 4" +"engine: innodb crash point: ddl_log_rename_after_revert_rename_table position: 5" +"engine: innodb crash point: ddl_log_rename_after_revert_rename_table position: 6" +"engine: innodb crash point: ddl_log_rename_after_revert_rename_table position: 7" +"engine: innodb crash point: ddl_log_rename_after_revert_rename_table position: 8" +"engine: innodb crash point: ddl_log_rename_after_revert_rename_table position: 9" +"engine: innodb crash point: ddl_log_rename_after_revert_rename_table position: 10" +"engine: innodb crash point: ddl_log_rename_after_disable_entry position: 1" +"engine: innodb crash point: ddl_log_rename_after_disable_entry position: 2" +"engine: innodb crash point: ddl_log_rename_after_disable_entry position: 3" +"engine: innodb crash point: ddl_log_rename_after_disable_entry position: 4" +"engine: innodb crash point: ddl_log_rename_after_disable_entry position: 5" +"engine: innodb crash point: ddl_log_rename_after_disable_entry position: 6" +"engine: innodb crash point: ddl_log_rename_after_disable_entry position: 7" +"engine: innodb crash point: ddl_log_rename_after_disable_entry position: 8" +"engine: innodb crash point: ddl_log_rename_after_disable_entry position: 9" +"engine: innodb crash point: ddl_log_rename_after_disable_entry position: 10" +"engine: csv crash point: ddl_log_rename_after_failed_rename_trigger position: 1" +"engine: csv crash point: ddl_log_rename_after_failed_rename_trigger position: 2" +"engine: csv crash point: ddl_log_rename_after_failed_rename_trigger position: 3" +"engine: csv crash point: ddl_log_rename_after_failed_rename_trigger position: 4" +"engine: csv crash point: ddl_log_rename_after_failed_rename_trigger position: 5" +"engine: csv crash point: ddl_log_rename_after_failed_rename_trigger position: 6" +"engine: csv crash point: ddl_log_rename_after_failed_rename_trigger position: 7" +"engine: csv crash point: ddl_log_rename_after_failed_rename_trigger position: 8" +"engine: csv crash point: ddl_log_rename_after_failed_rename_trigger position: 9" +"engine: csv crash point: ddl_log_rename_after_failed_rename_trigger position: 10" +"engine: csv crash point: ddl_log_rename_after_revert_rename_table position: 1" +"engine: csv crash point: ddl_log_rename_after_revert_rename_table position: 2" +"engine: csv crash point: ddl_log_rename_after_revert_rename_table position: 3" +"engine: csv crash point: ddl_log_rename_after_revert_rename_table position: 4" +"engine: csv crash point: ddl_log_rename_after_revert_rename_table position: 5" +"engine: csv crash point: ddl_log_rename_after_revert_rename_table position: 6" +"engine: csv crash point: ddl_log_rename_after_revert_rename_table position: 7" +"engine: csv crash point: ddl_log_rename_after_revert_rename_table position: 8" +"engine: csv crash point: ddl_log_rename_after_revert_rename_table position: 9" +"engine: csv crash point: ddl_log_rename_after_revert_rename_table position: 10" +"engine: csv crash point: ddl_log_rename_after_disable_entry position: 1" +"engine: csv crash point: ddl_log_rename_after_disable_entry position: 2" +"engine: csv crash point: ddl_log_rename_after_disable_entry position: 3" +"engine: csv crash point: ddl_log_rename_after_disable_entry position: 4" +"engine: csv crash point: ddl_log_rename_after_disable_entry position: 5" +"engine: csv crash point: ddl_log_rename_after_disable_entry position: 6" +"engine: csv crash point: ddl_log_rename_after_disable_entry position: 7" +"engine: csv crash point: ddl_log_rename_after_disable_entry position: 8" +"engine: csv crash point: ddl_log_rename_after_disable_entry position: 9" +"engine: csv crash point: ddl_log_rename_after_disable_entry position: 10" diff --git a/mysql-test/suite/atomic/rename_trigger.test b/mysql-test/suite/atomic/rename_trigger.test new file mode 100644 index 00000000000..0509b109005 --- /dev/null +++ b/mysql-test/suite/atomic/rename_trigger.test @@ -0,0 +1,158 @@ +--source include/have_debug.inc +--source include/have_innodb.inc +--source include/have_csv.inc +--source include/not_valgrind.inc +--source include/not_embedded.inc + +# +# Testing of atomic rename of table with triggers when table rename works but +# rename of trigger fails. +# This test can't be combined with rename_table.test as we need to simulate +# an error in sql_create_definition_file() +# + +let $crash_count=3; +let $crash_points='ddl_log_rename_after_failed_rename_trigger','ddl_log_rename_after_revert_rename_table', 'ddl_log_rename_after_disable_entry'; + +let $engine_count=5; +let $engines='myisam','aria','aria_notrans','innodb','csv'; + +# Number times sql_create_definition_file() is called during one statement. +# This is number of renames (5)*2 +let $renames=10; + +let $old_debug=`select @@debug_dbug`; + +let $e=0; +--disable_query_log +while ($e < $engine_count) +{ + inc $e; + let $engine=`select ELT($e, $engines)`; + let $default_engine=$engine; + let $extra_option=; + + if ($engine == "aria") + { + let $extra_option=transactional=1; + } + if ($engine == "aria_notrans") + { + let $default_engine="aria"; + let $extra_option=transactional=0; + } + + --eval set @@default_storage_engine=$default_engine + --eval create table t1 (a int not null) $extra_option; + --eval create table t2 (b int not null) $extra_option; + --eval create table t3 (c int not null) $extra_option; + --eval create table t4 (d int not null) $extra_option; + insert into t1 values(1); + insert into t2 values(2); + insert into t3 values(3); + insert into t4 values(4); + + delimiter |; + create trigger t1_trg before insert on t1 for each row + begin + if isnull(new.a) then + set new.a:= 1000; + end if; + end| + create trigger t2_trg before insert on t2 for each row + begin + if isnull(new.b) then + set new.b:= 2000; + end if; + end| + create trigger t3_trg before insert on t3 for each row + begin + if isnull(new.c) then + set new.c:= 4000; + end if; + end| + create trigger t4_trg before insert on t4 for each row + begin + if isnull(new.d) then + set new.d:= 8000; + end if; + end| + delimiter ;| + let $c=0; + while ($c < $crash_count) + { + inc $c; + let $crash=`select ELT($c, $crash_points)`; + let $r=0; + while ($r < $renames) + { + inc $r; + echo "engine: $engine crash point: $crash position: $r"; + flush tables; + + --exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect + --disable_reconnect + --eval set @@debug_dbug="+d,$crash,definition_file_simulate_write_error",@debug_crash_counter=1; + --eval set @@debug_dbug="+d,definition_file_simulate_write_error",@debug_error_counter=$r; + let $errno=0; + --error 0,2013 + rename table t1 to t5, t2 to t1, t5 to t2, t4 to t5, t3 to t4; + let $error=$errno; + --enable_reconnect + --source include/wait_until_connected_again.inc + --disable_query_log + --eval set @@debug_dbug="$old_debug" + + if ($error == 0) + { + echo "No crash!"; + # No crash, rename things back + rename table t4 to t3, t5 to t4, t2 to t5, t1 to t2, t5 to t1; + } + + # Ensure that the tables are back to original + let $res=`select t1.a+t2.b+t3.c+t4.d from t1,t2,t3,t4`; + if ($res != 10) + { + die "Got result $res when 10 was expected"; + } + + # Ensure that triggers work + insert into t1 values(null); + insert into t2 values(null); + insert into t3 values(null); + insert into t4 values(null); + let $res=`select (select sum(t1.a) from t1)+ (select sum(t2.b) from t2) + (select sum(t3.c) from t3)+ (select sum(t4.d) from t4)`; + if ($res != 15010) + { + die "Got result $res when 15010 was expected"; + } + # Restore tables + delete from t1 where a > 100; + delete from t2 where b > 100; + delete from t3 where c > 100; + delete from t4 where d > 100; + } + } + + # Last test, check that rename really worked + rename table t1 to t5, t2 to t1, t5 to t2, t4 to t5, t3 to t4; + insert into t1 values(null); + insert into t2 values(null); + insert into t5 values(null); + insert into t4 values(null); + let $res=`select (select sum(t1.b) from t1)+ (select sum(t2.a) from t2) + (select sum(t4.c) from t4)+ (select sum(t5.d) from t5)`; + if ($res != 15010) + { + die "Got result $res when 15010 was expected"; + } + let $res=`select (select count(*)=2 from t1) + (select count(*)=2 from t2) + (select count(*)=2 from t4)+ (select count(*)=2 from t5)`; + if ($res != 4) + { + die "Got result $res when 4 was expected"; + } + + drop table t1,t2,t4,t5; +} +--enable_query_log +--disable_query_log diff --git a/mysql-test/suite/atomic/rename_view.result b/mysql-test/suite/atomic/rename_view.result new file mode 100644 index 00000000000..a8a630c5117 --- /dev/null +++ b/mysql-test/suite/atomic/rename_view.result @@ -0,0 +1,23 @@ +"engine: crash point: ddl_log_rename_before_rename_view position: 1" +"engine: crash point: ddl_log_rename_before_rename_view position: 2" +"engine: crash point: ddl_log_rename_before_rename_view position: 3" +"engine: crash point: ddl_log_rename_before_rename_view position: 4" +"engine: crash point: ddl_log_rename_before_rename_view position: 5" +"engine: crash point: ddl_log_rename_after_rename_view position: 1" +"engine: crash point: ddl_log_rename_after_rename_view position: 2" +"engine: crash point: ddl_log_rename_after_rename_view position: 3" +"engine: crash point: ddl_log_rename_after_rename_view position: 4" +"engine: crash point: ddl_log_rename_after_rename_view position: 5" +"engine: crash point: rename_view_after_rename_schema_file position: 1" +"engine: crash point: rename_view_after_rename_schema_file position: 2" +"engine: crash point: rename_view_after_rename_schema_file position: 3" +"engine: crash point: rename_view_after_rename_schema_file position: 4" +"engine: crash point: rename_view_after_rename_schema_file position: 5" +"engine: crash point: definition_file_after_create position: 1" +"engine: crash point: definition_file_after_create position: 2" +"engine: crash point: definition_file_after_create position: 3" +"engine: crash point: definition_file_after_create position: 4" +"engine: crash point: definition_file_after_create position: 5" +# +# At last check that rename works when there is no crash +# diff --git a/mysql-test/suite/atomic/rename_view.test b/mysql-test/suite/atomic/rename_view.test new file mode 100644 index 00000000000..c326d842d67 --- /dev/null +++ b/mysql-test/suite/atomic/rename_view.test @@ -0,0 +1,85 @@ +--source include/have_debug.inc +--source include/not_valgrind.inc +--source include/not_embedded.inc + +# +# Testing of atomic rename with crashes in a lot of different places +# + +let $crash_count=4; +let $crash_points='ddl_log_rename_before_rename_view','ddl_log_rename_after_rename_view','rename_view_after_rename_schema_file','definition_file_after_create'; + +# Number of renames in the tested statement +let $renames=5; + +let $old_debug=`select @@debug_dbug`; + +let $e=0; + +--disable_query_log +create table t1 (a int not null); +create table t2 (b int not null); +create table t3 (c int not null); +create table t4 (d int not null); +insert into t1 values(1); +insert into t2 values(2); +insert into t3 values(3); +insert into t4 values(4); +create view v1 as select t1.a from t1; +create view v2 as select t2.b from t2; +create view v3 as select t3.c from t3; +create view v4 as select t4.d from t4; +flush tables; + +let $c=0; +while ($c < $crash_count) +{ + inc $c; + let $crash=`select ELT($c, $crash_points)`; + let $r=0; + while ($r < $renames) + { + inc $r; + echo "engine: crash point: $crash position: $r"; + + --exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect + --disable_reconnect + --eval set @@debug_dbug="+d,$crash",@debug_crash_counter=$r + let $errno=0; + --error 0,2013 + rename table v1 to v5, v2 to v1, v5 to v2, v4 to v5, v3 to v4; + let $error=$errno; + --enable_reconnect + --source include/wait_until_connected_again.inc + --disable_query_log + --eval set @@debug_dbug="$old_debug" + + if ($error == 0) + { + echo "No crash!"; + # No crash, rename things back + rename table v4 to v3, v5 to v4, v2 to v5, v1 to v2, v5 to v1; + } + + # Ensure that the tables are back to original + let $res=`select v1.a+v2.b+v3.c+v4.d from v1,v2,v3,v4`; + if ($res != 10) + { + die "Got result $res when 10 was expected"; + } + } +} + +--echo # +--echo # At last check that rename works when there is no crash +--echo # + +rename table v1 to v5, v2 to v1, v5 to v2, v4 to v5, v3 to v4; +let $res=`select (select sum(v1.b) from v1)+ (select sum(v2.a) from v2) + (select sum(v4.c) from v4)+ (select sum(v5.d) from v5)`; +if ($res != 10) +{ + die "Got result $res when 10 was expected"; +} +drop view v1,v2,v4,v5; +drop table t1,t2,t3,t4; +--enable_query_log diff --git a/mysql-test/suite/atomic/rename_view2.result b/mysql-test/suite/atomic/rename_view2.result new file mode 100644 index 00000000000..8af0320c4ae --- /dev/null +++ b/mysql-test/suite/atomic/rename_view2.result @@ -0,0 +1,8 @@ +"engine: crash point: ddl_log_rename_after_disable_entry position: 1" +"engine: crash point: ddl_log_rename_after_disable_entry position: 2" +"engine: crash point: ddl_log_rename_after_disable_entry position: 3" +"engine: crash point: ddl_log_rename_after_disable_entry position: 4" +"engine: crash point: ddl_log_rename_after_disable_entry position: 5" +# +# At last check that rename works when there is no crash +# diff --git a/mysql-test/suite/atomic/rename_view2.test b/mysql-test/suite/atomic/rename_view2.test new file mode 100644 index 00000000000..5dfc72bbae8 --- /dev/null +++ b/mysql-test/suite/atomic/rename_view2.test @@ -0,0 +1,89 @@ +--source include/have_debug.inc +--source include/not_valgrind.inc + +# +# Testing of atomic rename of view when creating of definition file fails +# and we crash after the last rename entry has been disabled. +# This is not possible to test with rename_view.test, which is why +# we have a separate test for this case +# + +let $crash_count=1; +let $crash_points='ddl_log_rename_after_disable_entry'; + +# Number of renames in the tested statement +let $renames=5; + +let $old_debug=`select @@debug_dbug`; + +let $e=0; + +--disable_query_log +create table t1 (a int not null); +create table t2 (b int not null); +create table t3 (c int not null); +create table t4 (d int not null); +insert into t1 values(1); +insert into t2 values(2); +insert into t3 values(3); +insert into t4 values(4); +create view v1 as select t1.a from t1; +create view v2 as select t2.b from t2; +create view v3 as select t3.c from t3; +create view v4 as select t4.d from t4; +flush tables; + +let $c=0; +while ($c < $crash_count) +{ + inc $c; + let $crash=`select ELT($c, $crash_points)`; + let $r=0; + while ($r < $renames) + { + inc $r; + echo "engine: crash point: $crash position: $r"; + + --exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect + --disable_reconnect + --eval set @@debug_dbug="+d,$crash,definition_file_simulate_write_error",@debuge_crash_counter=1; + --eval set @@debug_dbug="+d,definition_file_simulate_write_error",@debug_error_counter=$r; + + let $errno=0; + --error 0,3,2013 + rename table v1 to v5, v2 to v1, v5 to v2, v4 to v5, v3 to v4; + let $error=$errno; + --enable_reconnect + --source include/wait_until_connected_again.inc + --disable_query_log + --eval set @@debug_dbug="$old_debug" + + if ($error == 0) + { + echo "No crash!"; + # No crash, rename things back + rename table v4 to v3, v5 to v4, v2 to v5, v1 to v2, v5 to v1; + } + + # Ensure that the tables are back to original + let $res=`select v1.a+v2.b+v3.c+v4.d from v1,v2,v3,v4`; + if ($res != 10) + { + die "Got result $res when 10 was expected"; + } + } +} + +--echo # +--echo # At last check that rename works when there is no crash +--echo # + +rename table v1 to v5, v2 to v1, v5 to v2, v4 to v5, v3 to v4; +let $res=`select (select sum(v1.b) from v1)+ (select sum(v2.a) from v2) + (select sum(v4.c) from v4)+ (select sum(v5.d) from v5)`; +if ($res != 10) +{ + die "Got result $res when 10 was expected"; +} +drop view v1,v2,v4,v5; +drop table t1,t2,t3,t4; +--enable_query_log diff --git a/sql/ddl_log.cc b/sql/ddl_log.cc index bad786b3799..dc00a29c786 100644 --- a/sql/ddl_log.cc +++ b/sql/ddl_log.cc @@ -22,178 +22,302 @@ #include "log.h" // sql_print_error() #include "ddl_log.h" #include "ha_partition.h" // PAR_EXT +#include "sql_table.h" // build_table_filename +#include "sql_statistics.h" // rename_table_in_stats_tables +#include "sql_view.h" // mysql_rename_view() +#include "strfunc.h" // strconvert +#include <mysys_err.h> // EE_LINK /*-------------------------------------------------------------------------- - MODULE: DDL log - ----------------- - - This module is used to ensure that we can recover from crashes that occur - in the middle of a meta-data operation in MySQL. E.g. DROP TABLE t1, t2; - We need to ensure that both t1 and t2 are dropped and not only t1 and - also that each table drop is entirely done and not "half-baked". + MODULE: DDL log + ----------------- + + This module is used to ensure that we can recover from crashes that + occur in the middle of a meta-data operation in MySQL. E.g. DROP + TABLE t1, t2; We need to ensure that both t1 and t2 are dropped and + not only t1 and also that each table drop is entirely done and not + "half-baked". + + To support this we create log entries for each meta-data statement + in the ddl log while we are executing. These entries are dropped + when the operation is completed. + + At recovery those entries that were not completed will be executed. + + There is only one ddl log in the system and it is protected by a mutex + and there is a global struct that contains information about its current + state. + + DDl recovery after a crash works the following way: + + - ddl_log_initialize() initializes the global global_ddl_log variable + and opens the binary log if it exists. If it doesn't exists a new one + is created. + - ddl_log_close_binlogged_events() loops over all log events and checks if + their xid (stored in the EXECUTE_CODE event) is in the binary log. If xid + exists in the binary log the entry is marked as finished in the ddl log. + - After a new binary log is created and is open for new entries, + ddl_log_execute_recovery() is executed on remaining open events: + - Loop over all events + - For each entry with DDL_LOG_ENTRY_CODE execute the remaining phases + in ddl_log_execute_entry_no_lock() + + The ddl_log.log file is created at startup and deleted when server goes down. + After the final recovery phase is done, the file is truncated. + + History: + First version written in 2006 by Mikael Ronstrom + Second version in 2020 by Monty +--------------------------------------------------------------------------*/ - To support this we create log entries for each meta-data statement in the - ddl log while we are executing. These entries are dropped when the - operation is completed. +#define DDL_LOG_MAGIC_LENGTH 4 - At recovery those entries that were not completed will be executed. +uchar ddl_log_file_magic[]= +{ (uchar) 254, (uchar) 254, (uchar) 11, (uchar) 1 }; - There is only one ddl log in the system and it is protected by a mutex - and there is a global struct that contains information about its current - state. +/* Number of phases per entry */ +const uchar ddl_log_entry_phases[DDL_LOG_LAST_ACTION]= +{ + 1, 1, 2, 3, 4, 1 +}; - History: - First version written in 2006 by Mikael Ronstrom - Second version written in 2020 by Monty ---------------------------------------------------------------------------*/ struct st_global_ddl_log { - /* - We need to adjust buffer size to be able to handle downgrades/upgrades - where IO_SIZE has changed. We'll set the buffer size such that we can - handle that the buffer size was upto 4 times bigger in the version - that wrote the DDL log. - */ - char file_entry_buf[4*IO_SIZE]; - char file_name_str[FN_REFLEN]; - char *file_name; + uchar *file_entry_buf; DDL_LOG_MEMORY_ENTRY *first_free; DDL_LOG_MEMORY_ENTRY *first_used; - uint num_entries; File file_id; - uint name_len; + uint num_entries; + uint name_pos; uint io_size; - bool inited; - bool do_release; - bool recovery_phase; - st_global_ddl_log() : inited(false), do_release(false) {} + bool initialized; + bool open; }; st_global_ddl_log global_ddl_log; mysql_mutex_t LOCK_gdl; +/* Positions to different data in a ddl log block */ #define DDL_LOG_ENTRY_TYPE_POS 0 +/* + Note that ACTION_TYPE and PHASE_POS must be after each other. + See update_phase() +*/ #define DDL_LOG_ACTION_TYPE_POS 1 #define DDL_LOG_PHASE_POS 2 #define DDL_LOG_NEXT_ENTRY_POS 4 -#define DDL_LOG_NAME_POS 8 +#define DDL_LOG_XID_POS 8 +#define DDL_LOG_ID_POS 16 + +/* + Position to where names are stored in the ddl log blocks. The current + value is stored in the header and can thus be changed if we need more + space for constants in the header +*/ +#define DDL_LOG_TMP_NAME_POS 48 + +/* Definitions for the ddl log header, the first block in the file */ +/* IO_SIZE is stored in the header and can thus be changed */ +#define DDL_LOG_IO_SIZE IO_SIZE + +/* Header is stored in positions 0-3 */ +#define DDL_LOG_IO_SIZE_POS 4 +#define DDL_LOG_NAME_OFFSET_POS 6 +/* Sum of the above variables */ +#define DDL_LOG_HEADER_SIZE 4+2+2 + +/** + Sync the ddl log file. + + @return Operation status + @retval FALSE Success + @retval TRUE Error +*/ + +static bool ddl_log_sync_file() +{ + DBUG_ENTER("ddl_log_sync_file"); + DBUG_RETURN(mysql_file_sync(global_ddl_log.file_id, MYF(MY_WME))); +} + +/* Same as above, but ensure we have the LOCK_gdb locked */ + +static bool ddl_log_sync_no_lock() +{ + DBUG_ENTER("ddl_log_sync_no_lock"); + + mysql_mutex_assert_owner(&LOCK_gdl); + DBUG_RETURN(ddl_log_sync_file()); +} + + +/** + Create ddl log file name. + @param file_name Filename setup +*/ + +static inline void create_ddl_log_file_name(char *file_name) +{ + fn_format(file_name, opt_ddl_recovery_file, mysql_data_home, ".log", 0); +} + + +/** + Write ddl log header. + + @return Operation status + @retval TRUE Error + @retval FALSE Success +*/ + +static bool write_ddl_log_header() +{ + uchar header[DDL_LOG_HEADER_SIZE]; + DBUG_ENTER("write_ddl_log_header"); + + memcpy(&header, ddl_log_file_magic, DDL_LOG_MAGIC_LENGTH); + int2store(&header[DDL_LOG_IO_SIZE_POS], global_ddl_log.io_size); + int2store(&header[DDL_LOG_NAME_OFFSET_POS], global_ddl_log.name_pos); + + if (mysql_file_pwrite(global_ddl_log.file_id, + header, sizeof(header), 0, + MYF(MY_WME | MY_NABP))) + DBUG_RETURN(TRUE); + DBUG_RETURN(ddl_log_sync_file()); +} -#define DDL_LOG_NUM_ENTRY_POS 0 -#define DDL_LOG_NAME_LEN_POS 4 -#define DDL_LOG_IO_SIZE_POS 8 /** Read one entry from ddl log file. - @param entry_no Entry number to read + @param entry_pos Entry number to read @return Operation status @retval true Error @retval false Success */ -static bool read_ddl_log_file_entry(uint entry_no) +static bool read_ddl_log_file_entry(uint entry_pos) { - bool error= FALSE; - File file_id= global_ddl_log.file_id; - uchar *file_entry_buf= (uchar*)global_ddl_log.file_entry_buf; + uchar *file_entry_buf= global_ddl_log.file_entry_buf; size_t io_size= global_ddl_log.io_size; DBUG_ENTER("read_ddl_log_file_entry"); mysql_mutex_assert_owner(&LOCK_gdl); - if (mysql_file_pread(file_id, file_entry_buf, io_size, io_size * entry_no, - MYF(MY_WME)) != io_size) - error= TRUE; - DBUG_RETURN(error); + DBUG_RETURN (mysql_file_pread(global_ddl_log.file_id, + file_entry_buf, io_size, + io_size * entry_pos, + MYF(MY_WME | MY_NABP))); } /** Write one entry to ddl log file. - @param entry_no Entry number to write + @param entry_pos Entry number to write - @return Operation status + @return @retval true Error @retval false Success */ -static bool write_ddl_log_file_entry(uint entry_no) +static bool write_ddl_log_file_entry(uint entry_pos) { bool error= FALSE; File file_id= global_ddl_log.file_id; - uchar *file_entry_buf= (uchar*)global_ddl_log.file_entry_buf; + uchar *file_entry_buf= global_ddl_log.file_entry_buf; DBUG_ENTER("write_ddl_log_file_entry"); - mysql_mutex_assert_owner(&LOCK_gdl); - if (mysql_file_pwrite(file_id, file_entry_buf, - IO_SIZE, IO_SIZE * entry_no, MYF(MY_WME)) != IO_SIZE) - error= TRUE; + mysql_mutex_assert_owner(&LOCK_gdl); // To be removed + DBUG_RETURN(mysql_file_pwrite(file_id, file_entry_buf, + global_ddl_log.io_size, + global_ddl_log.io_size * entry_pos, + MYF(MY_WME | MY_NABP))); DBUG_RETURN(error); } /** - Sync the ddl log file. + Update phase of ddl log entry - @return Operation status - @retval FALSE Success - @retval TRUE Error + @param entry_pos ddl_log entry to update + @param code Type of entry. Normally DDL_LOG_ENTRY_CODE or + DDL_IGNORE_LOG_ENTRY_CODE + @param phase New phase + + @return + @retval 0 ok + Â @retval 1 Write error. Error given + + This is done without locks as it's guaranteed to be atomic */ +static bool update_phase(uint entry_pos, uchar phase) +{ + DBUG_ENTER("update_phase"); -static bool ddl_log_sync_file() + DBUG_RETURN(mysql_file_pwrite(global_ddl_log.file_id, &phase, 1, + global_ddl_log.io_size * entry_pos + + DDL_LOG_PHASE_POS, + MYF(MY_WME | MY_NABP)) || + ddl_log_sync_file()); +} + + +static bool update_xid(uint entry_pos, ulonglong xid) { - DBUG_ENTER("ddl_log_sync_file"); - DBUG_RETURN(mysql_file_sync(global_ddl_log.file_id, MYF(MY_WME))); + uchar buff[8]; + DBUG_ENTER("update_xid"); + + int8store(buff, xid); + DBUG_RETURN(mysql_file_pwrite(global_ddl_log.file_id, buff, 8, + global_ddl_log.io_size * entry_pos + + DDL_LOG_XID_POS, + MYF(MY_WME | MY_NABP)) || + ddl_log_sync_file()); } -/** - Write ddl log header. +/* + Disable an execute entry - @return Operation status - @retval TRUE Error - @retval FALSE Success + @param entry_pos ddl_log entry to update + + Notes: + We don't need sync here as this is mainly done during + recover phase to mark already done entries. We instead sync all entries + at the same time. */ -static bool write_ddl_log_header() +static bool disable_execute_entry(uint entry_pos) { - uint16 const_var; - DBUG_ENTER("write_ddl_log_header"); - - int4store(&global_ddl_log.file_entry_buf[DDL_LOG_NUM_ENTRY_POS], - global_ddl_log.num_entries); - const_var= FN_REFLEN; - int4store(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_LEN_POS], - (ulong) const_var); - const_var= IO_SIZE; - int4store(&global_ddl_log.file_entry_buf[DDL_LOG_IO_SIZE_POS], - (ulong) const_var); - if (write_ddl_log_file_entry(0UL)) - { - sql_print_error("Error writing ddl log header"); - DBUG_RETURN(TRUE); - } - DBUG_RETURN(ddl_log_sync_file()); + uchar buff[1]; + DBUG_ENTER("disable_execute_entry"); + + buff[0]= DDL_IGNORE_LOG_ENTRY_CODE; + DBUG_RETURN(mysql_file_pwrite(global_ddl_log.file_id, buff, sizeof(buff), + global_ddl_log.io_size * entry_pos + + DDL_LOG_ENTRY_TYPE_POS, + MYF(MY_WME | MY_NABP))); } - -/** - Create ddl log file name. - @param file_name Filename setup +/* + Disable an execute entry */ -static inline void create_ddl_log_file_name(char *file_name) +bool ddl_log_disable_execute_entry(DDL_LOG_MEMORY_ENTRY **active_entry) { - strxmov(file_name, mysql_data_home, "/", "ddl_log.log", NullS); + bool res= disable_execute_entry((*active_entry)->entry_pos); + ddl_log_sync_no_lock(); + return res; } + /** Read header of ddl log file. @@ -201,50 +325,121 @@ static inline void create_ddl_log_file_name(char *file_name) of names in the ddl log and we also get information about the number of entries in the ddl log. - @return Last entry in ddl log (0 if no entries) + This is read only once at server startup, so no mutex is needed. + + @return Last entry in ddl log (0 if no entries). + @return -1 if log could not be opened or could not be read */ -static uint read_ddl_log_header() +static int read_ddl_log_header(const char *file_name) { - uchar *file_entry_buf= (uchar*)global_ddl_log.file_entry_buf; - char file_name[FN_REFLEN]; - uint entry_no; - bool successful_open= FALSE; + uchar header[DDL_LOG_HEADER_SIZE]; + int max_entry; + int file_id; + uint io_size; DBUG_ENTER("read_ddl_log_header"); - mysql_mutex_init(key_LOCK_gdl, &LOCK_gdl, MY_MUTEX_INIT_SLOW); - mysql_mutex_lock(&LOCK_gdl); - create_ddl_log_file_name(file_name); - if ((global_ddl_log.file_id= mysql_file_open(key_file_global_ddl_log, + if ((file_id= mysql_file_open(key_file_global_ddl_log, file_name, - O_RDWR | O_BINARY, MYF(0))) >= 0) + O_RDWR | O_BINARY, MYF(0))) < 0) + DBUG_RETURN(-1); + + if (mysql_file_read(file_id, + header, sizeof(header), MYF(MY_WME | MY_NABP))) { - if (read_ddl_log_file_entry(0UL)) - { - /* Write message into error log */ - sql_print_error("Failed to read ddl log file in recovery"); - } - else - successful_open= TRUE; + /* Write message into error log */ + sql_print_error("Failed to read ddl log file '%s' during recovery", + file_name); + goto err; } - if (successful_open) + + if (memcmp(header, ddl_log_file_magic, 4)) { - entry_no= uint4korr(&file_entry_buf[DDL_LOG_NUM_ENTRY_POS]); - global_ddl_log.name_len= uint4korr(&file_entry_buf[DDL_LOG_NAME_LEN_POS]); - global_ddl_log.io_size= uint4korr(&file_entry_buf[DDL_LOG_IO_SIZE_POS]); - DBUG_ASSERT(global_ddl_log.io_size <= - sizeof(global_ddl_log.file_entry_buf)); + /* Probably upgrade from MySQL 10.5 or earlier */ + sql_print_warning("Wrong header in %s. Assuming it is an old recovery file " + "from MariaDB 10.5 or earlier. Skipping DDL recovery", + file_name); + goto err; } - else + + io_size= uint2korr(&header[DDL_LOG_IO_SIZE_POS]); + global_ddl_log.name_pos= uint2korr(&header[DDL_LOG_NAME_OFFSET_POS]); + + max_entry= (uint) (mysql_file_seek(file_id, 0L, MY_SEEK_END, MYF(0)) / + io_size); + if (max_entry) + max_entry--; // Don't count first block + + if (!(global_ddl_log.file_entry_buf= (uchar*) + my_malloc(key_memory_DDL_LOG_MEMORY_ENTRY, io_size, + MYF(MY_WME | MY_ZEROFILL)))) + goto err; + + global_ddl_log.open= TRUE; + global_ddl_log.file_id= file_id; + global_ddl_log.num_entries= max_entry; + global_ddl_log.io_size= io_size; + DBUG_RETURN(max_entry); + +err: + if (file_id >= 0) + my_close(file_id, MYF(0)); + /* We return -1 to force the ddl log to be re-created */ + DBUG_RETURN(-1); +} + + +/* + Store and read strings in ddl log buffers + + Format is: + 2 byte: length (not counting end \0) + X byte: string value of length 'length' + 1 byte: \0 +*/ + +static uchar *store_string(uchar *pos, uchar *end, const LEX_CSTRING *str) +{ + uint32 length= (uint32) str->length; + if (unlikely(pos + 2 + length +1 >= end)) { - entry_no= 0; + DBUG_ASSERT(0); + return end; // Overflow } - global_ddl_log.first_free= NULL; - global_ddl_log.first_used= NULL; - global_ddl_log.num_entries= 0; - global_ddl_log.do_release= true; - mysql_mutex_unlock(&LOCK_gdl); - DBUG_RETURN(entry_no); + + int2store(pos, length); + if (likely(length)) + memcpy(pos+2, str->str, length); + pos[2+length]= 0; // Store end \0 + return pos + 2 + length +1; +} + + +static LEX_CSTRING get_string(uchar **pos, const uchar *end) +{ + LEX_CSTRING tmp; + uint32 length; + if (likely(*pos + 3 < end)) + { + length= uint2korr(*pos); + if (likely(*pos + 2 + length +1 < end)) + { + char *str= (char*) *pos+2; + *pos= *pos + 2 + length + 1; + tmp.str= str; + tmp.length= length; + return tmp; + } + } + /* + Overflow on read, should never happen + Set *pos to end to ensure any future calls also returns empty string + */ + DBUG_ASSERT(0); + *pos= (uchar*) end; + tmp.str= ""; + tmp.length= 0; + return tmp; } @@ -256,38 +451,28 @@ static uint read_ddl_log_header() static void set_global_from_ddl_log_entry(const DDL_LOG_ENTRY *ddl_log_entry) { + uchar *file_entry_buf= global_ddl_log.file_entry_buf, *pos, *end; + mysql_mutex_assert_owner(&LOCK_gdl); - global_ddl_log.file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= - (char)DDL_LOG_ENTRY_CODE; - global_ddl_log.file_entry_buf[DDL_LOG_ACTION_TYPE_POS]= - (char)ddl_log_entry->action_type; - global_ddl_log.file_entry_buf[DDL_LOG_PHASE_POS]= 0; - int4store(&global_ddl_log.file_entry_buf[DDL_LOG_NEXT_ENTRY_POS], - ddl_log_entry->next_entry); - DBUG_ASSERT(strlen(ddl_log_entry->name) < FN_REFLEN); - strmake(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS], - ddl_log_entry->name, FN_REFLEN - 1); - if (ddl_log_entry->action_type == DDL_LOG_RENAME_ACTION || - ddl_log_entry->action_type == DDL_LOG_REPLACE_ACTION || - ddl_log_entry->action_type == DDL_LOG_EXCHANGE_ACTION) - { - DBUG_ASSERT(strlen(ddl_log_entry->from_name) < FN_REFLEN); - strmake(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + FN_REFLEN], - ddl_log_entry->from_name, FN_REFLEN - 1); - } - else - global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + FN_REFLEN]= 0; - DBUG_ASSERT(strlen(ddl_log_entry->handler_name) < FN_REFLEN); - strmake(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + (2*FN_REFLEN)], - ddl_log_entry->handler_name, FN_REFLEN - 1); - if (ddl_log_entry->action_type == DDL_LOG_EXCHANGE_ACTION) - { - DBUG_ASSERT(strlen(ddl_log_entry->tmp_name) < FN_REFLEN); - strmake(&global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + (3*FN_REFLEN)], - ddl_log_entry->tmp_name, FN_REFLEN - 1); - } - else - global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS + (3*FN_REFLEN)]= 0; + + file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= (uchar) ddl_log_entry->entry_type; + file_entry_buf[DDL_LOG_ACTION_TYPE_POS]= (uchar) ddl_log_entry->action_type; + file_entry_buf[DDL_LOG_PHASE_POS]= (uchar) ddl_log_entry->phase; + int4store(file_entry_buf+DDL_LOG_NEXT_ENTRY_POS, ddl_log_entry->next_entry); + int8store(file_entry_buf+DDL_LOG_XID_POS, ddl_log_entry->xid); + int8store(file_entry_buf+DDL_LOG_ID_POS, ddl_log_entry->unique_id); + bzero(file_entry_buf+8, global_ddl_log.name_pos - DDL_LOG_ID_POS - 8); + + pos= file_entry_buf + global_ddl_log.name_pos; + end= file_entry_buf + global_ddl_log.io_size; + + pos= store_string(pos, end, &ddl_log_entry->handler_name); + pos= store_string(pos, end, &ddl_log_entry->db); + pos= store_string(pos, end, &ddl_log_entry->name); + pos= store_string(pos, end, &ddl_log_entry->from_db); + pos= store_string(pos, end, &ddl_log_entry->from_name); + pos= store_string(pos, end, &ddl_log_entry->tmp_name); + bzero(pos, global_ddl_log.io_size - (pos - file_entry_buf)); } @@ -303,30 +488,28 @@ static void set_global_from_ddl_log_entry(const DDL_LOG_ENTRY *ddl_log_entry) static void set_ddl_log_entry_from_global(DDL_LOG_ENTRY *ddl_log_entry, const uint read_entry) { - char *file_entry_buf= (char*) global_ddl_log.file_entry_buf; - uint inx; + uchar *file_entry_buf= global_ddl_log.file_entry_buf, *pos; + const uchar *end= file_entry_buf + global_ddl_log.io_size; uchar single_char; mysql_mutex_assert_owner(&LOCK_gdl); ddl_log_entry->entry_pos= read_entry; single_char= file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]; - ddl_log_entry->entry_type= (enum ddl_log_entry_code)single_char; + ddl_log_entry->entry_type= (enum ddl_log_entry_code) single_char; single_char= file_entry_buf[DDL_LOG_ACTION_TYPE_POS]; - ddl_log_entry->action_type= (enum ddl_log_action_code)single_char; + ddl_log_entry->action_type= (enum ddl_log_action_code) single_char; ddl_log_entry->phase= file_entry_buf[DDL_LOG_PHASE_POS]; ddl_log_entry->next_entry= uint4korr(&file_entry_buf[DDL_LOG_NEXT_ENTRY_POS]); - ddl_log_entry->name= &file_entry_buf[DDL_LOG_NAME_POS]; - inx= DDL_LOG_NAME_POS + global_ddl_log.name_len; - ddl_log_entry->from_name= &file_entry_buf[inx]; - inx+= global_ddl_log.name_len; - ddl_log_entry->handler_name= &file_entry_buf[inx]; - if (ddl_log_entry->action_type == DDL_LOG_EXCHANGE_ACTION) - { - inx+= global_ddl_log.name_len; - ddl_log_entry->tmp_name= &file_entry_buf[inx]; - } - else - ddl_log_entry->tmp_name= NULL; + ddl_log_entry->xid= uint8korr(file_entry_buf + DDL_LOG_XID_POS); + ddl_log_entry->unique_id= uint8korr(file_entry_buf + DDL_LOG_ID_POS); + + pos= file_entry_buf + global_ddl_log.name_pos; + ddl_log_entry->handler_name= get_string(&pos, end); + ddl_log_entry->db= get_string(&pos, end); + ddl_log_entry->name= get_string(&pos, end); + ddl_log_entry->from_db= get_string(&pos, end); + ddl_log_entry->from_name= get_string(&pos, end); + ddl_log_entry->tmp_name= get_string(&pos, end); } @@ -349,6 +532,7 @@ static bool read_ddl_log_entry(uint read_entry, DDL_LOG_ENTRY *ddl_log_entry) if (read_ddl_log_file_entry(read_entry)) { + sql_print_error("Failed to read entry %u from ddl log", read_entry); DBUG_RETURN(TRUE); } set_ddl_log_entry_from_global(ddl_log_entry, read_entry); @@ -357,68 +541,93 @@ static bool read_ddl_log_entry(uint read_entry, DDL_LOG_ENTRY *ddl_log_entry) /** - Initialise ddl log. - - Write the header of the ddl log file and length of names. Also set - number of entries to zero. + Create the ddl log file @return Operation status @retval TRUE Error @retval FALSE Success */ -static bool init_ddl_log() +static bool create_ddl_log() { char file_name[FN_REFLEN]; - DBUG_ENTER("init_ddl_log"); + DBUG_ENTER("create_ddl_log"); - if (global_ddl_log.inited) - goto end; + global_ddl_log.open= 0; + global_ddl_log.num_entries= 0; + global_ddl_log.name_pos= DDL_LOG_TMP_NAME_POS; - global_ddl_log.io_size= IO_SIZE; - global_ddl_log.name_len= FN_REFLEN; + /* + Fix file_entry_buf if the old log had a different io_size or if open of old + log didn't succeed. + */ + if (global_ddl_log.io_size != DDL_LOG_IO_SIZE) + { + uchar *ptr= (uchar*) + my_realloc(key_memory_DDL_LOG_MEMORY_ENTRY, + global_ddl_log.file_entry_buf, IO_SIZE, + MYF(MY_WME | MY_ALLOW_ZERO_PTR)); + if (ptr) // Resize succeded */ + { + global_ddl_log.file_entry_buf= ptr; + global_ddl_log.io_size= IO_SIZE; + } + if (!global_ddl_log.file_entry_buf) + DBUG_RETURN(TRUE); + } + DBUG_ASSERT(global_ddl_log.file_entry_buf); + bzero(global_ddl_log.file_entry_buf, global_ddl_log.io_size); create_ddl_log_file_name(file_name); - if ((global_ddl_log.file_id= mysql_file_create(key_file_global_ddl_log, - file_name, CREATE_MODE, - O_RDWR | O_TRUNC | O_BINARY, - MYF(MY_WME))) < 0) + if ((global_ddl_log.file_id= + mysql_file_create(key_file_global_ddl_log, + file_name, CREATE_MODE, + O_RDWR | O_TRUNC | O_BINARY, + MYF(MY_WME | ME_ERROR_LOG))) < 0) { /* Couldn't create ddl log file, this is serious error */ - sql_print_error("Failed to open ddl log file"); + sql_print_error("Failed to create ddl log file: %s", file_name); + my_free(global_ddl_log.file_entry_buf); + global_ddl_log.file_entry_buf= 0; DBUG_RETURN(TRUE); } - global_ddl_log.inited= TRUE; if (write_ddl_log_header()) { (void) mysql_file_close(global_ddl_log.file_id, MYF(MY_WME)); - global_ddl_log.inited= FALSE; + my_free(global_ddl_log.file_entry_buf); + global_ddl_log.file_entry_buf= 0; DBUG_RETURN(TRUE); } - -end: + global_ddl_log.open= TRUE; DBUG_RETURN(FALSE); } /** - Sync ddl log file. - - @return Operation status - @retval TRUE Error - @retval FALSE Success + Open ddl log and initialise ddl log variables */ -static bool ddl_log_sync_no_lock() +bool ddl_log_initialize() { - DBUG_ENTER("ddl_log_sync_no_lock"); + int num_entries; + char file_name[FN_REFLEN]; + DBUG_ENTER("ddl_log_initialize"); - mysql_mutex_assert_owner(&LOCK_gdl); - if ((!global_ddl_log.recovery_phase) && - init_ddl_log()) + bzero(&global_ddl_log, sizeof(global_ddl_log)); + global_ddl_log.file_id= (File) -1; + global_ddl_log.initialized= 1; + + mysql_mutex_init(key_LOCK_gdl, &LOCK_gdl, MY_MUTEX_INIT_SLOW); + + create_ddl_log_file_name(file_name); + if (likely((num_entries= read_ddl_log_header(file_name)) < 0)) { - DBUG_RETURN(TRUE); + /* Fatal error, log not opened. Recreate it */ + if (create_ddl_log()) + DBUG_RETURN(1); } - DBUG_RETURN(ddl_log_sync_file()); + else + global_ddl_log.num_entries= (uint) num_entries; + DBUG_RETURN(0); } @@ -442,66 +651,123 @@ static bool ddl_log_sync_no_lock() action. Thus the first phase will drop y and the second phase will rename x -> y. - @param entry_no Entry position of record to change + @param entry_pos Entry position of record to change @return Operation status @retval TRUE Error @retval FALSE Success */ -static bool ddl_log_increment_phase_no_lock(uint entry_no) +static bool ddl_log_increment_phase_no_lock(uint entry_pos) { - uchar *file_entry_buf= (uchar*)global_ddl_log.file_entry_buf; + uchar *file_entry_buf= global_ddl_log.file_entry_buf; DBUG_ENTER("ddl_log_increment_phase_no_lock"); mysql_mutex_assert_owner(&LOCK_gdl); - if (!read_ddl_log_file_entry(entry_no)) + if (!read_ddl_log_file_entry(entry_pos)) { - if (file_entry_buf[DDL_LOG_ENTRY_TYPE_POS] == DDL_LOG_ENTRY_CODE) + ddl_log_entry_code code= ((ddl_log_entry_code) + file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]); + ddl_log_action_code action= ((ddl_log_action_code) + file_entry_buf[DDL_LOG_ACTION_TYPE_POS]); + + if (code == DDL_LOG_ENTRY_CODE && action < (uint) DDL_LOG_LAST_ACTION) { /* - Log entry, if complete mark it done (IGNORE). - Otherwise increase the phase by one. + Log entry: + Increase the phase by one. If complete mark it done (IGNORE). */ - if (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_DELETE_ACTION || - file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_RENAME_ACTION || - (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_REPLACE_ACTION && - file_entry_buf[DDL_LOG_PHASE_POS] == 1) || - (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_EXCHANGE_ACTION && - file_entry_buf[DDL_LOG_PHASE_POS] >= EXCH_PHASE_TEMP_TO_FROM)) - file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= DDL_IGNORE_LOG_ENTRY_CODE; - else if (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_REPLACE_ACTION) - { - DBUG_ASSERT(file_entry_buf[DDL_LOG_PHASE_POS] == 0); - file_entry_buf[DDL_LOG_PHASE_POS]= 1; - } - else if (file_entry_buf[DDL_LOG_ACTION_TYPE_POS] == DDL_LOG_EXCHANGE_ACTION) - { - DBUG_ASSERT(file_entry_buf[DDL_LOG_PHASE_POS] <= - EXCH_PHASE_FROM_TO_NAME); - file_entry_buf[DDL_LOG_PHASE_POS]++; - } - else + char phase= file_entry_buf[DDL_LOG_PHASE_POS]+ 1; + if (ddl_log_entry_phases[action] <= phase) { - DBUG_ASSERT(0); + DBUG_ASSERT(phase == ddl_log_entry_phases[action]); + /* Same effect as setting DDL_IGNORE_LOG_ENTRY_CODE */ + phase= DDL_LOG_FINAL_PHASE; } - if (write_ddl_log_file_entry(entry_no)) - { - sql_print_error("Error in deactivating log entry. Position = %u", - entry_no); + file_entry_buf[DDL_LOG_PHASE_POS]= phase; + if (update_phase(entry_pos, phase)) DBUG_RETURN(TRUE); - } + } + else + { + /* + Trying to deativate an execute entry or already deactive entry. + This should not happen + */ + DBUG_ASSERT(0); } } else { - sql_print_error("Failed in reading entry before deactivating it"); + sql_print_error("Failed in reading entry before updating it"); DBUG_RETURN(TRUE); } DBUG_RETURN(FALSE); } +/* + Ignore errors from the file system about: + - Non existing tables or file (from drop table or delete file) + - Error about tables files that already exists. +*/ + +class ddl_log_error_handler : public Internal_error_handler +{ +public: + int handled_errors; + int unhandled_errors; + + ddl_log_error_handler() : handled_errors(0), unhandled_errors(0) + {} + + bool handle_condition(THD *thd, + uint sql_errno, + const char* sqlstate, + Sql_condition::enum_warning_level *level, + const char* msg, + Sql_condition ** cond_hdl) + { + *cond_hdl= NULL; + if (non_existing_table_error(sql_errno) || sql_errno == EE_LINK) + { + handled_errors++; + return TRUE; + } + + if (*level == Sql_condition::WARN_LEVEL_ERROR) + unhandled_errors++; + return FALSE; + } + + bool safely_trapped_errors() + { + return (handled_errors > 0 && unhandled_errors == 0); + } +}; + + +/* + Build a filename for a table, trigger file or .frm + Delete also any temporary file suffixed with ~ +*/ + +static void build_filename_and_delete_tmp_file(char *path, size_t path_length, + const LEX_CSTRING *db, + const LEX_CSTRING *name, + const char *ext, + PSI_file_key psi_key) +{ + uint length= build_table_filename(path, path_length-1, + db->str, name->str, ext, 0); + + path[length]= '~'; + path[length+1]= 0; + (void) mysql_file_delete(psi_key, path, MYF(0)); + path[length]= 0; +} + + /** Execute one action in a ddl log entry @@ -512,180 +778,329 @@ static bool ddl_log_increment_phase_no_lock(uint entry_no) @retval FALSE Success */ -static int execute_ddl_log_action(THD *thd, DDL_LOG_ENTRY *ddl_log_entry) +static int ddl_log_execute_action(THD *thd, MEM_ROOT *mem_root, + DDL_LOG_ENTRY *ddl_log_entry) { - bool frm_action= FALSE; LEX_CSTRING handler_name; handler *file= NULL; - MEM_ROOT mem_root; - int error= 1; - char to_path[FN_REFLEN]; - char from_path[FN_REFLEN]; - handlerton *hton; - DBUG_ENTER("execute_ddl_log_action"); + char to_path[FN_REFLEN+1], from_path[FN_REFLEN+1]; + handlerton *hton= 0; + ddl_log_error_handler no_such_table_handler; + uint entry_pos= ddl_log_entry->entry_pos; + int error; + bool frm_action= FALSE; + DBUG_ENTER("ddl_log_execute_action"); mysql_mutex_assert_owner(&LOCK_gdl); - if (ddl_log_entry->entry_type == DDL_IGNORE_LOG_ENTRY_CODE) - { - DBUG_RETURN(FALSE); - } DBUG_PRINT("ddl_log", - ("execute type %c next %u name '%s' from_name '%s' handler '%s'" - " tmp_name '%s'", - ddl_log_entry->action_type, - ddl_log_entry->next_entry, - ddl_log_entry->name, - ddl_log_entry->from_name, - ddl_log_entry->handler_name, - ddl_log_entry->tmp_name)); - handler_name.str= (char*)ddl_log_entry->handler_name; - handler_name.length= strlen(ddl_log_entry->handler_name); - init_sql_alloc(key_memory_gdl, &mem_root, TABLE_ALLOC_BLOCK_SIZE, 0, - MYF(MY_THREAD_SPECIFIC)); - if (!strcmp(ddl_log_entry->handler_name, reg_ext)) + ("entry type: %u action type: %u phase: %u next: %u " + "handler: '%s' name: '%s' from_name: '%s' tmp_name: '%s'", + (uint) ddl_log_entry->entry_type, + (uint) ddl_log_entry->action_type, + (uint) ddl_log_entry->phase, + ddl_log_entry->next_entry, + ddl_log_entry->handler_name.str, + ddl_log_entry->name.str, + ddl_log_entry->from_name.str, + ddl_log_entry->tmp_name.str)); + + if (ddl_log_entry->entry_type == DDL_IGNORE_LOG_ENTRY_CODE || + ddl_log_entry->phase == DDL_LOG_FINAL_PHASE) + DBUG_RETURN(FALSE); + + handler_name= ddl_log_entry->handler_name; + thd->push_internal_handler(&no_such_table_handler); + + if (!strcmp(ddl_log_entry->handler_name.str, reg_ext)) frm_action= TRUE; - else + else if (ddl_log_entry->handler_name.length) { - plugin_ref plugin= ha_resolve_by_name(thd, &handler_name, false); + plugin_ref plugin= my_plugin_lock_by_name(thd, &handler_name, + MYSQL_STORAGE_ENGINE_PLUGIN); if (!plugin) { my_error(ER_UNKNOWN_STORAGE_ENGINE, MYF(0), ddl_log_entry->handler_name); - goto error; + goto end; } - hton= plugin_data(plugin, handlerton*); - file= get_new_handler((TABLE_SHARE*)0, &mem_root, hton); + hton= plugin_hton(plugin); + file= get_new_handler((TABLE_SHARE*)0, mem_root, hton); if (unlikely(!file)) - goto error; + goto end; } - switch (ddl_log_entry->action_type) + + switch (ddl_log_entry->action_type) { + case DDL_LOG_REPLACE_ACTION: + case DDL_LOG_DELETE_ACTION: { - case DDL_LOG_REPLACE_ACTION: - case DDL_LOG_DELETE_ACTION: + if (ddl_log_entry->phase == 0) { - if (ddl_log_entry->phase == 0) + if (frm_action) { - if (frm_action) - { - strxmov(to_path, ddl_log_entry->name, reg_ext, NullS); - if (unlikely((error= mysql_file_delete(key_file_frm, to_path, - MYF(MY_WME | - MY_IGNORE_ENOENT))))) - break; + strxmov(to_path, ddl_log_entry->name.str, reg_ext, NullS); + if (unlikely((error= mysql_file_delete(key_file_frm, to_path, + MYF(MY_WME | + MY_IGNORE_ENOENT))))) + break; #ifdef WITH_PARTITION_STORAGE_ENGINE - strxmov(to_path, ddl_log_entry->name, PAR_EXT, NullS); - (void) mysql_file_delete(key_file_partition_ddl_log, to_path, - MYF(0)); + strxmov(to_path, ddl_log_entry->name.str, PAR_EXT, NullS); + (void) mysql_file_delete(key_file_partition_ddl_log, to_path, + MYF(0)); #endif - } - else + } + else + { + if (unlikely((error= hton->drop_table(hton, ddl_log_entry->name.str)))) { - if (unlikely((error= hton->drop_table(hton, ddl_log_entry->name)))) - { - if (!non_existing_table_error(error)) - break; - } + if (!non_existing_table_error(error)) + break; } - if ((ddl_log_increment_phase_no_lock(ddl_log_entry->entry_pos))) - break; - (void) ddl_log_sync_no_lock(); - error= 0; - if (ddl_log_entry->action_type == DDL_LOG_DELETE_ACTION) - break; } - DBUG_ASSERT(ddl_log_entry->action_type == DDL_LOG_REPLACE_ACTION); - /* - Fall through and perform the rename action of the replace - action. We have already indicated the success of the delete - action in the log entry by stepping up the phase. - */ + if (ddl_log_increment_phase_no_lock(entry_pos)) + break; + (void) ddl_log_sync_no_lock(); + error= 0; + if (ddl_log_entry->action_type == DDL_LOG_DELETE_ACTION) + break; } - /* fall through */ - case DDL_LOG_RENAME_ACTION: + } + DBUG_ASSERT(ddl_log_entry->action_type == DDL_LOG_REPLACE_ACTION); + /* + Fall through and perform the rename action of the replace + action. We have already indicated the success of the delete + action in the log entry by stepping up the phase. + */ + /* fall through */ + case DDL_LOG_RENAME_ACTION: + { + error= TRUE; + if (frm_action) { - error= TRUE; - if (frm_action) - { - strxmov(to_path, ddl_log_entry->name, reg_ext, NullS); - strxmov(from_path, ddl_log_entry->from_name, reg_ext, NullS); - if (mysql_file_rename(key_file_frm, from_path, to_path, MYF(MY_WME))) - break; + strxmov(to_path, ddl_log_entry->name.str, reg_ext, NullS); + strxmov(from_path, ddl_log_entry->from_name.str, reg_ext, NullS); + (void) mysql_file_rename(key_file_frm, from_path, to_path, MYF(MY_WME)); #ifdef WITH_PARTITION_STORAGE_ENGINE - strxmov(to_path, ddl_log_entry->name, PAR_EXT, NullS); - strxmov(from_path, ddl_log_entry->from_name, PAR_EXT, NullS); - (void) mysql_file_rename(key_file_partition_ddl_log, from_path, to_path, MYF(MY_WME)); + strxmov(to_path, ddl_log_entry->name.str, PAR_EXT, NullS); + strxmov(from_path, ddl_log_entry->from_name.str, PAR_EXT, NullS); + (void) mysql_file_rename(key_file_partition_ddl_log, from_path, to_path, + MYF(MY_WME)); #endif + } + else + (void) file->ha_rename_table(ddl_log_entry->from_name.str, + ddl_log_entry->name.str); + if (ddl_log_increment_phase_no_lock(entry_pos)) + break; + (void) ddl_log_sync_no_lock(); + break; + } + case DDL_LOG_EXCHANGE_ACTION: + { + /* We hold LOCK_gdl, so we can alter global_ddl_log.file_entry_buf */ + uchar *file_entry_buf= global_ddl_log.file_entry_buf; + /* not yet implemented for frm */ + DBUG_ASSERT(!frm_action); + /* + Using a case-switch here to revert all currently done phases, + since it will fall through until the first phase is undone. + */ + switch (ddl_log_entry->phase) { + case EXCH_PHASE_TEMP_TO_FROM: + /* tmp_name -> from_name possibly done */ + (void) file->ha_rename_table(ddl_log_entry->from_name.str, + ddl_log_entry->tmp_name.str); + /* decrease the phase and sync */ + file_entry_buf[DDL_LOG_PHASE_POS]--; + if (write_ddl_log_file_entry(entry_pos)) + break; + (void) ddl_log_sync_no_lock(); + /* fall through */ + case EXCH_PHASE_FROM_TO_NAME: + /* from_name -> name possibly done */ + (void) file->ha_rename_table(ddl_log_entry->name.str, + ddl_log_entry->from_name.str); + /* decrease the phase and sync */ + file_entry_buf[DDL_LOG_PHASE_POS]--; + if (write_ddl_log_file_entry(entry_pos)) + break; + (void) ddl_log_sync_no_lock(); + /* fall through */ + case EXCH_PHASE_NAME_TO_TEMP: + /* name -> tmp_name possibly done */ + (void) file->ha_rename_table(ddl_log_entry->tmp_name.str, + ddl_log_entry->name.str); + /* disable the entry and sync */ + file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= DDL_IGNORE_LOG_ENTRY_CODE; + (void) write_ddl_log_file_entry(entry_pos); + (void) ddl_log_sync_no_lock(); + break; + } + break; + } + case DDL_LOG_RENAME_TABLE_ACTION: + { + /* + We should restore things by renaming from + 'entry->name' to 'entry->from_name' + + In the following code 'to_' stands for what the table was renamed to + that we have to rename back. + */ + size_t fr_length, to_length; + LEX_CSTRING from_table, to_table, to_converted_name; + from_table= ddl_log_entry->from_name; + to_table= ddl_log_entry->name; + + /* Some functions wants to have the lower case table name as an argument */ + if (lower_case_table_names) + { + uint errors; + to_converted_name.str= to_path; + to_converted_name.length= + strconvert(system_charset_info, to_table.str, to_table.length, + files_charset_info, from_path, FN_REFLEN, &errors); + } + else + to_converted_name= to_table; + + switch (ddl_log_entry->phase) { + case DDL_RENAME_PHASE_TRIGGER: + { + MDL_request mdl_request; + + build_filename_and_delete_tmp_file(to_path, sizeof(to_path), + &ddl_log_entry->db, + &ddl_log_entry->name, + TRG_EXT, + key_file_trg); + build_filename_and_delete_tmp_file(from_path, sizeof(from_path), + &ddl_log_entry->from_db, + &ddl_log_entry->from_name, + TRG_EXT, key_file_trg); + + if (!access(from_path, F_OK)) + { + /* + The original file was never renamed or we crashed in recovery + just after renaming back the file. + In this case the current file is correct and we can remove any + left over copied files + */ + (void) mysql_file_delete(key_file_trg, to_path, MYF(0)); } - else + else if (!access(to_path, F_OK)) { - if (file->ha_rename_table(ddl_log_entry->from_name, - ddl_log_entry->name)) - break; + /* .TRG file was renamed. Rename it back */ + /* + We have to create a MDL lock as change_table_names() checks that we + have a mdl locks for the table + */ + MDL_REQUEST_INIT(&mdl_request, MDL_key::TABLE, + ddl_log_entry->db.str, + to_converted_name.str, + MDL_EXCLUSIVE, MDL_EXPLICIT); + error= thd->mdl_context.acquire_lock(&mdl_request, 1); + /* acquire_locks() should never fail during recovery */ + DBUG_ASSERT(error == 0); + + (void) Table_triggers_list::change_table_name(thd, + &ddl_log_entry->db, + &to_table, + &to_converted_name, + &ddl_log_entry->from_db, + &from_table); + + thd->mdl_context.release_lock(mdl_request.ticket); } - if ((ddl_log_increment_phase_no_lock(ddl_log_entry->entry_pos))) + if (ddl_log_increment_phase_no_lock(entry_pos)) break; (void) ddl_log_sync_no_lock(); - error= FALSE; - break; } - case DDL_LOG_EXCHANGE_ACTION: + /* fall through */ + case DDL_RENAME_PHASE_STAT: { - /* We hold LOCK_gdl, so we can alter global_ddl_log.file_entry_buf */ - char *file_entry_buf= (char*)&global_ddl_log.file_entry_buf; - /* not yet implemented for frm */ - DBUG_ASSERT(!frm_action); - /* - Using a case-switch here to revert all currently done phases, - since it will fall through until the first phase is undone. - */ - switch (ddl_log_entry->phase) { - case EXCH_PHASE_TEMP_TO_FROM: - /* tmp_name -> from_name possibly done */ - (void) file->ha_rename_table(ddl_log_entry->from_name, - ddl_log_entry->tmp_name); - /* decrease the phase and sync */ - file_entry_buf[DDL_LOG_PHASE_POS]--; - if (write_ddl_log_file_entry(ddl_log_entry->entry_pos)) - break; - if (ddl_log_sync_no_lock()) - break; - /* fall through */ - case EXCH_PHASE_FROM_TO_NAME: - /* from_name -> name possibly done */ - (void) file->ha_rename_table(ddl_log_entry->name, - ddl_log_entry->from_name); - /* decrease the phase and sync */ - file_entry_buf[DDL_LOG_PHASE_POS]--; - if (write_ddl_log_file_entry(ddl_log_entry->entry_pos)) - break; - if (ddl_log_sync_no_lock()) - break; - /* fall through */ - case EXCH_PHASE_NAME_TO_TEMP: - /* name -> tmp_name possibly done */ - (void) file->ha_rename_table(ddl_log_entry->tmp_name, - ddl_log_entry->name); - /* disable the entry and sync */ - file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= DDL_IGNORE_LOG_ENTRY_CODE; - if (write_ddl_log_file_entry(ddl_log_entry->entry_pos)) - break; - if (ddl_log_sync_no_lock()) - break; - error= FALSE; - break; - default: - DBUG_ASSERT(0); - break; + (void) rename_table_in_stat_tables(thd, + &ddl_log_entry->db, + &to_converted_name, + &ddl_log_entry->from_db, + &from_table); + if (ddl_log_increment_phase_no_lock(entry_pos)) + break; + (void) ddl_log_sync_no_lock(); + } + /* fall through */ + case DDL_RENAME_PHASE_TABLE: + /* Restore frm and table to original names */ + to_length= build_table_filename(to_path, sizeof(to_path) - 1, + ddl_log_entry->db.str, + ddl_log_entry->name.str, + reg_ext, 0); + fr_length= build_table_filename(from_path, sizeof(from_path) - 1, + ddl_log_entry->from_db.str, + ddl_log_entry->from_name.str, + reg_ext, 0); + (void) mysql_file_rename(key_file_frm, to_path, from_path, MYF(MY_WME)); + + if (file->needs_lower_case_filenames()) + { + build_lower_case_table_filename(to_path, sizeof(to_path) - 1, + &ddl_log_entry->db, + &to_table, 0); + build_lower_case_table_filename(from_path, sizeof(from_path) - 1, + &ddl_log_entry->from_db, + &from_table, 0); + } + else + { + /* remove extension from file name */ + DBUG_ASSERT(to_length != 0 && fr_length != 0); + to_path[to_length - reg_ext_length]= 0; + from_path[fr_length - reg_ext_length]= 0; } + file->ha_rename_table(to_path, from_path); + /* disable the entry and sync */ + (void) update_phase(entry_pos, DDL_LOG_FINAL_PHASE); break; - } default: DBUG_ASSERT(0); break; + } + break; + } + case DDL_LOG_RENAME_VIEW_ACTION: + { + LEX_CSTRING from_table, to_table; + from_table= ddl_log_entry->from_name; + to_table= ddl_log_entry->name; + + /* Delete any left over .frm~ files */ + build_filename_and_delete_tmp_file(to_path, sizeof(to_path) - 1, + &ddl_log_entry->db, + &ddl_log_entry->name, + reg_ext, + key_file_fileparser); + build_filename_and_delete_tmp_file(from_path, sizeof(from_path) - 1, + &ddl_log_entry->from_db, + &ddl_log_entry->from_name, + reg_ext, key_file_fileparser); + + /* Rename view back if the original rename did succeed */ + if (!access(to_path, F_OK)) + (void) mysql_rename_view(thd, + &ddl_log_entry->from_db, &from_table, + &ddl_log_entry->db, &to_table); + (void) update_phase(entry_pos, DDL_LOG_FINAL_PHASE); } + break; + default: + DBUG_ASSERT(0); + break; + } + +end: delete file; -error: - free_root(&mem_root, MYF(0)); + error= no_such_table_handler.unhandled_errors > 0; + thd->pop_internal_handler(); DBUG_RETURN(error); } @@ -694,36 +1109,36 @@ error: Get a free entry in the ddl log @param[out] active_entry A ddl log memory entry returned + @param[out] write_header Set to 1 if ddl log was enlarged @return Operation status @retval TRUE Error @retval FALSE Success */ -static bool get_free_ddl_log_entry(DDL_LOG_MEMORY_ENTRY **active_entry, - bool *write_header) +static bool ddl_log_get_free_entry(DDL_LOG_MEMORY_ENTRY **active_entry) { DDL_LOG_MEMORY_ENTRY *used_entry; DDL_LOG_MEMORY_ENTRY *first_used= global_ddl_log.first_used; - DBUG_ENTER("get_free_ddl_log_entry"); + DBUG_ENTER("ddl_log_get_free_entry"); if (global_ddl_log.first_free == NULL) { - if (!(used_entry= (DDL_LOG_MEMORY_ENTRY*)my_malloc(key_memory_DDL_LOG_MEMORY_ENTRY, - sizeof(DDL_LOG_MEMORY_ENTRY), MYF(MY_WME)))) + if (!(used_entry= ((DDL_LOG_MEMORY_ENTRY*) + my_malloc(key_memory_DDL_LOG_MEMORY_ENTRY, + sizeof(DDL_LOG_MEMORY_ENTRY), MYF(MY_WME))))) { sql_print_error("Failed to allocate memory for ddl log free list"); + *active_entry= 0; DBUG_RETURN(TRUE); } global_ddl_log.num_entries++; used_entry->entry_pos= global_ddl_log.num_entries; - *write_header= TRUE; } else { used_entry= global_ddl_log.first_free; global_ddl_log.first_free= used_entry->next_log_entry; - *write_header= FALSE; } /* Move from free list to used list @@ -741,6 +1156,31 @@ static bool get_free_ddl_log_entry(DDL_LOG_MEMORY_ENTRY **active_entry, /** + Release a log memory entry. + @param log_memory_entry Log memory entry to release +*/ + +void ddl_log_release_memory_entry(DDL_LOG_MEMORY_ENTRY *log_entry) +{ + DDL_LOG_MEMORY_ENTRY *next_log_entry= log_entry->next_log_entry; + DDL_LOG_MEMORY_ENTRY *prev_log_entry= log_entry->prev_log_entry; + DBUG_ENTER("ddl_log_release_memory_entry"); + + mysql_mutex_assert_owner(&LOCK_gdl); + log_entry->next_log_entry= global_ddl_log.first_free; + global_ddl_log.first_free= log_entry; + + if (prev_log_entry) + prev_log_entry->next_log_entry= next_log_entry; + else + global_ddl_log.first_used= next_log_entry; + if (next_log_entry) + next_log_entry->prev_log_entry= prev_log_entry; + DBUG_VOID_RETURN; +} + + +/** Execute one entry in the ddl log. Executing an entry means executing a linked list of actions. @@ -756,30 +1196,33 @@ static bool ddl_log_execute_entry_no_lock(THD *thd, uint first_entry) { DDL_LOG_ENTRY ddl_log_entry; uint read_entry= first_entry; + MEM_ROOT mem_root; DBUG_ENTER("ddl_log_execute_entry_no_lock"); mysql_mutex_assert_owner(&LOCK_gdl); + init_sql_alloc(key_memory_gdl, &mem_root, TABLE_ALLOC_BLOCK_SIZE, 0, + MYF(MY_THREAD_SPECIFIC)); do { if (read_ddl_log_entry(read_entry, &ddl_log_entry)) { - /* Write to error log and continue with next log entry */ - sql_print_error("Failed to read entry = %u from ddl log", - read_entry); + /* Error logged to error log. Continue with next log entry */ break; } DBUG_ASSERT(ddl_log_entry.entry_type == DDL_LOG_ENTRY_CODE || ddl_log_entry.entry_type == DDL_IGNORE_LOG_ENTRY_CODE); - if (execute_ddl_log_action(thd, &ddl_log_entry)) + if (ddl_log_execute_action(thd, &mem_root, &ddl_log_entry)) { /* Write to error log and continue with next log entry */ - sql_print_error("Failed to execute action for entry = %u from ddl log", + sql_print_error("Failed to execute action for entry %u from ddl log", read_entry); break; } read_entry= ddl_log_entry.next_entry; } while (read_entry); + + free_root(&mem_root, MYF(0)); DBUG_RETURN(FALSE); } @@ -806,154 +1249,134 @@ static bool ddl_log_execute_entry_no_lock(THD *thd, uint first_entry) bool ddl_log_write_entry(DDL_LOG_ENTRY *ddl_log_entry, DDL_LOG_MEMORY_ENTRY **active_entry) { - bool error, write_header; + bool error; + uchar *pos, *end; DBUG_ENTER("ddl_log_write_entry"); mysql_mutex_assert_owner(&LOCK_gdl); - if (init_ddl_log()) - { + if (!global_ddl_log.open) DBUG_RETURN(TRUE); - } + + ddl_log_entry->entry_type= DDL_LOG_ENTRY_CODE; set_global_from_ddl_log_entry(ddl_log_entry); - if (get_free_ddl_log_entry(active_entry, &write_header)) - { + if (ddl_log_get_free_entry(active_entry)) DBUG_RETURN(TRUE); - } + error= FALSE; + pos= global_ddl_log.file_entry_buf + global_ddl_log.name_pos; + end= global_ddl_log.file_entry_buf + global_ddl_log.io_size; DBUG_PRINT("ddl_log", - ("write type %c next %u name '%s' from_name '%s' handler '%s'" - " tmp_name '%s'", - (char) global_ddl_log.file_entry_buf[DDL_LOG_ACTION_TYPE_POS], - ddl_log_entry->next_entry, - (char*) &global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS], - (char*) &global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS - + FN_REFLEN], - (char*) &global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS - + (2*FN_REFLEN)], - (char*) &global_ddl_log.file_entry_buf[DDL_LOG_NAME_POS - + (3*FN_REFLEN)])); + ("type: %c next: %u handler: %s " + "to_name: '%s.%s' from_name: '%s.%s' " + "tmp_name: '%s'", + (char) global_ddl_log.file_entry_buf[DDL_LOG_ACTION_TYPE_POS], + ddl_log_entry->next_entry, + get_string(&pos, end).str, // Handler + get_string(&pos, end).str, // to db.table + get_string(&pos, end).str, + get_string(&pos, end).str, // From db.table + get_string(&pos, end).str, + get_string(&pos, end).str)); // Tmp name + if (unlikely(write_ddl_log_file_entry((*active_entry)->entry_pos))) { + ddl_log_release_memory_entry(*active_entry); + *active_entry= 0; error= TRUE; - sql_print_error("Failed to write entry_no = %u", + sql_print_error("Failed to write entry_pos = %u", (*active_entry)->entry_pos); } - if (write_header && likely(!error)) - { - (void) ddl_log_sync_no_lock(); - if (write_ddl_log_header()) - error= TRUE; - } - if (unlikely(error)) - ddl_log_release_memory_entry(*active_entry); DBUG_RETURN(error); } /** - @brief Write final entry in the ddl log. + @brief Write or update execute entry in the ddl log. - @details This is the last write in the ddl log. The previous log entries - have already been written but not yet synched to disk. - We write a couple of log entries that describes action to perform. - This entries are set-up in a linked list, however only when a first - execute entry is put as the first entry these will be executed. - This routine writes this first. + @details An execute entry points to the first entry that should + be excuted during recovery. In some cases it's only written once, + in other cases it's updated for each log entry to point to the new + header for the list. + + When called, the previous log entries have already been written but not yet + synched to disk. We write a couple of log entries that describes + action to perform. This entries are set-up in a linked list, + however only when an execute entry is put as the first entry these will be + executed during recovery. @param first_entry First entry in linked list of entries - to execute, if 0 = NULL it means that - the entry is removed and the entries - are put into the free list. - @param complete Flag indicating we are simply writing - info about that entry has been completed + to execute. @param[in,out] active_entry Entry to execute, 0 = NULL if the entry is written first time and needs to be returned. In this case the entry written is returned in this parameter - @return Operation status @retval TRUE Error @retval FALSE Success */ bool ddl_log_write_execute_entry(uint first_entry, - bool complete, DDL_LOG_MEMORY_ENTRY **active_entry) { - bool write_header= FALSE; - char *file_entry_buf= (char*)global_ddl_log.file_entry_buf; + uchar *file_entry_buf= global_ddl_log.file_entry_buf; + bool got_free_entry= 0; DBUG_ENTER("ddl_log_write_execute_entry"); mysql_mutex_assert_owner(&LOCK_gdl); - if (init_ddl_log()) - { - DBUG_RETURN(TRUE); - } - if (!complete) - { - /* - We haven't synched the log entries yet, we synch them now before - writing the execute entry. If complete is true we haven't written - any log entries before, we are only here to write the execute - entry to indicate it is done. - */ - (void) ddl_log_sync_no_lock(); - file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= (char)DDL_LOG_EXECUTE_CODE; - } - else - file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= (char)DDL_IGNORE_LOG_ENTRY_CODE; + /* + We haven't synched the log entries yet, we synch them now before + writing the execute entry. If complete is true we haven't written + any log entries before, we are only here to write the execute + entry to indicate it is done. + */ + (void) ddl_log_sync_no_lock(); + file_entry_buf[DDL_LOG_ENTRY_TYPE_POS]= (uchar)DDL_LOG_EXECUTE_CODE; file_entry_buf[DDL_LOG_ACTION_TYPE_POS]= 0; /* Ignored for execute entries */ file_entry_buf[DDL_LOG_PHASE_POS]= 0; - int4store(&file_entry_buf[DDL_LOG_NEXT_ENTRY_POS], first_entry); - file_entry_buf[DDL_LOG_NAME_POS]= 0; - file_entry_buf[DDL_LOG_NAME_POS + FN_REFLEN]= 0; - file_entry_buf[DDL_LOG_NAME_POS + 2*FN_REFLEN]= 0; + int4store(file_entry_buf + DDL_LOG_NEXT_ENTRY_POS, first_entry); + int8store(file_entry_buf + DDL_LOG_XID_POS, 0); + bzero(file_entry_buf + global_ddl_log.name_pos, global_ddl_log.io_size - + global_ddl_log.name_pos); + if (!(*active_entry)) { - if (get_free_ddl_log_entry(active_entry, &write_header)) - { + if (ddl_log_get_free_entry(active_entry)) DBUG_RETURN(TRUE); - } - write_header= TRUE; + got_free_entry= TRUE; } if (write_ddl_log_file_entry((*active_entry)->entry_pos)) { - sql_print_error("Error writing execute entry in ddl log"); - ddl_log_release_memory_entry(*active_entry); - DBUG_RETURN(TRUE); - } - (void) ddl_log_sync_no_lock(); - if (write_header) - { - if (write_ddl_log_header()) + if (got_free_entry) { ddl_log_release_memory_entry(*active_entry); - DBUG_RETURN(TRUE); + *active_entry= 0; } + sql_print_error("Error writing execute entry in ddl log"); + DBUG_RETURN(TRUE); } + (void) ddl_log_sync_no_lock(); DBUG_RETURN(FALSE); } - /** - Deactivate an individual entry. + Increment phase for enty. Will deactivate entry after all phases are done @details see ddl_log_increment_phase_no_lock. - @param entry_no Entry position of record to change + @param entry_pos Entry position of record to change @return Operation status @retval TRUE Error @retval FALSE Success */ -bool ddl_log_increment_phase(uint entry_no) +bool ddl_log_increment_phase(uint entry_pos) { bool error; DBUG_ENTER("ddl_log_increment_phase"); mysql_mutex_lock(&LOCK_gdl); - error= ddl_log_increment_phase_no_lock(entry_no); + error= ddl_log_increment_phase_no_lock(entry_pos); mysql_mutex_unlock(&LOCK_gdl); DBUG_RETURN(error); } @@ -981,32 +1404,6 @@ bool ddl_log_sync() /** - Release a log memory entry. - @param log_memory_entry Log memory entry to release -*/ - -void ddl_log_release_memory_entry(DDL_LOG_MEMORY_ENTRY *log_entry) -{ - DDL_LOG_MEMORY_ENTRY *first_free= global_ddl_log.first_free; - DDL_LOG_MEMORY_ENTRY *next_log_entry= log_entry->next_log_entry; - DDL_LOG_MEMORY_ENTRY *prev_log_entry= log_entry->prev_log_entry; - DBUG_ENTER("ddl_log_release_memory_entry"); - - mysql_mutex_assert_owner(&LOCK_gdl); - global_ddl_log.first_free= log_entry; - log_entry->next_log_entry= first_free; - - if (prev_log_entry) - prev_log_entry->next_log_entry= next_log_entry; - else - global_ddl_log.first_used= next_log_entry; - if (next_log_entry) - next_log_entry->prev_log_entry= prev_log_entry; - DBUG_VOID_RETURN; -} - - -/** Execute one entry in the ddl log. Executing an entry means executing a linked list of actions. @@ -1042,51 +1439,94 @@ static void close_ddl_log() (void) mysql_file_close(global_ddl_log.file_id, MYF(MY_WME)); global_ddl_log.file_id= (File) -1; } + global_ddl_log.open= 0; DBUG_VOID_RETURN; } /** + Loop over ddl log excute entries and mark those that are already stored + in the binary log as completed + + @return + @retval 0 ok + @return 1 fail (write error) + +*/ + +bool ddl_log_close_binlogged_events(HASH *xids) +{ + uint i; + DDL_LOG_ENTRY ddl_log_entry; + DBUG_ENTER("ddl_log_close_binlogged_events"); + + if (global_ddl_log.num_entries == 0 || xids->records == 0) + DBUG_RETURN(0); + + mysql_mutex_lock(&LOCK_gdl); + for (i= 1; i <= global_ddl_log.num_entries; i++) + { + if (read_ddl_log_entry(i, &ddl_log_entry)) + break; // Read error. Ignore + if (ddl_log_entry.entry_type == DDL_LOG_EXECUTE_CODE && + ddl_log_entry.xid != 0 && + my_hash_search(xids, (uchar*) &ddl_log_entry.xid, + sizeof(ddl_log_entry.xid))) + { + if (disable_execute_entry(i)) + { + mysql_mutex_unlock(&LOCK_gdl); + DBUG_RETURN(1); // Write error. Fatal! + } + } + } + (void) ddl_log_sync_no_lock(); + mysql_mutex_unlock(&LOCK_gdl); + DBUG_RETURN(0); +} + + +/** Execute the ddl log at recovery of MySQL Server. + + @return + @retval 0 Ok. + @retval > 0 Fatal error. We have to abort (can't create ddl log) + @return < -1 Recovery failed, but new log exists and is usable + */ -void ddl_log_execute_recovery() +int ddl_log_execute_recovery() { - uint num_entries, i; + uint i; + int error= 0; THD *thd; DDL_LOG_ENTRY ddl_log_entry; - char file_name[FN_REFLEN]; static char recover_query_string[]= "INTERNAL DDL LOG RECOVER IN PROGRESS"; DBUG_ENTER("ddl_log_execute_recovery"); - /* - Initialise global_ddl_log struct - */ - bzero(global_ddl_log.file_entry_buf, sizeof(global_ddl_log.file_entry_buf)); - global_ddl_log.inited= FALSE; - global_ddl_log.recovery_phase= TRUE; - global_ddl_log.io_size= IO_SIZE; - global_ddl_log.file_id= (File) -1; + if (global_ddl_log.num_entries == 0) + DBUG_RETURN(0); /* To be able to run this from boot, we allocate a temporary THD */ if (!(thd=new THD(0))) - DBUG_VOID_RETURN; + { + DBUG_ASSERT(0); // Fatal error + DBUG_RETURN(1); + } thd->thread_stack= (char*) &thd; thd->store_globals(); thd->set_query(recover_query_string, strlen(recover_query_string)); - /* this also initialize LOCK_gdl */ - num_entries= read_ddl_log_header(); mysql_mutex_lock(&LOCK_gdl); - for (i= 1; i < num_entries + 1; i++) + for (i= 1; i <= global_ddl_log.num_entries; i++) { if (read_ddl_log_entry(i, &ddl_log_entry)) { - sql_print_error("Failed to read entry no = %u from ddl log", - i); + error= -1; continue; } if (ddl_log_entry.entry_type == DDL_LOG_EXECUTE_CODE) @@ -1094,35 +1534,42 @@ void ddl_log_execute_recovery() if (ddl_log_execute_entry_no_lock(thd, ddl_log_entry.next_entry)) { /* Real unpleasant scenario but we continue anyways. */ + error= -1; continue; } } } close_ddl_log(); - create_ddl_log_file_name(file_name); - (void) mysql_file_delete(key_file_global_ddl_log, file_name, MYF(0)); - global_ddl_log.recovery_phase= FALSE; mysql_mutex_unlock(&LOCK_gdl); thd->reset_query(); delete thd; - DBUG_VOID_RETURN; + + /* + Create a new ddl_log to get rid of old stuff and ensure that header matches + the current source version + */ + if (create_ddl_log()) + DBUG_RETURN(1); + DBUG_RETURN(error); } /** - Release all memory allocated to the ddl log. + Release all memory allocated to the ddl log and delete the ddl log */ void ddl_log_release() { + char file_name[FN_REFLEN]; DDL_LOG_MEMORY_ENTRY *free_list; DDL_LOG_MEMORY_ENTRY *used_list; DBUG_ENTER("ddl_log_release"); - if (!global_ddl_log.do_release) + if (!global_ddl_log.initialized) DBUG_VOID_RETURN; - mysql_mutex_lock(&LOCK_gdl); + global_ddl_log.initialized= 0; + free_list= global_ddl_log.first_free; used_list= global_ddl_log.first_used; while (used_list) @@ -1137,20 +1584,215 @@ void ddl_log_release() my_free(free_list); free_list= tmp; } + my_free(global_ddl_log.file_entry_buf); + global_ddl_log.file_entry_buf= 0; close_ddl_log(); - global_ddl_log.inited= 0; - mysql_mutex_unlock(&LOCK_gdl); + + create_ddl_log_file_name(file_name); + (void) mysql_file_delete(key_file_global_ddl_log, file_name, MYF(0)); mysql_mutex_destroy(&LOCK_gdl); - global_ddl_log.do_release= false; + DBUG_VOID_RETURN; +} + + +/** + Methods for DDL_LOG_STATE +*/ + +static void add_log_entry(DDL_LOG_STATE *state, + DDL_LOG_MEMORY_ENTRY *log_entry) +{ + log_entry->next_active_log_entry= state->list; + state->list= log_entry; +} + + +void ddl_log_release_entries(DDL_LOG_STATE *ddl_log_state) +{ + DDL_LOG_MEMORY_ENTRY *next; + for (DDL_LOG_MEMORY_ENTRY *log_entry= ddl_log_state->list; + log_entry; + log_entry= next) + { + next= log_entry->next_active_log_entry; + ddl_log_release_memory_entry(log_entry); + } + + if (ddl_log_state->execute_entry) + ddl_log_release_memory_entry(ddl_log_state->execute_entry); +} + + +/**************************************************************************** + Implementations of common ddl entries +*****************************************************************************/ + +/** + Complete ddl logging. This is done when all statements has completed + successfully and we can disable the execute log entry. +*/ + +void ddl_log_complete(DDL_LOG_STATE *state) +{ + DBUG_ENTER("ddl_log_complete"); + + if (unlikely(!state->list)) + DBUG_VOID_RETURN; // ddl log not used + + mysql_mutex_lock(&LOCK_gdl); + if (likely(state->execute_entry)) + ddl_log_disable_execute_entry(&state->execute_entry); + ddl_log_release_entries(state); + mysql_mutex_unlock(&LOCK_gdl); + DBUG_VOID_RETURN; +}; + + +/** + Revert all entries in the ddl log +*/ + +void ddl_log_revert(THD *thd, DDL_LOG_STATE *state) +{ + DBUG_ENTER("ddl_log_revert"); + + if (unlikely(!state->list)) + DBUG_VOID_RETURN; // ddl log not used + + mysql_mutex_lock(&LOCK_gdl); + if (likely(state->execute_entry)) + { + ddl_log_execute_entry_no_lock(thd, state->list->entry_pos); + ddl_log_disable_execute_entry(&state->execute_entry); + } + ddl_log_release_entries(state); + mysql_mutex_unlock(&LOCK_gdl); DBUG_VOID_RETURN; } /* ---------------------------------------------------------------------------- + Update phase of last created ddl log entry +*/ + +bool ddl_log_update_phase(DDL_LOG_STATE *state, uchar phase) +{ + DBUG_ENTER("ddl_log_update_phase"); + DBUG_RETURN(update_phase(state->list->entry_pos, phase)); +} + + +/** + Disable last ddl entry +*/ + +bool ddl_log_disable_entry(DDL_LOG_STATE *state) +{ + DBUG_ENTER("ddl_log_disable_entry"); + /* The following may not be true in case of temporary tables */ + if (likely(state->list)) + DBUG_RETURN(update_phase(state->list->entry_pos, DDL_LOG_FINAL_PHASE)); + DBUG_RETURN(0); +} + + +/** + Update XID for execute event +*/ + +bool ddl_log_update_xid(DDL_LOG_STATE *state, ulonglong xid) +{ + DBUG_ENTER("ddl_log_update_xid"); + DBUG_PRINT("enter", ("xid: %llu", xid)); + /* The following may not be true in case of temporary tables */ + if (likely(state->execute_entry)) + DBUG_RETURN(update_xid(state->execute_entry->entry_pos, xid)); + DBUG_RETURN(0); +} - END MODULE DDL log - -------------------- ---------------------------------------------------------------------------- +/** + Logging of rename table +*/ + +bool ddl_log_rename_table(THD *thd, DDL_LOG_STATE *ddl_state, + handlerton *hton, + const LEX_CSTRING *org_db, + const LEX_CSTRING *org_alias, + const LEX_CSTRING *new_db, + const LEX_CSTRING *new_alias) +{ + DDL_LOG_ENTRY ddl_log_entry; + DDL_LOG_MEMORY_ENTRY *log_entry; + DBUG_ENTER("ddl_log_rename_file"); + + bzero(&ddl_log_entry, sizeof(ddl_log_entry)); + + mysql_mutex_lock(&LOCK_gdl); + + ddl_log_entry.action_type= DDL_LOG_RENAME_TABLE_ACTION; + ddl_log_entry.next_entry= ddl_state->list ? ddl_state->list->entry_pos : 0; + lex_string_set(&ddl_log_entry.handler_name, + ha_resolve_storage_engine_name(hton)); + ddl_log_entry.db= *const_cast<LEX_CSTRING*>(new_db); + ddl_log_entry.name= *const_cast<LEX_CSTRING*>(new_alias); + ddl_log_entry.from_db= *const_cast<LEX_CSTRING*>(org_db); + ddl_log_entry.from_name= *const_cast<LEX_CSTRING*>(org_alias); + ddl_log_entry.phase= DDL_RENAME_PHASE_TABLE; + + if (ddl_log_write_entry(&ddl_log_entry, &log_entry)) + goto error; + + if (ddl_log_write_execute_entry(log_entry->entry_pos, + &ddl_state->execute_entry)) + goto error; + + add_log_entry(ddl_state, log_entry); + mysql_mutex_unlock(&LOCK_gdl); + DBUG_RETURN(0); + +error: + mysql_mutex_unlock(&LOCK_gdl); + DBUG_RETURN(1); +} + +/* + Logging of rename view */ + +bool ddl_log_rename_view(THD *thd, DDL_LOG_STATE *ddl_state, + const LEX_CSTRING *org_db, + const LEX_CSTRING *org_alias, + const LEX_CSTRING *new_db, + const LEX_CSTRING *new_alias) +{ + DDL_LOG_ENTRY ddl_log_entry; + DDL_LOG_MEMORY_ENTRY *log_entry; + DBUG_ENTER("ddl_log_rename_file"); + + bzero(&ddl_log_entry, sizeof(ddl_log_entry)); + + mysql_mutex_lock(&LOCK_gdl); + + ddl_log_entry.action_type= DDL_LOG_RENAME_VIEW_ACTION; + ddl_log_entry.next_entry= ddl_state->list ? ddl_state->list->entry_pos : 0; + ddl_log_entry.db= *const_cast<LEX_CSTRING*>(new_db); + ddl_log_entry.name= *const_cast<LEX_CSTRING*>(new_alias); + ddl_log_entry.from_db= *const_cast<LEX_CSTRING*>(org_db); + ddl_log_entry.from_name= *const_cast<LEX_CSTRING*>(org_alias); + + if (ddl_log_write_entry(&ddl_log_entry, &log_entry)) + goto error; + + if (ddl_log_write_execute_entry(log_entry->entry_pos, + &ddl_state->execute_entry)) + goto error; + + add_log_entry(ddl_state, log_entry); + mysql_mutex_unlock(&LOCK_gdl); + DBUG_RETURN(0); + +error: + mysql_mutex_unlock(&LOCK_gdl); + DBUG_RETURN(1); +} diff --git a/sql/ddl_log.h b/sql/ddl_log.h index 0747699cd85..5132099395f 100644 --- a/sql/ddl_log.h +++ b/sql/ddl_log.h @@ -24,6 +24,9 @@ enum ddl_log_entry_code { /* + DDL_LOG_UNKOWN + Here mainly to detect blocks that are all zero + DDL_LOG_EXECUTE_CODE: This is a code that indicates that this is a log entry to be executed, from this entry a linked list of log entries @@ -34,48 +37,87 @@ enum ddl_log_entry_code DDL_IGNORE_LOG_ENTRY_CODE: An entry that is to be ignored */ - DDL_LOG_EXECUTE_CODE = 'e', - DDL_LOG_ENTRY_CODE = 'l', - DDL_IGNORE_LOG_ENTRY_CODE = 'i' + DDL_LOG_UNKNOWN= 0, + DDL_LOG_EXECUTE_CODE= 1, + DDL_LOG_ENTRY_CODE= 2, + DDL_IGNORE_LOG_ENTRY_CODE= 3, + DDL_LOG_ENTRY_CODE_LAST= 4 }; + +/* + When adding things below, also add an entry to ddl_log_entry_phases in + ddl_log.cc +*/ + enum ddl_log_action_code { /* The type of action that a DDL_LOG_ENTRY_CODE entry is to perform. - DDL_LOG_DELETE_ACTION: - Delete an entity - DDL_LOG_RENAME_ACTION: - Rename an entity - DDL_LOG_REPLACE_ACTION: - Rename an entity after removing the previous entry with the - new name, that is replace this entry. - DDL_LOG_EXCHANGE_ACTION: - Exchange two entities by renaming them a -> tmp, b -> a, tmp -> b. */ - DDL_LOG_DELETE_ACTION = 'd', - DDL_LOG_RENAME_ACTION = 'r', - DDL_LOG_REPLACE_ACTION = 's', - DDL_LOG_EXCHANGE_ACTION = 'e' + + /* Delete a .frm file or a table in the partition engine */ + DDL_LOG_DELETE_ACTION= 0, + + /* Rename a .frm fire a table in the partition engine */ + DDL_LOG_RENAME_ACTION= 1, + + /* + Rename an entity after removing the previous entry with the + new name, that is replace this entry. + */ + DDL_LOG_REPLACE_ACTION= 2, + + /* Exchange two entities by renaming them a -> tmp, b -> a, tmp -> b */ + DDL_LOG_EXCHANGE_ACTION= 3, + /* + log do_rename(): Rename of .frm file, table, stat_tables and triggers + */ + DDL_LOG_RENAME_TABLE_ACTION= 4, + DDL_LOG_RENAME_VIEW_ACTION= 5, + DDL_LOG_LAST_ACTION /* End marker */ }; + +/* Number of phases for each ddl_log_action_code */ +extern const uchar ddl_log_entry_phases[DDL_LOG_LAST_ACTION]; + + enum enum_ddl_log_exchange_phase { EXCH_PHASE_NAME_TO_TEMP= 0, EXCH_PHASE_FROM_TO_NAME= 1, EXCH_PHASE_TEMP_TO_FROM= 2 }; +enum enum_ddl_log_rename_table_phase { + DDL_RENAME_PHASE_TRIGGER= 0, + DDL_RENAME_PHASE_STAT, + DDL_RENAME_PHASE_TABLE, +}; + +/* + Setting ddl_log_entry.phase to this has the same effect as setting + action_type to DDL_IGNORE_LOG_ENTRY_CODE +*/ + +#define DDL_LOG_FINAL_PHASE ((uchar) 0xff) typedef struct st_ddl_log_entry { - const char *name; - const char *from_name; - const char *handler_name; - const char *tmp_name; + LEX_CSTRING name; + LEX_CSTRING from_name; + LEX_CSTRING handler_name; + LEX_CSTRING tmp_name; + LEX_CSTRING db; + LEX_CSTRING from_db; + + ulonglong xid; // Xid stored in the binary log + ulonglong unique_id; // Unique id for file version uint next_entry; - uint entry_pos; - enum ddl_log_entry_code entry_type; + uint entry_pos; // Set by write_dll_log_entry() + // bool temporary; // If drop of temporary table + enum ddl_log_entry_code entry_type; // Set automatically enum ddl_log_action_code action_type; /* Most actions have only one phase. REPLACE does however have two @@ -83,7 +125,7 @@ typedef struct st_ddl_log_entry there was one there before and the second phase renames the old name to the new name. */ - char phase; + uchar phase; // set automatically } DDL_LOG_ENTRY; typedef struct st_ddl_log_memory_entry @@ -95,17 +137,52 @@ typedef struct st_ddl_log_memory_entry } DDL_LOG_MEMORY_ENTRY; +typedef struct st_ddl_log_state +{ + /* List of ddl log entries */ + DDL_LOG_MEMORY_ENTRY *list; + /* One execute entry per list */ + DDL_LOG_MEMORY_ENTRY *execute_entry; +} DDL_LOG_STATE; + + +/* These functions are for recovery */ +bool ddl_log_initialize(); +void ddl_log_release(); +bool ddl_log_close_binlogged_events(HASH *xids); +int ddl_log_execute_recovery(); + +/* functions for updating the ddl log */ bool ddl_log_write_entry(DDL_LOG_ENTRY *ddl_log_entry, DDL_LOG_MEMORY_ENTRY **active_entry); + bool ddl_log_write_execute_entry(uint first_entry, - bool complete, - DDL_LOG_MEMORY_ENTRY **active_entry); -bool ddl_log_increment_phase(uint entry_no); + DDL_LOG_MEMORY_ENTRY **active_entry); +bool ddl_log_disable_execute_entry(DDL_LOG_MEMORY_ENTRY **active_entry); + +void ddl_log_complete(DDL_LOG_STATE *ddl_log_state); +void ddl_log_revert(THD *thd, DDL_LOG_STATE *ddl_log_state); + +bool ddl_log_update_phase(DDL_LOG_STATE *entry, uchar phase); +bool ddl_log_update_xid(DDL_LOG_STATE *state, ulonglong xid); +bool ddl_log_disable_entry(DDL_LOG_STATE *state); +bool ddl_log_increment_phase(uint entry_pos); void ddl_log_release_memory_entry(DDL_LOG_MEMORY_ENTRY *log_entry); bool ddl_log_sync(); -void ddl_log_release(); -void ddl_log_execute_recovery(); bool ddl_log_execute_entry(THD *thd, uint first_entry); +void ddl_log_release_entries(DDL_LOG_STATE *ddl_log_state); +bool ddl_log_rename_table(THD *thd, DDL_LOG_STATE *ddl_state, + handlerton *hton, + const LEX_CSTRING *org_db, + const LEX_CSTRING *org_alias, + const LEX_CSTRING *new_db, + const LEX_CSTRING *new_alias); +bool ddl_log_rename_view(THD *thd, DDL_LOG_STATE *ddl_state, + const LEX_CSTRING *org_db, + const LEX_CSTRING *org_alias, + const LEX_CSTRING *new_db, + const LEX_CSTRING *new_alias); + extern mysql_mutex_t LOCK_gdl; #endif /* DDL_LOG_INCLUDED */ diff --git a/sql/debug_sync.cc b/sql/debug_sync.cc index dde3ce5a35b..f8afa6719dd 100644 --- a/sql/debug_sync.cc +++ b/sql/debug_sync.cc @@ -1629,3 +1629,68 @@ bool debug_sync_set_action(THD *thd, const char *action_str, size_t len) /* prevent linker/lib warning about file without public symbols */ int debug_sync_dummy; #endif /* defined(ENABLED_DEBUG_SYNC) */ + + +/** + Debug utility to do crash after a set number of executions + + The user variable, either @debug_crash_counter or @debug_error_counter, + is decremented each time debug_crash() or debug_simulate_error is called + if the keyword is set with @@debug_push, like + @@debug_push="d+frm_data_type_info_emulate" + + If the variable is not set or is not an integer it will be ignored. +*/ + +#ifndef DBUG_OFF + +static const LEX_CSTRING debug_crash_counter= +{ STRING_WITH_LEN("debug_crash_counter") }; +static const LEX_CSTRING debug_error_counter= +{ STRING_WITH_LEN("debug_error_counter") }; + +static bool debug_decrement_counter(const LEX_CSTRING *name) +{ + THD *thd= current_thd; + user_var_entry *entry= (user_var_entry*) + my_hash_search(&thd->user_vars, (uchar*) name->str, name->length); + if (!entry || entry->type != INT_RESULT || ! entry->value) + return 0; + (*(ulonglong*) entry->value)= (*(ulonglong*) entry->value)-1; + return !*(ulonglong*) entry->value; +} + +void debug_crash_here(const char *keyword) +{ + DBUG_EXECUTE_IF(keyword, + if (debug_decrement_counter(&debug_crash_counter)) + { + my_printf_error(ER_INTERNAL_ERROR, + "Crashing at %s", + MYF(ME_ERROR_LOG | ME_NOTE), keyword); + DBUG_SUICIDE(); + }); +} + +/* + This can be used as debug_counter to simulate an error at a specific + position. + + Typical usage would be + if (debug_simualte_error("keyword")) + error= 1; +*/ + +bool debug_simulate_error(const char *keyword, uint error) +{ + DBUG_EXECUTE_IF(keyword, + if (debug_decrement_counter(&debug_error_counter)) + { + my_printf_error(error, + "Simulating error for '%s'", + MYF(ME_ERROR_LOG), keyword); + return 1; + }); + return 0; +} +#endif /* DBUG_OFF */ diff --git a/sql/debug_sync.h b/sql/debug_sync.h index 3b8aa8815e1..4e3e10fcc51 100644 --- a/sql/debug_sync.h +++ b/sql/debug_sync.h @@ -53,4 +53,12 @@ static inline bool debug_sync_set_action(THD *, const char *, size_t) { return false; } #endif /* defined(ENABLED_DEBUG_SYNC) */ +#ifndef DBUG_OFF +void debug_crash_here(const char *keyword); +bool debug_simulate_error(const char *keyword, uint error); +#else +#define debug_crash_here(A) do { } while(0) +#define debug_simulate_error(A, B) 0 +#endif + #endif /* DEBUG_SYNC_INCLUDED */ diff --git a/sql/handler.cc b/sql/handler.cc index ce71504a5ce..02194cbdc99 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -2725,7 +2725,7 @@ const char *get_canonical_filename(handler *file, const char *path, char *tmp_path) { uint i; - if (lower_case_table_names != 2 || (file->ha_table_flags() & HA_FILE_BASED)) + if (!file->needs_lower_case_filenames()) return path; for (i= 0; i <= mysql_tmpdir_list.max; i++) diff --git a/sql/handler.h b/sql/handler.h index 939c31ce84a..76a8ad82dd1 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -5030,6 +5030,17 @@ public: const KEY_PART_INFO &old_part, const KEY_PART_INFO &new_part) const; + +/* + If lower_case_table_names == 2 (case-preserving but case-insensitive + file system) and the storage is not HA_FILE_BASED, we need to provide + a lowercase file name for the engine. +*/ + inline bool needs_lower_case_filenames() + { + return (lower_case_table_names == 2 && !(ha_table_flags() & HA_FILE_BASED)); + } + protected: Handler_share *get_ha_share_ptr(); void set_ha_share_ptr(Handler_share *arg_ha_share); diff --git a/sql/log.cc b/sql/log.cc index 5eb1bbb9bd8..a5c94685df6 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -39,6 +39,7 @@ #include "rpl_rli.h" #include "sql_audit.h" #include "mysqld.h" +#include "ddl_log.h" #include <my_dir.h> #include <m_ctype.h> // For test_if_number @@ -10485,6 +10486,20 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name, } break; } + case QUERY_EVENT: + { + Query_log_event *query_ev= (Query_log_event*) ev; + if (do_xa && query_ev->xid) + { + DBUG_ASSERT(sizeof(query_ev->xid) == sizeof(my_xid)); + uchar *x= (uchar *) memdup_root(&mem_root, + (uchar*) &query_ev->xid, + sizeof(query_ev->xid)); + if (!x || my_hash_insert(&xids, x)) + goto err2; + } + break; + } case BINLOG_CHECKPOINT_EVENT: if (first_round && do_xa) { @@ -10625,6 +10640,8 @@ int TC_LOG_BINLOG::recover(LOG_INFO *linfo, const char *last_log_name, { if (ha_recover(&xids)) goto err2; + if (ddl_log_close_binlogged_events(&xids)) + goto err2; free_root(&mem_root, MYF(0)); my_hash_free(&xids); diff --git a/sql/log_event.cc b/sql/log_event.cc index 7d1a52609e2..0e4cf817f0a 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -1392,6 +1392,7 @@ code_name(int code) case Q_TABLE_MAP_FOR_UPDATE_CODE: return "Q_TABLE_MAP_FOR_UPDATE_CODE"; case Q_MASTER_DATA_WRITTEN_CODE: return "Q_MASTER_DATA_WRITTEN_CODE"; case Q_HRNOW: return "Q_HRNOW"; + case Q_XID: return "XID"; } sprintf(buf, "CODE#%d", code); return buf; @@ -1440,7 +1441,7 @@ Query_log_event::Query_log_event(const char* buf, uint event_len, flags2_inited(0), sql_mode_inited(0), charset_inited(0), flags2(0), auto_increment_increment(1), auto_increment_offset(1), time_zone_len(0), lc_time_names_number(0), charset_database_number(0), - table_map_for_update(0), master_data_written(0) + table_map_for_update(0), xid(0), master_data_written(0) { ulong data_len; uint32 tmp; @@ -1624,6 +1625,13 @@ Query_log_event::Query_log_event(const char* buf, uint event_len, pos+= 3; break; } + case Q_XID: + { + CHECK_SPACE(pos, end, 8); + xid= uint8korr(pos); + pos+= 8; + break; + } default: /* That's why you must write status vars in growing order of code */ DBUG_PRINT("info",("Query_log_event has unknown status vars (first has\ diff --git a/sql/log_event.h b/sql/log_event.h index 4e193232f4b..23f794f69b7 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -319,6 +319,7 @@ class String; #define Q_INVOKER 11 #define Q_HRNOW 128 +#define Q_XID 129 /* Intvar event post-header */ @@ -2118,6 +2119,8 @@ public: statement, for other query statements, this will be zero. */ ulonglong table_map_for_update; + /* Xid for the event, if such exists */ + ulonglong xid; /* Holds the original length of a Query_log_event that comes from a master of version < 5.0 (i.e., binlog_version < 4). When the IO diff --git a/sql/log_event_client.cc b/sql/log_event_client.cc index 97f43cb388e..54b9d9184b1 100644 --- a/sql/log_event_client.cc +++ b/sql/log_event_client.cc @@ -1822,7 +1822,7 @@ bool Query_log_event::print_query_header(IO_CACHE* file, my_b_printf(file, "\t%s\tthread_id=%lu\texec_time=%lu\terror_code=%d\n", get_type_str(), (ulong) thread_id, (ulong) exec_time, - error_code)) + error_code, (ulong) xid)) goto err; } diff --git a/sql/log_event_server.cc b/sql/log_event_server.cc index 82f99d8ef21..6e33acc744d 100644 --- a/sql/log_event_server.cc +++ b/sql/log_event_server.cc @@ -1294,6 +1294,15 @@ bool Query_log_event::write() int3store(start, when_sec_part); start+= 3; } + + /* xid's is used with ddl_log handling */ + if (thd && thd->binlog_xid) + { + *start++= Q_XID; + int8store(start, thd->query_id); + start+= 8; + } + /* NOTE: When adding new status vars, please don't forget to update the MAX_SIZE_LOG_EVENT_STATUS in log_event.h and update the function diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 327c5d0bb02..a10e54743aa 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -575,7 +575,7 @@ char log_error_file[FN_REFLEN], glob_hostname[FN_REFLEN], *opt_log_basename; char mysql_real_data_home[FN_REFLEN], lc_messages_dir[FN_REFLEN], reg_ext[FN_EXTLEN], mysql_charsets_dir[FN_REFLEN], - *opt_init_file, *opt_tc_log_file; + *opt_init_file, *opt_tc_log_file, *opt_ddl_recovery_file; char *lc_messages_dir_ptr= lc_messages_dir, *log_error_file_ptr; char mysql_unpacked_real_data_home[FN_REFLEN]; size_t mysql_unpacked_real_data_home_len; @@ -5186,6 +5186,9 @@ static int init_server_components() } #endif + if (ddl_log_initialize()) + unireg_abort(1); + tc_log= get_tc_log_implementation(); if (tc_log->open(opt_bin_log ? opt_bin_logname : opt_tc_log_file)) @@ -5195,9 +5198,7 @@ static int init_server_components() } if (ha_recover(0)) - { unireg_abort(1); - } if (opt_bin_log) { @@ -5591,7 +5592,8 @@ int mysqld_main(int argc, char **argv) initialize_information_schema_acl(); - ddl_log_execute_recovery(); + if (ddl_log_execute_recovery() > 0) + unireg_abort(1); /* Change EVENTS_ORIGINAL to EVENTS_OFF (the default value) as there is no @@ -6371,6 +6373,10 @@ struct my_option my_long_options[]= "relay logs", &opt_relaylog_index_name, &opt_relaylog_index_name, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"log-ddl-recovery", 0, + "Path to file used for recovery of DDL statements after a crash", + &opt_ddl_recovery_file, &opt_ddl_recovery_file, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, {"log-isam", OPT_ISAM_LOG, "Log all MyISAM changes to file.", &myisam_log_filename, &myisam_log_filename, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, @@ -7493,6 +7499,7 @@ static int mysql_init_variables(void) opt_logname= opt_binlog_index_name= opt_slow_logname= 0; opt_log_basename= 0; opt_tc_log_file= (char *)"tc.log"; // no hostname in tc_log file name ! + opt_ddl_recovery_file= (char *) "ddl_recovery.log"; opt_secure_auth= 0; opt_bootstrap= opt_myisam_log= 0; disable_log_notes= 0; diff --git a/sql/mysqld.h b/sql/mysqld.h index 28be25a976e..26ecd9b9a54 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -207,7 +207,7 @@ extern MYSQL_PLUGIN_IMPORT char glob_hostname[FN_REFLEN]; extern char mysql_home[FN_REFLEN]; extern char pidfile_name[FN_REFLEN], system_time_zone[30], *opt_init_file; extern char default_logfile_name[FN_REFLEN]; -extern char log_error_file[FN_REFLEN], *opt_tc_log_file; +extern char log_error_file[FN_REFLEN], *opt_tc_log_file, *opt_ddl_recovery_file; extern const double log_10[309]; extern ulonglong keybuff_size; extern ulonglong thd_startup_options; diff --git a/sql/parse_file.cc b/sql/parse_file.cc index 59b4027a352..aaf36970702 100644 --- a/sql/parse_file.cc +++ b/sql/parse_file.cc @@ -24,7 +24,9 @@ #include "sql_priv.h" #include "parse_file.h" #include "unireg.h" // CREATE_MODE -#include "sql_table.h" // build_table_filename +#include "sql_table.h" // build_table_filename +#include "debug_sync.h" +#include <mysys_err.h> // EE_WRITE #include <m_ctype.h> #include <my_dir.h> @@ -245,7 +247,6 @@ write_parameter(IO_CACHE *file, const uchar* base, File_option *parameter) TRUE error */ - my_bool sql_create_definition_file(const LEX_CSTRING *dir, const LEX_CSTRING *file_name, @@ -287,6 +288,8 @@ sql_create_definition_file(const LEX_CSTRING *dir, DBUG_RETURN(TRUE); } + debug_crash_here("definition_file_after_create"); + if (init_io_cache(&file, handler, 0, WRITE_CACHE, 0L, 0, MYF(MY_WME))) goto err_w_file; @@ -296,6 +299,9 @@ sql_create_definition_file(const LEX_CSTRING *dir, my_b_write(&file, (const uchar *)STRING_WITH_LEN("\n"))) goto err_w_cache; + if (debug_simulate_error("definition_file_simulate_write_error", EE_WRITE)) + goto err_w_cache; + // write parameters to temporary file for (param= parameters; param->name.str; param++) { @@ -337,6 +343,7 @@ err_w_cache: end_io_cache(&file); err_w_file: mysql_file_close(handler, MYF(MY_WME)); + mysql_file_delete(key_file_fileparser, path, MYF(MY_WME)); DBUG_RETURN(TRUE); } diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 29d8970318c..6951045a65e 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -797,6 +797,7 @@ THD::THD(my_thread_id id, bool is_wsrep_applier) mysys_var=0; binlog_evt_union.do_union= FALSE; binlog_table_maps= FALSE; + binlog_xid= 0; enable_slow_log= 0; durability_property= HA_REGULAR_DURABILITY; diff --git a/sql/sql_class.h b/sql/sql_class.h index 63af50ad234..b96e3e5a5fd 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -2638,6 +2638,7 @@ public: #ifndef MYSQL_CLIENT binlog_cache_mngr * binlog_setup_trx_data(); + ulonglong binlog_xid; /* binlog request storing of xid */ /* Public interface to write RBR events to the binlog diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index a691ba0c83b..672859405e0 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -6201,15 +6201,17 @@ static bool write_log_replace_delete_frm(ALTER_PARTITION_PARAM_TYPE *lpt, DDL_LOG_MEMORY_ENTRY *log_entry; DBUG_ENTER("write_log_replace_delete_frm"); + bzero(&ddl_log_entry, sizeof(ddl_log_entry)); if (replace_flag) ddl_log_entry.action_type= DDL_LOG_REPLACE_ACTION; else ddl_log_entry.action_type= DDL_LOG_DELETE_ACTION; ddl_log_entry.next_entry= next_entry; - ddl_log_entry.handler_name= reg_ext; - ddl_log_entry.name= to_path; + lex_string_set(&ddl_log_entry.handler_name, reg_ext); + lex_string_set(&ddl_log_entry.name, to_path); + if (replace_flag) - ddl_log_entry.from_name= from_path; + lex_string_set(&ddl_log_entry.from_name, from_path); if (ddl_log_write_entry(&ddl_log_entry, &log_entry)) { DBUG_RETURN(TRUE); @@ -6261,6 +6263,7 @@ static bool write_log_changed_partitions(ALTER_PARTITION_PARAM_TYPE *lpt, if (part_elem->part_state == PART_IS_CHANGED || (part_elem->part_state == PART_IS_ADDED && temp_partitions)) { + bzero(&ddl_log_entry, sizeof(ddl_log_entry)); if (part_info->is_sub_partitioned()) { List_iterator<partition_element> sub_it(part_elem->subpartitions); @@ -6270,8 +6273,9 @@ static bool write_log_changed_partitions(ALTER_PARTITION_PARAM_TYPE *lpt, { partition_element *sub_elem= sub_it++; ddl_log_entry.next_entry= *next_entry; - ddl_log_entry.handler_name= - ha_resolve_storage_engine_name(sub_elem->engine_type); + lex_string_set(&ddl_log_entry.handler_name, + ha_resolve_storage_engine_name(sub_elem-> + engine_type)); if (create_subpartition_name(tmp_path, sizeof(tmp_path), path, part_elem->partition_name, sub_elem->partition_name, @@ -6281,16 +6285,15 @@ static bool write_log_changed_partitions(ALTER_PARTITION_PARAM_TYPE *lpt, sub_elem->partition_name, NORMAL_PART_NAME)) DBUG_RETURN(TRUE); - ddl_log_entry.name= normal_path; - ddl_log_entry.from_name= tmp_path; + lex_string_set(&ddl_log_entry.name, normal_path); + lex_string_set(&ddl_log_entry.from_name, tmp_path); if (part_elem->part_state == PART_IS_CHANGED) ddl_log_entry.action_type= DDL_LOG_REPLACE_ACTION; else ddl_log_entry.action_type= DDL_LOG_RENAME_ACTION; if (ddl_log_write_entry(&ddl_log_entry, &log_entry)) - { DBUG_RETURN(TRUE); - } + *next_entry= log_entry->entry_pos; sub_elem->log_entry= log_entry; insert_part_info_log_entry_list(part_info, log_entry); @@ -6299,8 +6302,8 @@ static bool write_log_changed_partitions(ALTER_PARTITION_PARAM_TYPE *lpt, else { ddl_log_entry.next_entry= *next_entry; - ddl_log_entry.handler_name= - ha_resolve_storage_engine_name(part_elem->engine_type); + lex_string_set(&ddl_log_entry.handler_name, + ha_resolve_storage_engine_name(part_elem->engine_type)); if (create_partition_name(tmp_path, sizeof(tmp_path), path, part_elem->partition_name, TEMP_PART_NAME, TRUE) || @@ -6308,8 +6311,8 @@ static bool write_log_changed_partitions(ALTER_PARTITION_PARAM_TYPE *lpt, part_elem->partition_name, NORMAL_PART_NAME, TRUE)) DBUG_RETURN(TRUE); - ddl_log_entry.name= normal_path; - ddl_log_entry.from_name= tmp_path; + lex_string_set(&ddl_log_entry.name, normal_path); + lex_string_set(&ddl_log_entry.from_name, tmp_path); if (part_elem->part_state == PART_IS_CHANGED) ddl_log_entry.action_type= DDL_LOG_REPLACE_ACTION; else @@ -6353,6 +6356,7 @@ static bool write_log_dropped_partitions(ALTER_PARTITION_PARAM_TYPE *lpt, uint num_elements= part_info->partitions.elements; DBUG_ENTER("write_log_dropped_partitions"); + bzero(&ddl_log_entry, sizeof(ddl_log_entry)); ddl_log_entry.action_type= DDL_LOG_DELETE_ACTION; if (temp_list) num_elements= num_temp_partitions; @@ -6383,13 +6387,14 @@ static bool write_log_dropped_partitions(ALTER_PARTITION_PARAM_TYPE *lpt, { partition_element *sub_elem= sub_it++; ddl_log_entry.next_entry= *next_entry; - ddl_log_entry.handler_name= - ha_resolve_storage_engine_name(sub_elem->engine_type); + lex_string_set(&ddl_log_entry.handler_name, + ha_resolve_storage_engine_name(sub_elem-> + engine_type)); if (create_subpartition_name(tmp_path, sizeof(tmp_path), path, part_elem->partition_name, sub_elem->partition_name, name_variant)) DBUG_RETURN(TRUE); - ddl_log_entry.name= tmp_path; + lex_string_set(&ddl_log_entry.name, tmp_path); if (ddl_log_write_entry(&ddl_log_entry, &log_entry)) { DBUG_RETURN(TRUE); @@ -6402,13 +6407,13 @@ static bool write_log_dropped_partitions(ALTER_PARTITION_PARAM_TYPE *lpt, else { ddl_log_entry.next_entry= *next_entry; - ddl_log_entry.handler_name= - ha_resolve_storage_engine_name(part_elem->engine_type); + lex_string_set(&ddl_log_entry.handler_name, + ha_resolve_storage_engine_name(part_elem->engine_type)); if (create_partition_name(tmp_path, sizeof(tmp_path), path, part_elem->partition_name, name_variant, TRUE)) DBUG_RETURN(TRUE); - ddl_log_entry.name= tmp_path; + lex_string_set(&ddl_log_entry.name, tmp_path); if (ddl_log_write_entry(&ddl_log_entry, &log_entry)) { DBUG_RETURN(TRUE); @@ -6472,7 +6477,7 @@ static bool write_log_drop_shadow_frm(ALTER_PARTITION_PARAM_TYPE *lpt) goto error; log_entry= part_info->first_log_entry; if (ddl_log_write_execute_entry(log_entry->entry_pos, - FALSE, &exec_log_entry)) + &exec_log_entry)) goto error; mysql_mutex_unlock(&LOCK_gdl); set_part_info_exec_log_entry(part_info, exec_log_entry); @@ -6519,7 +6524,7 @@ static bool write_log_rename_frm(ALTER_PARTITION_PARAM_TYPE *lpt) log_entry= part_info->first_log_entry; part_info->frm_log_entry= log_entry; if (ddl_log_write_execute_entry(log_entry->entry_pos, - FALSE, &exec_log_entry)) + &exec_log_entry)) goto error; release_part_info_log_entries(old_first_log_entry); mysql_mutex_unlock(&LOCK_gdl); @@ -6574,7 +6579,7 @@ static bool write_log_drop_partition(ALTER_PARTITION_PARAM_TYPE *lpt) log_entry= part_info->first_log_entry; part_info->frm_log_entry= log_entry; if (ddl_log_write_execute_entry(log_entry->entry_pos, - FALSE, &exec_log_entry)) + &exec_log_entry)) goto error; release_part_info_log_entries(old_first_log_entry); mysql_mutex_unlock(&LOCK_gdl); @@ -6633,7 +6638,6 @@ static bool write_log_add_change_partition(ALTER_PARTITION_PARAM_TYPE *lpt) log_entry= part_info->first_log_entry; if (ddl_log_write_execute_entry(log_entry->entry_pos, - FALSE, /* Reuse the old execute ddl_log_entry */ &exec_log_entry)) goto error; @@ -6703,7 +6707,7 @@ static bool write_log_final_change_partition(ALTER_PARTITION_PARAM_TYPE *lpt) part_info->frm_log_entry= log_entry; /* Overwrite the revert execute log entry with this retry execute entry */ if (ddl_log_write_execute_entry(log_entry->entry_pos, - FALSE, &exec_log_entry)) + &exec_log_entry)) goto error; release_part_info_log_entries(old_first_log_entry); mysql_mutex_unlock(&LOCK_gdl); @@ -6739,7 +6743,7 @@ static void write_log_completed(ALTER_PARTITION_PARAM_TYPE *lpt, DBUG_ASSERT(log_entry); mysql_mutex_lock(&LOCK_gdl); - if (ddl_log_write_execute_entry(0UL, TRUE, &log_entry)) + if (ddl_log_disable_execute_entry(&log_entry)) { /* Failed to write, Bad... diff --git a/sql/sql_partition_admin.cc b/sql/sql_partition_admin.cc index 18c53a7e2fb..ca564af1bf6 100644 --- a/sql/sql_partition_admin.cc +++ b/sql/sql_partition_admin.cc @@ -355,13 +355,14 @@ static bool exchange_name_with_ddl_log(THD *thd, DBUG_RETURN(TRUE); /* prepare the action entry */ + bzero(&exchange_entry, sizeof(exchange_entry)); exchange_entry.entry_type= DDL_LOG_ENTRY_CODE; exchange_entry.action_type= DDL_LOG_EXCHANGE_ACTION; - exchange_entry.next_entry= 0; - exchange_entry.name= name; - exchange_entry.from_name= from_name; - exchange_entry.tmp_name= tmp_name; - exchange_entry.handler_name= ha_resolve_storage_engine_name(ht); + lex_string_set(&exchange_entry.name, name); + lex_string_set(&exchange_entry.from_name, from_name); + lex_string_set(&exchange_entry.tmp_name, tmp_name); + lex_string_set(&exchange_entry.handler_name, + ha_resolve_storage_engine_name(ht)); exchange_entry.phase= EXCH_PHASE_NAME_TO_TEMP; mysql_mutex_lock(&LOCK_gdl); @@ -377,8 +378,8 @@ static bool exchange_name_with_ddl_log(THD *thd, DBUG_EXECUTE_IF("exchange_partition_fail_2", goto err_no_execute_written;); DBUG_EXECUTE_IF("exchange_partition_abort_2", DBUG_SUICIDE();); - if (unlikely(ddl_log_write_execute_entry(log_entry->entry_pos, FALSE, - &exec_log_entry))) + if (unlikely(ddl_log_write_execute_entry(log_entry->entry_pos, + &exec_log_entry))) goto err_no_execute_written; /* ddl_log is written and synced */ @@ -457,7 +458,7 @@ err_rename: (void) ddl_log_execute_entry(current_thd, log_entry->entry_pos); mysql_mutex_lock(&LOCK_gdl); /* mark the execute log entry done */ - (void) ddl_log_write_execute_entry(0, TRUE, &exec_log_entry); + (void) ddl_log_disable_execute_entry(&exec_log_entry); /* release the execute log entry */ (void) ddl_log_release_memory_entry(exec_log_entry); err_no_execute_written: diff --git a/sql/sql_rename.cc b/sql/sql_rename.cc index 77a1e46a75a..316cd2c9ca7 100644 --- a/sql/sql_rename.cc +++ b/sql/sql_rename.cc @@ -30,17 +30,20 @@ #include "sql_base.h" // tdc_remove_table, lock_table_names, #include "sql_handler.h" // mysql_ha_rm_tables #include "sql_statistics.h" +#include "ddl_log.h" +#include "debug_sync.h" + +/* used to hold table entries for as part of list of renamed temporary tables */ +struct TABLE_PAIR +{ + TABLE_LIST *from, *to; +}; -static TABLE_LIST *rename_tables(THD *thd, TABLE_LIST *table_list, - bool skip_error, bool if_exits, - bool *force_if_exists); -static bool do_rename(THD *thd, TABLE_LIST *ren_table, - const LEX_CSTRING *new_db, - const LEX_CSTRING *new_table_name, - const LEX_CSTRING *new_table_alias, - bool skip_error, bool if_exists, bool *force_if_exists); -static TABLE_LIST *reverse_table_list(TABLE_LIST *table_list); +static bool rename_tables(THD *thd, TABLE_LIST *table_list, + DDL_LOG_STATE *ddl_log_state, + bool skip_error, bool if_exits, + bool *force_if_exists); /* Every two entries in the table_list form a pair of original name and @@ -55,6 +58,7 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent, TABLE_LIST *ren_table= 0; int to_table; const char *rename_log_table[2]= {NULL, NULL}; + DDL_LOG_STATE ddl_log_state; DBUG_ENTER("mysql_rename_tables"); /* @@ -151,32 +155,14 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent, goto err; error=0; + bzero(&ddl_log_state, sizeof(ddl_log_state)); + /* An exclusive lock on table names is satisfactory to ensure no other thread accesses this table. */ - if ((ren_table= rename_tables(thd, table_list, 0, if_exists, - &force_if_exists))) - { - /* Rename didn't succeed; rename back the tables in reverse order */ - TABLE_LIST *table; - - /* Reverse the table list */ - table_list= reverse_table_list(table_list); - - /* Find the last renamed table */ - for (table= table_list; - table->next_local != ren_table ; - table= table->next_local->next_local) ; - table= table->next_local->next_local; // Skip error table - /* Revert to old names */ - rename_tables(thd, table, 1, if_exists, &force_if_exists); - - /* Revert the table list (for prepared statements) */ - table_list= reverse_table_list(table_list); - - error= 1; - } + error= rename_tables(thd, table_list, &ddl_log_state, + 0, if_exists, &force_if_exists); if (likely(!silent && !error)) { @@ -186,46 +172,41 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent, /* Add IF EXISTS to binary log */ thd->variables.option_bits|= OPTION_IF_EXISTS; } + + debug_crash_here("ddl_log_rename_before_binlog"); + /* + Store xid in ddl log and binary log so that we can check on ddl recovery + if the item is in the binary log (and thus the operation was complete + */ + thd->binlog_xid= thd->query_id; + ddl_log_update_xid(&ddl_log_state, thd->binlog_xid); binlog_error= write_bin_log(thd, TRUE, thd->query(), thd->query_length()); + if (binlog_error) + error= 1; + thd->binlog_xid= 0; thd->variables.option_bits= save_option_bits; + debug_crash_here("ddl_log_rename_after_binlog"); if (likely(!binlog_error)) my_ok(thd); } if (likely(!error)) + { query_cache_invalidate3(thd, table_list, 0); + ddl_log_complete(&ddl_log_state); + } + else + { + /* Revert the renames of normal tables with the help of the ddl log */ + ddl_log_revert(thd, &ddl_log_state); + } err: DBUG_RETURN(error || binlog_error); } -/* - reverse table list - - SYNOPSIS - reverse_table_list() - table_list pointer to table _list - - RETURN - pointer to new (reversed) list -*/ -static TABLE_LIST *reverse_table_list(TABLE_LIST *table_list) -{ - TABLE_LIST *prev= 0; - - while (table_list) - { - TABLE_LIST *next= table_list->next_local; - table_list->next_local= prev; - prev= table_list; - table_list= next; - } - return (prev); -} - - static bool do_rename_temporary(THD *thd, TABLE_LIST *ren_table, TABLE_LIST *new_table, bool skip_error) @@ -242,12 +223,85 @@ do_rename_temporary(THD *thd, TABLE_LIST *ren_table, TABLE_LIST *new_table, DBUG_RETURN(1); // This can't be skipped } - DBUG_RETURN(thd->rename_temporary_table(ren_table->table, &new_table->db, new_alias)); } +/** + Parameters for do_rename +*/ + +struct rename_param +{ + LEX_CSTRING old_alias, new_alias; + handlerton *from_table_hton; +}; + + +/** + Check pre-conditions for rename + - From table should exists + - To table should not exists. + + @return + @retval 0 ok + @retval >0 Error (from table doesn't exists or to table exists) + @retval <0 Can't do rename, but no error +*/ + +static int +check_rename(THD *thd, rename_param *param, + TABLE_LIST *ren_table, + const LEX_CSTRING *new_db, + const LEX_CSTRING *new_table_name, + const LEX_CSTRING *new_table_alias, + bool skip_error, bool if_exists) +{ + DBUG_ENTER("check_rename"); + + if (lower_case_table_names == 2) + { + param->old_alias= ren_table->alias; + param->new_alias= *new_table_alias; + } + else + { + param->old_alias= ren_table->table_name; + param->new_alias= *new_table_name; + } + DBUG_ASSERT(param->new_alias.str); + + if (!ha_table_exists(thd, &ren_table->db, ¶m->old_alias, + ¶m->from_table_hton) || + !param->from_table_hton) + { + my_error(ER_NO_SUCH_TABLE, MYF((skip_error | if_exists) ? ME_NOTE : 0), + ren_table->db.str, param->old_alias.str); + DBUG_RETURN(skip_error || if_exists ? -1 : 1); + } + + if (param->from_table_hton != view_pseudo_hton && + ha_check_if_updates_are_ignored(thd, param->from_table_hton, "RENAME")) + { + /* + Shared table. Just drop the old .frm as it's not correct anymore + Discovery will find the old table when it's accessed + */ + tdc_remove_table(thd, ren_table->db.str, ren_table->table_name.str); + quick_rm_table(thd, 0, &ren_table->db, ¶m->old_alias, FRM_ONLY, 0); + DBUG_RETURN(-1); + } + + if (ha_table_exists(thd, new_db, ¶m->new_alias, 0)) + { + my_error(ER_TABLE_EXISTS_ERROR, MYF(0), param->new_alias.str); + DBUG_RETURN(1); // This can't be skipped + } + DBUG_RETURN(0); +} + + /* Rename a single table or a view @@ -265,6 +319,7 @@ do_rename_temporary(THD *thd, TABLE_LIST *ren_table, TABLE_LIST *new_table, DESCRIPTION Rename a single table or a view. + In case of failure, all changes will be reverted RETURN false Ok @@ -272,54 +327,22 @@ do_rename_temporary(THD *thd, TABLE_LIST *ren_table, TABLE_LIST *new_table, */ static bool -do_rename(THD *thd, TABLE_LIST *ren_table, const LEX_CSTRING *new_db, +do_rename(THD *thd, rename_param *param, DDL_LOG_STATE *ddl_log_state, + TABLE_LIST *ren_table, const LEX_CSTRING *new_db, const LEX_CSTRING *new_table_name, const LEX_CSTRING *new_table_alias, bool skip_error, bool if_exists, bool *force_if_exists) { int rc= 1; - handlerton *hton, *new_hton; - LEX_CSTRING old_alias, new_alias; + handlerton *hton; + LEX_CSTRING *old_alias, *new_alias; DBUG_ENTER("do_rename"); DBUG_PRINT("enter", ("skip_error: %d if_exists: %d", (int) skip_error, (int) if_exists)); - if (lower_case_table_names == 2) - { - old_alias= ren_table->alias; - new_alias= *new_table_alias; - } - else - { - old_alias= ren_table->table_name; - new_alias= *new_table_name; - } - DBUG_ASSERT(new_alias.str); - - if (!ha_table_exists(thd, &ren_table->db, &old_alias, &hton) || !hton) - { - my_error(ER_NO_SUCH_TABLE, MYF((skip_error | if_exists) ? ME_NOTE : 0), - ren_table->db.str, old_alias.str); - DBUG_RETURN(skip_error || if_exists ? 0 : 1); - } - - if (hton != view_pseudo_hton && - ha_check_if_updates_are_ignored(thd, hton, "RENAME")) - { - /* - Shared table. Just drop the old .frm as it's not correct anymore - Discovery will find the old table when it's accessed - */ - tdc_remove_table(thd, ren_table->db.str, ren_table->table_name.str); - quick_rm_table(thd, 0, &ren_table->db, &old_alias, FRM_ONLY, 0); - DBUG_RETURN(0); - } - - if (ha_table_exists(thd, new_db, &new_alias, &new_hton)) - { - my_error(ER_TABLE_EXISTS_ERROR, MYF(0), new_alias.str); - DBUG_RETURN(1); // This can't be skipped - } + old_alias= ¶m->old_alias; + new_alias= ¶m->new_alias; + hton= param->from_table_hton; DBUG_ASSERT(!thd->locked_tables_mode); @@ -337,17 +360,36 @@ do_rename(THD *thd, TABLE_LIST *ren_table, const LEX_CSTRING *new_db, *force_if_exists= 1; thd->replication_flags= 0; - if (!(rc= mysql_rename_table(hton, &ren_table->db, &old_alias, - new_db, &new_alias, 0))) + + if (ddl_log_rename_table(thd, ddl_log_state, hton, + &ren_table->db, old_alias, new_db, new_alias)) + DBUG_RETURN(1); + + debug_crash_here("ddl_log_rename_before_rename_table"); + if (!(rc= mysql_rename_table(hton, &ren_table->db, old_alias, + new_db, new_alias, 0))) { - (void) rename_table_in_stat_tables(thd, &ren_table->db, - &ren_table->table_name, - new_db, &new_alias); - if ((rc= Table_triggers_list::change_table_name(thd, &ren_table->db, - &old_alias, - &ren_table->table_name, - new_db, - &new_alias))) + /* Table rename succeded. + It's safe to start recovery at rename trigger phase + */ + debug_crash_here("ddl_log_rename_before_phase_trigger"); + ddl_log_update_phase(ddl_log_state, DDL_RENAME_PHASE_TRIGGER); + + debug_crash_here("ddl_log_rename_before_rename_trigger"); + + if (!(rc= Table_triggers_list::change_table_name(thd, &ren_table->db, + old_alias, + &ren_table->table_name, + new_db, + new_alias))) + { + debug_crash_here("ddl_log_rename_before_stat_tables"); + (void) rename_table_in_stat_tables(thd, &ren_table->db, + &ren_table->table_name, + new_db, new_alias); + debug_crash_here("ddl_log_rename_after_stat_tables"); + } + else { /* We've succeeded in renaming table's .frm and in updating @@ -355,8 +397,12 @@ do_rename(THD *thd, TABLE_LIST *ren_table, const LEX_CSTRING *new_db, triggers appropriately. So let us revert operations on .frm and handler's data and report about failure to rename table. */ - (void) mysql_rename_table(hton, new_db, &new_alias, - &ren_table->db, &old_alias, NO_FK_CHECKS); + debug_crash_here("ddl_log_rename_after_failed_rename_trigger"); + (void) mysql_rename_table(hton, new_db, new_alias, + &ren_table->db, old_alias, NO_FK_CHECKS); + debug_crash_here("ddl_log_rename_after_revert_rename_table"); + ddl_log_disable_entry(ddl_log_state); + debug_crash_here("ddl_log_rename_after_disable_entry"); } } if (thd->replication_flags & OPTION_IF_EXISTS) @@ -371,9 +417,25 @@ do_rename(THD *thd, TABLE_LIST *ren_table, const LEX_CSTRING *new_db, */ if (thd->lex->sql_command != SQLCOM_ALTER_DB_UPGRADE && cmp(&ren_table->db, new_db)) + { my_error(ER_FORBID_SCHEMA_CHANGE, MYF(0), ren_table->db.str, new_db->str); - else - rc= mysql_rename_view(thd, new_db, &new_alias, ren_table); + DBUG_RETURN(1); + } + + ddl_log_rename_view(thd, ddl_log_state, &ren_table->db, + &ren_table->table_name, new_db, new_alias); + debug_crash_here("ddl_log_rename_before_rename_view"); + rc= mysql_rename_view(thd, new_db, new_alias, &ren_table->db, + &ren_table->table_name); + debug_crash_here("ddl_log_rename_after_rename_view"); + if (rc) + { + /* + On error mysql_rename_view() will leave things as such. + */ + ddl_log_disable_entry(ddl_log_state); + debug_crash_here("ddl_log_rename_after_disable_entry"); + } } DBUG_RETURN(rc && !skip_error ? 1 : 0); } @@ -391,6 +453,7 @@ do_rename(THD *thd, TABLE_LIST *ren_table, const LEX_CSTRING *new_db, rename_tables() thd Thread handle table_list List of tables to rename + ddl_log_state ddl logging skip_error Whether to skip errors if_exists Don't give an error if table doesn't exists force_if_exists Set to 1 if we have to log the query with 'IF EXISTS' @@ -403,14 +466,16 @@ do_rename(THD *thd, TABLE_LIST *ren_table, const LEX_CSTRING *new_db, RETURN 0 Ok - table pointer to the table list element which rename failed + 1 error + All tables are reverted to their original names */ -static TABLE_LIST * -rename_tables(THD *thd, TABLE_LIST *table_list, bool skip_error, - bool if_exists, bool *force_if_exists) +static bool +rename_tables(THD *thd, TABLE_LIST *table_list, DDL_LOG_STATE *ddl_log_state, + bool skip_error, bool if_exists, bool *force_if_exists) { TABLE_LIST *ren_table, *new_table; + List<TABLE_PAIR> tmp_tables; DBUG_ENTER("rename_tables"); *force_if_exists= 0; @@ -419,11 +484,49 @@ rename_tables(THD *thd, TABLE_LIST *table_list, bool skip_error, { new_table= ren_table->next_local; - if (is_temporary_table(ren_table) ? - do_rename_temporary(thd, ren_table, new_table, skip_error) : - do_rename(thd, ren_table, &new_table->db, &new_table->table_name, - &new_table->alias, skip_error, if_exists, force_if_exists)) - DBUG_RETURN(ren_table); + if (is_temporary_table(ren_table)) + { + /* + Store renamed temporary tables into a list. + We don't store these in the ddl log to avoid writes and syncs + when only using temporary tables. We don't need the log as + all temporary tables will disappear anyway in a crash. + */ + TABLE_PAIR *pair= (TABLE_PAIR*) thd->alloc(sizeof(*pair)); + if (! pair || tmp_tables.push_front(pair, thd->mem_root)) + goto revert_rename; + pair->from= ren_table; + pair->to= new_table; + + if (do_rename_temporary(thd, ren_table, new_table, skip_error)) + goto revert_rename; + } + else + { + int error; + rename_param param; + error= check_rename(thd, ¶m, ren_table, &new_table->db, + &new_table->table_name, + &new_table->alias, skip_error, if_exists); + if (error < 0) + continue; // Ignore rename (if exists) + if (error > 0) + goto revert_rename; + + if (do_rename(thd, ¶m, ddl_log_state, + ren_table, + &new_table->db, &new_table->table_name, &new_table->alias, + skip_error, if_exists, force_if_exists)) + goto revert_rename; + } } DBUG_RETURN(0); + +revert_rename: + /* Revert temporary tables. Normal tables are reverted in the caller */ + List_iterator_fast<TABLE_PAIR> it(tmp_tables); + while (TABLE_PAIR *pair= it++) + do_rename_temporary(thd, pair->to, pair->from, 1); + + DBUG_RETURN(1); } diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 9c27f0038d4..5271e4f8e0a 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -549,8 +549,11 @@ uint build_table_filename(char *buff, size_t bufflen, const char *db, (void) tablename_to_filename(db, dbbuff, sizeof(dbbuff)); - /* Check if this is a temporary table name. Allow it if a corresponding .frm file exists */ - if (is_prefix(table_name, tmp_file_prefix) && strlen(table_name) < NAME_CHAR_LEN && + /* + Check if this is a temporary table name. Allow it if a corresponding .frm + file exists */ + if (is_prefix(table_name, tmp_file_prefix) && + strlen(table_name) < NAME_CHAR_LEN && check_if_frm_exists(tbbuff, dbbuff, table_name)) flags|= FN_IS_TMP; @@ -560,13 +563,16 @@ uint build_table_filename(char *buff, size_t bufflen, const char *db, (void) tablename_to_filename(table_name, tbbuff, sizeof(tbbuff)); char *end = buff + bufflen; - /* Don't add FN_ROOTDIR if mysql_data_home already includes it */ - char *pos = strnmov(buff, mysql_data_home, bufflen); - size_t rootdir_len= strlen(FN_ROOTDIR); - if (pos - rootdir_len >= buff && - memcmp(pos - rootdir_len, FN_ROOTDIR, rootdir_len) != 0) - pos= strnmov(pos, FN_ROOTDIR, end - pos); - pos= strxnmov(pos, end - pos, dbbuff, FN_ROOTDIR, NullS); + char *pos= strnmov(buff, mysql_data_home, bufflen-3); + /* + Add FN_LIBCHAR if mysql_data_home does not include it + In most cases mysql_data_home is just '.' + */ + if (pos[-1] != FN_LIBCHAR) + *pos++= FN_LIBCHAR; + pos= strxnmov(pos, end - 2 - pos, dbbuff,NullS); + *pos++= FN_LIBCHAR; + *pos= 0; #ifdef USE_SYMDIR if (!(flags & SKIP_SYMDIR_ACCESS)) { @@ -616,6 +622,31 @@ uint build_tmptable_filename(THD* thd, char *buff, size_t bufflen) DBUG_RETURN((uint)length); } +/* + Create lower case paths for engines that requires them +*/ + +void build_lower_case_table_filename(char *buff, size_t bufflen, + const LEX_CSTRING *db, + const LEX_CSTRING *table, + uint flags) +{ + char table_name[SAFE_NAME_LEN+1], db_name[SAFE_NAME_LEN+1]; + + DBUG_ASSERT(db->length <= SAFE_NAME_LEN && table->length <= SAFE_NAME_LEN); + + memcpy(db_name, db->str, db->length); + db_name[db->length]= 0; + my_casedn_str(files_charset_info, db_name); + + memcpy(table_name, table->str, table->length); + table_name[table->length]= 0; + my_casedn_str(files_charset_info, table_name); + + build_table_filename(buff, bufflen, db_name, table_name, "", + flags & FN_IS_TMP); +} + /** @brief construct a temporary shadow file name. @@ -4675,10 +4706,8 @@ mysql_rename_table(handlerton *base, const LEX_CSTRING *old_db, const LEX_CSTRING *new_name, uint flags) { THD *thd= current_thd; - char from[FN_REFLEN + 1], to[FN_REFLEN + 1], - lc_from[FN_REFLEN + 1], lc_to[FN_REFLEN + 1]; + char from[FN_REFLEN], to[FN_REFLEN], lc_from[FN_REFLEN], lc_to[FN_REFLEN]; char *from_base= from, *to_base= to; - char tmp_name[SAFE_NAME_LEN+1], tmp_db_name[SAFE_NAME_LEN+1]; handler *file; int error=0; ulonglong save_bits= thd->variables.option_bits; @@ -4700,37 +4729,20 @@ mysql_rename_table(handlerton *base, const LEX_CSTRING *old_db, length= build_table_filename(to, sizeof(to) - 1, new_db->str, new_name->str, "", flags & FN_TO_IS_TMP); // Check if we hit FN_REFLEN bytes along with file extension. - if (length+reg_ext_length > FN_REFLEN) + if (length+reg_ext_length >= FN_REFLEN) { my_error(ER_IDENT_CAUSES_TOO_LONG_PATH, MYF(0), (int) sizeof(to)-1, to); DBUG_RETURN(TRUE); } - /* - If lower_case_table_names == 2 (case-preserving but case-insensitive - file system) and the storage is not HA_FILE_BASED, we need to provide - a lowercase file name, but we leave the .frm in mixed case. - */ - if (lower_case_table_names == 2 && file && - !(file->ha_table_flags() & HA_FILE_BASED)) + if (file->needs_lower_case_filenames()) { - strmov(tmp_name, old_name->str); - my_casedn_str(files_charset_info, tmp_name); - strmov(tmp_db_name, old_db->str); - my_casedn_str(files_charset_info, tmp_db_name); - - build_table_filename(lc_from, sizeof(lc_from) - 1, tmp_db_name, tmp_name, - "", flags & FN_FROM_IS_TMP); + build_lower_case_table_filename(lc_from, sizeof(lc_from) -1, + old_db, old_name, flags & FN_FROM_IS_TMP); + build_lower_case_table_filename(lc_to, sizeof(lc_from) -1, + new_db, new_name, flags & FN_TO_IS_TMP); from_base= lc_from; - - strmov(tmp_name, new_name->str); - my_casedn_str(files_charset_info, tmp_name); - strmov(tmp_db_name, new_db->str); - my_casedn_str(files_charset_info, tmp_db_name); - - build_table_filename(lc_to, sizeof(lc_to) - 1, tmp_db_name, tmp_name, "", - flags & FN_TO_IS_TMP); - to_base= lc_to; + to_base= lc_to; } if (flags & NO_HA_TABLE) diff --git a/sql/sql_table.h b/sql/sql_table.h index 4bae4f8610c..a1661086ce5 100644 --- a/sql/sql_table.h +++ b/sql/sql_table.h @@ -77,6 +77,10 @@ uint build_table_filename(char *buff, size_t bufflen, const char *db, const char *table, const char *ext, uint flags); uint build_table_shadow_filename(char *buff, size_t bufflen, ALTER_PARTITION_PARAM_TYPE *lpt); +void build_lower_case_table_filename(char *buff, size_t bufflen, + const LEX_CSTRING *db, + const LEX_CSTRING *table, + uint flags); uint build_tmptable_filename(THD* thd, char *buff, size_t bufflen); bool mysql_create_table(THD *thd, TABLE_LIST *create_table, Table_specification_st *create_info, diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 305a864c917..b4d88e37bdc 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -2056,7 +2056,9 @@ bool Trigger::change_on_table_name(void* param_arg) @param[in,out] thd Thread context @param[in] db Old database of subject table @param[in] old_alias Old alias of subject table - @param[in] old_table Old name of subject table + @param[in] old_table Old name of subject table. The difference between + old_table and old_alias is that in case of lower_case_table_names + old_table == lowercase(old_alias) @param[in] new_db New database for subject table @param[in] new_table New name of subject table diff --git a/sql/sql_view.cc b/sql/sql_view.cc index f2523c66bd6..3a3db659e9d 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -37,6 +37,7 @@ #include "sql_cte.h" // check_dependencies_in_with_clauses() #include "opt_trace.h" #include "wsrep_mysqld.h" +#include "debug_sync.h" // debug_crash_here #define MD5_BUFF_LENGTH 33 @@ -2175,7 +2176,8 @@ bool mysql_rename_view(THD *thd, const LEX_CSTRING *new_db, const LEX_CSTRING *new_name, - TABLE_LIST *view) + const LEX_CSTRING *old_db, + const LEX_CSTRING *old_name) { LEX_CSTRING pathstr; File_parser *parser; @@ -2185,7 +2187,7 @@ mysql_rename_view(THD *thd, pathstr.str= (char *) path_buff; pathstr.length= build_table_filename(path_buff, sizeof(path_buff) - 1, - view->db.str, view->table_name.str, + old_db->str, old_name->str, reg_ext, 0); if ((parser= sql_parse_prepare(&pathstr, thd->mem_root, 1)) && @@ -2212,9 +2214,10 @@ mysql_rename_view(THD *thd, goto err; /* rename view and it's backups */ - if (rename_in_schema_file(thd, view->db.str, view->table_name.str, + if (rename_in_schema_file(thd, old_db->str, old_name->str, new_db->str, new_name->str)) goto err; + debug_crash_here("rename_view_after_rename_schema_file"); dir.str= dir_buff; dir.length= build_table_filename(dir_buff, sizeof(dir_buff) - 1, @@ -2231,16 +2234,25 @@ mysql_rename_view(THD *thd, (uchar*)&view_def, view_parameters)) { /* restore renamed view in case of error */ - rename_in_schema_file(thd, new_db->str, new_name->str, view->db.str, - view->table_name.str); + rename_in_schema_file(thd, new_db->str, new_name->str, old_db->str, + old_name->str); goto err; } - } else + } + else DBUG_RETURN(1); /* remove cache entries */ - query_cache_invalidate3(thd, view, 0); - sp_cache_invalidate(); + { + char key[NAME_LEN*2+1], *ptr; + memcpy(key, old_db->str, old_db->length); + ptr= key+ old_db->length; + *ptr++= 0; + memcpy(key, old_name->str, old_name->length); + ptr= key+ old_db->length; + *ptr++= 0; + query_cache.invalidate(thd, key, (size_t) (ptr-key), 0); + } error= FALSE; err: diff --git a/sql/sql_view.h b/sql/sql_view.h index c1e5dc49da3..536b5f1b784 100644 --- a/sql/sql_view.h +++ b/sql/sql_view.h @@ -53,8 +53,10 @@ extern TYPELIB updatable_views_with_limit_typelib; bool check_duplicate_names(THD *thd, List<Item>& item_list, bool gen_unique_view_names); -bool mysql_rename_view(THD *thd, const LEX_CSTRING *new_db, const LEX_CSTRING *new_name, - TABLE_LIST *view); +bool mysql_rename_view(THD *thd, const LEX_CSTRING *new_db, + const LEX_CSTRING *new_name, + const LEX_CSTRING *old_db, + const LEX_CSTRING *old_name); void make_valid_column_names(THD *thd, List<Item> &item_list); diff --git a/storage/maria/ma_rename.c b/storage/maria/ma_rename.c index a4388596f6b..6d44695d6a5 100644 --- a/storage/maria/ma_rename.c +++ b/storage/maria/ma_rename.c @@ -40,7 +40,8 @@ int maria_rename(const char *old_name, const char *new_name) #endif MARIA_HA *info; MARIA_SHARE *share; - myf sync_dir; + myf sync_dir= 0; + my_bool ddl_recovery= 0; DBUG_ENTER("maria_rename"); #ifdef EXTRA_DEBUG @@ -49,7 +50,28 @@ int maria_rename(const char *old_name, const char *new_name) #endif /** @todo LOCK take X-lock on table */ if (!(info= maria_open(old_name, O_RDWR, HA_OPEN_FOR_REPAIR, 0))) - DBUG_RETURN(my_errno); + { + int error= my_errno; + /* + Check if we are in recovery from a rename that failed in the middle + and we are now renaming things back. + */ + if (error == ENOENT) + { + char *index_file= from; + char *data_file= to; + fn_format(index_file, old_name, "", MARIA_NAME_IEXT, + MY_UNPACK_FILENAME | MY_APPEND_EXT); + fn_format(data_file, old_name, "", MARIA_NAME_DEXT, + MY_UNPACK_FILENAME | MY_APPEND_EXT); + if (!access(data_file, F_OK) && access(index_file, F_OK)) + { + ddl_recovery= 1; + goto forced_rename; + } + } + DBUG_RETURN(error); + } share= info->s; #ifdef USE_RAID raid_type = share->base.raid_type; @@ -62,13 +84,12 @@ int maria_rename(const char *old_name, const char *new_name) this is important; make sure transactionality has been re-enabled. */ DBUG_ASSERT(share->now_transactional == share->base.born_transactional); - sync_dir= (share->now_transactional && !share->temporary && - !maria_in_recovery) ? MY_SYNC_DIR : 0; - if (sync_dir) + if (share->now_transactional && !share->temporary && !maria_in_recovery) { LSN lsn; LEX_CUSTRING log_array[TRANSLOG_INTERNAL_PARTS + 2]; size_t old_name_len= strlen(old_name)+1, new_name_len= strlen(new_name)+1; + sync_dir= MY_SYNC_DIR; log_array[TRANSLOG_INTERNAL_PARTS + 0].str= (uchar*)old_name; log_array[TRANSLOG_INTERNAL_PARTS + 0].length= old_name_len; log_array[TRANSLOG_INTERNAL_PARTS + 1].str= (uchar*)new_name; @@ -106,6 +127,7 @@ int maria_rename(const char *old_name, const char *new_name) _ma_reset_state(info); maria_close(info); +forced_rename: fn_format(from,old_name,"",MARIA_NAME_IEXT,MY_UNPACK_FILENAME|MY_APPEND_EXT); fn_format(to,new_name,"",MARIA_NAME_IEXT,MY_UNPACK_FILENAME|MY_APPEND_EXT); if (mysql_file_rename_with_symlink(key_file_kfile, from, to, @@ -116,7 +138,7 @@ int maria_rename(const char *old_name, const char *new_name) data_file_rename_error= mysql_file_rename_with_symlink(key_file_dfile, from, to, MYF(MY_WME | sync_dir)); - if (data_file_rename_error) + if (data_file_rename_error && ! ddl_recovery) { /* now we have a renamed index file and a non-renamed data file, try to @@ -129,5 +151,4 @@ int maria_rename(const char *old_name, const char *new_name) MYF(MY_WME | sync_dir)); } DBUG_RETURN(data_file_rename_error); - } |