diff options
author | Marko Mäkelä <marko.makela@mariadb.com> | 2020-11-13 21:54:21 +0200 |
---|---|---|
committer | Marko Mäkelä <marko.makela@mariadb.com> | 2020-11-13 21:54:21 +0200 |
commit | d7a582489900ea175d9659304c4fdc39b3880014 (patch) | |
tree | 06a2485b15b84fedf197c573276441ea8d8bccb0 | |
parent | 97569d3c37a66bc941b933ac7c2cefa5430a5175 (diff) | |
parent | b2029c0300bf8c311ff5d9fdc4b73a9e48bf6930 (diff) | |
download | mariadb-git-d7a582489900ea175d9659304c4fdc39b3880014.tar.gz |
Merge 10.4 into 10.5
53 files changed, 1627 insertions, 472 deletions
diff --git a/include/wsrep.h b/include/wsrep.h index f8a1863b966..703b89f966a 100644 --- a/include/wsrep.h +++ b/include/wsrep.h @@ -25,21 +25,24 @@ #define DBUG_ASSERT_IF_WSREP(A) DBUG_ASSERT(A) #define WSREP_MYSQL_DB (char *)"mysql" +#define WSREP_TO_ISOLATION_BEGIN_IF(db_, table_, table_list_) \ + if (WSREP_ON && WSREP(thd) && wsrep_to_isolation_begin(thd, db_, table_, table_list_)) -#define WSREP_TO_ISOLATION_BEGIN(db_, table_, table_list_) \ - if (WSREP(thd) && wsrep_to_isolation_begin(thd, db_, table_, table_list_)) \ +#define WSREP_TO_ISOLATION_BEGIN(db_, table_, table_list_) \ + if (WSREP_ON && WSREP(thd) && wsrep_to_isolation_begin(thd, db_, table_, table_list_)) \ goto wsrep_error_label; #define WSREP_TO_ISOLATION_BEGIN_CREATE(db_, table_, table_list_, create_info_) \ - if (WSREP(thd) && \ + if (WSREP_ON && WSREP(thd) && \ wsrep_to_isolation_begin(thd, db_, table_, \ - table_list_, NULL, create_info_)) \ + table_list_, nullptr, nullptr, create_info_))\ goto wsrep_error_label; -#define WSREP_TO_ISOLATION_BEGIN_ALTER(db_, table_, table_list_, alter_info_, create_info_) \ - if (WSREP(thd) && wsrep_thd_is_local(thd) && \ - wsrep_to_isolation_begin(thd, db_, table_, \ - table_list_, alter_info_, create_info_)) \ +#define WSREP_TO_ISOLATION_BEGIN_ALTER(db_, table_, table_list_, alter_info_, fk_tables_, create_info_) \ + if (WSREP(thd) && wsrep_thd_is_local(thd) && \ + wsrep_to_isolation_begin(thd, db_, table_, \ + table_list_, alter_info_, \ + fk_tables_, create_info_)) \ goto wsrep_error_label; #define WSREP_TO_ISOLATION_END \ @@ -55,6 +58,10 @@ if (WSREP(thd) && !thd->lex->no_write_to_binlog \ && wsrep_to_isolation_begin(thd, db_, table_, table_list_)) goto wsrep_error_label; +#define WSREP_TO_ISOLATION_BEGIN_FK_TABLES(db_, table_, table_list_, fk_tables) \ + if (WSREP(thd) && !thd->lex->no_write_to_binlog \ + && wsrep_to_isolation_begin(thd, db_, table_, table_list_, NULL, fk_tables)) + #define WSREP_SYNC_WAIT(thd_, before_) \ { if (WSREP_CLIENT(thd_) && \ wsrep_sync_wait(thd_, before_)) goto wsrep_error_label; } @@ -68,7 +75,8 @@ #define WSREP_DEBUG(...) #define WSREP_ERROR(...) #define WSREP_TO_ISOLATION_BEGIN(db_, table_, table_list_) do { } while(0) -#define WSREP_TO_ISOLATION_BEGIN_ALTER(db_, table_, table_list_, alter_info_, create_info_) +#define WSREP_TO_ISOLATION_BEGIN_ALTER(db_, table_, table_list_, alter_info_, fk_tables_, create_info_) +#define WSREP_TO_ISOLATION_BEGIN_FK_TABLES(db_, table_, table_list_, fk_tables_) #define WSREP_TO_ISOLATION_END #define WSREP_TO_ISOLATION_BEGIN_CREATE(db_, table_, table_list_, create_info_) #define WSREP_TO_ISOLATION_BEGIN_WRTCHK(db_, table_, table_list_) diff --git a/mysql-test/main/lock_user.result b/mysql-test/main/lock_user.result index 5048c5a9bee..d2221e2cd8c 100644 --- a/mysql-test/main/lock_user.result +++ b/mysql-test/main/lock_user.result @@ -130,5 +130,42 @@ connection default; # alter user user1@localhost account lock; ERROR HY000: Access denied, this account is locked +# +# MDEV-24098 SHOW CREATE USER invalid for both PASSWORD EXPIRE and +# and LOCKED +# +alter user user1@localhost PASSWORD EXPIRE; +show create user user1@localhost; +CREATE USER for user1@localhost +CREATE USER `user1`@`localhost` ACCOUNT LOCK PASSWORD EXPIRE +drop user user1@localhost; +# +# MDEV-24098 CREATE USER/ALTER USER PASSWORD EXPIRE/LOCK in +# either order. +# +create user user1@localhost PASSWORD EXPIRE ACCOUNT LOCK; +show create user user1@localhost; +CREATE USER for user1@localhost +CREATE USER `user1`@`localhost` ACCOUNT LOCK PASSWORD EXPIRE +drop user user1@localhost; +create user user1@localhost ACCOUNT LOCK PASSWORD EXPIRE; +show create user user1@localhost; +CREATE USER for user1@localhost +CREATE USER `user1`@`localhost` ACCOUNT LOCK PASSWORD EXPIRE +alter user user1@localhost PASSWORD EXPIRE NEVER ACCOUNT UNLOCK ; +show create user user1@localhost; +CREATE USER for user1@localhost +CREATE USER `user1`@`localhost` PASSWORD EXPIRE +alter user user1@localhost ACCOUNT LOCK PASSWORD EXPIRE DEFAULT; +show create user user1@localhost; +CREATE USER for user1@localhost +CREATE USER `user1`@`localhost` ACCOUNT LOCK PASSWORD EXPIRE +alter user user1@localhost PASSWORD EXPIRE INTERVAL 60 DAY ACCOUNT UNLOCK; +select * from mysql.global_priv where user='user1'; +Host User Priv +localhost user1 {"access":0,"version_id":100509,"plugin":"mysql_native_password","authentication_string":"","account_locked":false,"password_last_changed":0,"password_lifetime":60} +show create user user1@localhost; +CREATE USER for user1@localhost +CREATE USER `user1`@`localhost` PASSWORD EXPIRE drop user user1@localhost; drop user user2@localhost; diff --git a/mysql-test/main/lock_user.test b/mysql-test/main/lock_user.test index 17ce1cc79da..530883f33ef 100644 --- a/mysql-test/main/lock_user.test +++ b/mysql-test/main/lock_user.test @@ -137,6 +137,32 @@ alter user user1@localhost account lock; --error ER_ACCOUNT_HAS_BEEN_LOCKED --change_user user1 +--echo # +--echo # MDEV-24098 SHOW CREATE USER invalid for both PASSWORD EXPIRE and +--echo # and LOCKED +--echo # +alter user user1@localhost PASSWORD EXPIRE; +show create user user1@localhost; +drop user user1@localhost; + +--echo # +--echo # MDEV-24098 CREATE USER/ALTER USER PASSWORD EXPIRE/LOCK in +--echo # either order. +--echo # +create user user1@localhost PASSWORD EXPIRE ACCOUNT LOCK; +show create user user1@localhost; +drop user user1@localhost; +create user user1@localhost ACCOUNT LOCK PASSWORD EXPIRE; +show create user user1@localhost; +alter user user1@localhost PASSWORD EXPIRE NEVER ACCOUNT UNLOCK ; +show create user user1@localhost; +alter user user1@localhost ACCOUNT LOCK PASSWORD EXPIRE DEFAULT; +show create user user1@localhost; +# note output needs to be corrected by MDEV-24114: password expire users cannot be unexpired +alter user user1@localhost PASSWORD EXPIRE INTERVAL 60 DAY ACCOUNT UNLOCK; +select * from mysql.global_priv where user='user1'; +show create user user1@localhost; + drop user user1@localhost; drop user user2@localhost; diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 37089f6dec6..b2f948fe31d 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -647,6 +647,7 @@ sub run_test_server ($$$) { # Client disconnected mtr_verbose("Child closed socket"); $s->remove($sock); + $sock->close; if (--$childs == 0){ return ("Completed", $test_failure, $completed, $extra_warnings); } @@ -816,6 +817,7 @@ sub run_test_server ($$$) { # Test failure due to warnings, force is off return ("Warnings in log", 1, $completed, $extra_warnings); } + next; } elsif ($line =~ /^SPENT/) { add_total_times($line); @@ -4102,6 +4104,7 @@ sub run_testcase ($$) { if (start_servers($tinfo)) { report_failure_and_restart($tinfo); + unlink $path_current_testlog; return 1; } } diff --git a/mysql-test/suite/galera/r/MDEV-24063.result b/mysql-test/suite/galera/r/MDEV-24063.result new file mode 100644 index 00000000000..757cc07a642 --- /dev/null +++ b/mysql-test/suite/galera/r/MDEV-24063.result @@ -0,0 +1,8 @@ +connection node_2; +connection node_1; +connect node_2a, 127.0.0.1, root, , test, $NODE_MYPORT_2; +connection node_2a; +CREATE TABLE t1 (f1 INTEGER PRIMARY KEY); +connection node_2; +SET GLOBAL wsrep_on=OFF; +DROP TABLE t1; diff --git a/mysql-test/suite/galera/r/galera_ddl_fk_conflict.result b/mysql-test/suite/galera/r/galera_ddl_fk_conflict.result new file mode 100644 index 00000000000..5f09345b79b --- /dev/null +++ b/mysql-test/suite/galera/r/galera_ddl_fk_conflict.result @@ -0,0 +1,657 @@ +connection node_2; +connection node_1; +connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1; +connection node_1a; +SET SESSION wsrep_sync_wait=0; +connect node_1b, 127.0.0.1, root, , test, $NODE_MYPORT_1; +connection node_1b; +SET SESSION wsrep_sync_wait=0; +###################################################################### +# Test for OPTIMIZE +###################################################################### +###################################################################### +# +# Scenario #1: DML working on FK parent table BF aborted by DDL +# over child table +# +###################################################################### +connection node_1; +SET SESSION wsrep_sync_wait=0; +CREATE TABLE p1 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p1 VALUES (1, 'INITIAL VALUE'); +CREATE TABLE p2 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p2 VALUES (1, 'INITIAL VALUE'); +INSERT INTO p2 VALUES (2, 'INITIAL VALUE'); +CREATE TABLE c1 (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p1(pk)); +INSERT INTO c1 VALUES (1,1); +CREATE TABLE c2 (pk INTEGER PRIMARY KEY, fk1 INTEGER, fk2 INTEGER, FOREIGN KEY (fk1) REFERENCES p1(pk), FOREIGN KEY (fk2) REFERENCES p2(pk)); +INSERT INTO c2 VALUES (1,1,1), (2,1,2); +connection node_1; +SET AUTOCOMMIT=ON; +START TRANSACTION; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +connection node_2; +SET SESSION wsrep_sync_wait=0; +OPTIMIZE TABLE c1 ; +Table Op Msg_type Msg_text +test.c1 optimize note Table does not support optimize, doing recreate + analyze instead +test.c1 optimize status OK +connection node_1; +COMMIT; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +###################################################################### +# +# Scenario #2: DML working on FK parent table tries to replicate, but +# fails in certification for earlier DDL on child table +# +###################################################################### +connection node_1; +BEGIN; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +OPTIMIZE TABLE c1 ; +Table Op Msg_type Msg_text +test.c1 optimize note Table does not support optimize, doing recreate + analyze instead +test.c1 optimize status OK +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +COMMIT; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +###################################################################### +# +# Scenario #3: 2 DMLs working on two FK parent tables try to replicate, +# but fails in certification for earlier DDL on child table +# which is child to both FK parents +# +###################################################################### +connection node_1; +BEGIN; +connection node_1b; +BEGIN; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +OPTIMIZE TABLE c2 ; +Table Op Msg_type Msg_text +test.c2 optimize note Table does not support optimize, doing recreate + analyze instead +test.c2 optimize status OK +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +COMMIT; +connection node_1b; +UPDATE p2 SET f2 = 'TO DEADLOCK' WHERE pk = 2; +COMMIT; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +connection node_1b; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +DROP TABLE c1, c2; +DROP TABLE p1, p2; +###################################################################### +# Test for OPTIMIZE +###################################################################### +connection node_1; +SET SESSION wsrep_sync_wait=0; +CREATE TABLE p1 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p1 VALUES (1, 'INITIAL VALUE'); +CREATE TABLE c1 (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p1(pk)); +INSERT INTO c1 VALUES (1,1); +###################################################################### +# +# Scenario #4: DML working on FK parent table tries to replicate, but +# fails in certification for earlier DDL on child table +# and another temporary table. TMP table should be skipped +# but FK child table should be replicated with proper keys +# +###################################################################### +connection node_1; +BEGIN; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +CREATE TEMPORARY TABLE tmp (i int); +OPTIMIZE TABLE c1, tmp ; +Table Op Msg_type Msg_text +test.c1 optimize note Table does not support optimize, doing recreate + analyze instead +test.c1 optimize status OK +test.tmp optimize note Table does not support optimize, doing recreate + analyze instead +test.tmp optimize status OK +DROP TABLE tmp; +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +COMMIT; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +DROP TABLE c1; +DROP TABLE p1; +###################################################################### +# Test for REPAIR +###################################################################### +###################################################################### +# +# Scenario #1: DML working on FK parent table BF aborted by DDL +# over child table +# +###################################################################### +connection node_1; +SET SESSION wsrep_sync_wait=0; +CREATE TABLE p1 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p1 VALUES (1, 'INITIAL VALUE'); +CREATE TABLE p2 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p2 VALUES (1, 'INITIAL VALUE'); +INSERT INTO p2 VALUES (2, 'INITIAL VALUE'); +CREATE TABLE c1 (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p1(pk)); +INSERT INTO c1 VALUES (1,1); +CREATE TABLE c2 (pk INTEGER PRIMARY KEY, fk1 INTEGER, fk2 INTEGER, FOREIGN KEY (fk1) REFERENCES p1(pk), FOREIGN KEY (fk2) REFERENCES p2(pk)); +INSERT INTO c2 VALUES (1,1,1), (2,1,2); +connection node_1; +SET AUTOCOMMIT=ON; +START TRANSACTION; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +connection node_2; +SET SESSION wsrep_sync_wait=0; +REPAIR TABLE c1 ; +Table Op Msg_type Msg_text +test.c1 repair note The storage engine for the table doesn't support repair +connection node_1; +COMMIT; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +###################################################################### +# +# Scenario #2: DML working on FK parent table tries to replicate, but +# fails in certification for earlier DDL on child table +# +###################################################################### +connection node_1; +BEGIN; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +REPAIR TABLE c1 ; +Table Op Msg_type Msg_text +test.c1 repair note The storage engine for the table doesn't support repair +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +COMMIT; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +###################################################################### +# +# Scenario #3: 2 DMLs working on two FK parent tables try to replicate, +# but fails in certification for earlier DDL on child table +# which is child to both FK parents +# +###################################################################### +connection node_1; +BEGIN; +connection node_1b; +BEGIN; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +REPAIR TABLE c2 ; +Table Op Msg_type Msg_text +test.c2 repair note The storage engine for the table doesn't support repair +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +COMMIT; +connection node_1b; +UPDATE p2 SET f2 = 'TO DEADLOCK' WHERE pk = 2; +COMMIT; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +connection node_1b; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +DROP TABLE c1, c2; +DROP TABLE p1, p2; +###################################################################### +# Test for REPAIR +###################################################################### +connection node_1; +SET SESSION wsrep_sync_wait=0; +CREATE TABLE p1 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p1 VALUES (1, 'INITIAL VALUE'); +CREATE TABLE c1 (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p1(pk)); +INSERT INTO c1 VALUES (1,1); +###################################################################### +# +# Scenario #4: DML working on FK parent table tries to replicate, but +# fails in certification for earlier DDL on child table +# and another temporary table. TMP table should be skipped +# but FK child table should be replicated with proper keys +# +###################################################################### +connection node_1; +BEGIN; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +CREATE TEMPORARY TABLE tmp (i int); +REPAIR TABLE c1, tmp ; +Table Op Msg_type Msg_text +test.c1 repair note The storage engine for the table doesn't support repair +test.tmp repair note The storage engine for the table doesn't support repair +DROP TABLE tmp; +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +COMMIT; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +DROP TABLE c1; +DROP TABLE p1; +###################################################################### +# Test for ALTER ENGINE=INNODB +###################################################################### +###################################################################### +# +# Scenario #1: DML working on FK parent table BF aborted by DDL +# over child table +# +###################################################################### +connection node_1; +SET SESSION wsrep_sync_wait=0; +CREATE TABLE p1 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p1 VALUES (1, 'INITIAL VALUE'); +CREATE TABLE p2 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p2 VALUES (1, 'INITIAL VALUE'); +INSERT INTO p2 VALUES (2, 'INITIAL VALUE'); +CREATE TABLE c1 (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p1(pk)); +INSERT INTO c1 VALUES (1,1); +CREATE TABLE c2 (pk INTEGER PRIMARY KEY, fk1 INTEGER, fk2 INTEGER, FOREIGN KEY (fk1) REFERENCES p1(pk), FOREIGN KEY (fk2) REFERENCES p2(pk)); +INSERT INTO c2 VALUES (1,1,1), (2,1,2); +connection node_1; +SET AUTOCOMMIT=ON; +START TRANSACTION; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +connection node_2; +SET SESSION wsrep_sync_wait=0; +ALTER TABLE c1 ENGINE=INNODB; +connection node_1; +COMMIT; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +###################################################################### +# +# Scenario #2: DML working on FK parent table tries to replicate, but +# fails in certification for earlier DDL on child table +# +###################################################################### +connection node_1; +BEGIN; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +ALTER TABLE c1 ENGINE=INNODB; +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +COMMIT; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +###################################################################### +# +# Scenario #3: 2 DMLs working on two FK parent tables try to replicate, +# but fails in certification for earlier DDL on child table +# which is child to both FK parents +# +###################################################################### +connection node_1; +BEGIN; +connection node_1b; +BEGIN; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +ALTER TABLE c2 ENGINE=INNODB; +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +COMMIT; +connection node_1b; +UPDATE p2 SET f2 = 'TO DEADLOCK' WHERE pk = 2; +COMMIT; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +connection node_1b; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +DROP TABLE c1, c2; +DROP TABLE p1, p2; +###################################################################### +# Test for TRUNCATE +###################################################################### +###################################################################### +# +# Scenario #1: DML working on FK parent table BF aborted by DDL +# over child table +# +###################################################################### +connection node_1; +SET SESSION wsrep_sync_wait=0; +CREATE TABLE p1 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p1 VALUES (1, 'INITIAL VALUE'); +CREATE TABLE p2 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p2 VALUES (1, 'INITIAL VALUE'); +INSERT INTO p2 VALUES (2, 'INITIAL VALUE'); +CREATE TABLE c1 (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p1(pk)); +INSERT INTO c1 VALUES (1,1); +CREATE TABLE c2 (pk INTEGER PRIMARY KEY, fk1 INTEGER, fk2 INTEGER, FOREIGN KEY (fk1) REFERENCES p1(pk), FOREIGN KEY (fk2) REFERENCES p2(pk)); +INSERT INTO c2 VALUES (1,1,1), (2,1,2); +connection node_1; +SET AUTOCOMMIT=ON; +START TRANSACTION; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +connection node_2; +SET SESSION wsrep_sync_wait=0; +TRUNCATE TABLE c1 ; +connection node_1; +COMMIT; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +###################################################################### +# +# Scenario #2: DML working on FK parent table tries to replicate, but +# fails in certification for earlier DDL on child table +# +###################################################################### +connection node_1; +BEGIN; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +TRUNCATE TABLE c1 ; +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +COMMIT; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +###################################################################### +# +# Scenario #3: 2 DMLs working on two FK parent tables try to replicate, +# but fails in certification for earlier DDL on child table +# which is child to both FK parents +# +###################################################################### +connection node_1; +BEGIN; +connection node_1b; +BEGIN; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_2; +TRUNCATE TABLE c2 ; +connection node_1a; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +COMMIT; +connection node_1b; +UPDATE p2 SET f2 = 'TO DEADLOCK' WHERE pk = 2; +COMMIT; +connection node_1a; +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +connection node_1; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +connection node_1b; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction +SELECT 'I deadlocked'; +I deadlocked +I deadlocked +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +connection node_2; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +EXPECT_1 +1 +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; +EXPECT_2 +2 +DROP TABLE c1, c2; +DROP TABLE p1, p2; diff --git a/mysql-test/suite/galera/r/galera_trigger.result b/mysql-test/suite/galera/r/galera_trigger.result index 40b63b327af..2a31a104404 100644 --- a/mysql-test/suite/galera/r/galera_trigger.result +++ b/mysql-test/suite/galera/r/galera_trigger.result @@ -45,34 +45,34 @@ connection node_2; set session wsrep_sync_wait=15; insert into t1(value) values (3); insert into t1(value) values (4); -select * from t2; -id tbl action -1 t1 INSERT -3 t1 INSERT -4 t1 INSERT -6 t1 INSERT +select tbl, action from t2; +tbl action +t1 INSERT +t1 INSERT +t1 INSERT +t1 INSERT connection node_1; drop trigger if exists log_insert; insert into t1(value) values (5); -select * from t2; -id tbl action -1 t1 INSERT -3 t1 INSERT -4 t1 INSERT -6 t1 INSERT +select tbl, action from t2; +tbl action +t1 INSERT +t1 INSERT +t1 INSERT +t1 INSERT connection node_2; insert into t1(value) values (6); -select * from t2; -id tbl action -1 t1 INSERT -3 t1 INSERT -4 t1 INSERT -6 t1 INSERT +select tbl, action from t2; +tbl action +t1 INSERT +t1 INSERT +t1 INSERT +t1 INSERT connection node_1; -select * from t2; -id tbl action -1 t1 INSERT -3 t1 INSERT -4 t1 INSERT -6 t1 INSERT +select tbl, action from t2; +tbl action +t1 INSERT +t1 INSERT +t1 INSERT +t1 INSERT drop table t1, t2; diff --git a/mysql-test/suite/galera/t/MDEV-24063.test b/mysql-test/suite/galera/t/MDEV-24063.test new file mode 100644 index 00000000000..24c5071cb15 --- /dev/null +++ b/mysql-test/suite/galera/t/MDEV-24063.test @@ -0,0 +1,20 @@ +# +# MDEV-24063 +# +# my_bool wsrep_thd_is_aborting(const THD*): +# Assertion `((&(&thd->LOCK_thd_data)->m_mutex)->count > 0 && +# pthread_equal(pthread_self(), (&(&thd->LOCK_thd_data)->m_mutex)->thread))' failed. +# + +--source include/galera_cluster.inc + +--connect node_2a, 127.0.0.1, root, , test, $NODE_MYPORT_2 +--connection node_2a +CREATE TABLE t1 (f1 INTEGER PRIMARY KEY); + +--connection node_2 +SET GLOBAL wsrep_on=OFF; +--source include/shutdown_mysqld.inc +--source include/start_mysqld.inc + +DROP TABLE t1; diff --git a/mysql-test/suite/galera/t/galera_ddl_fk_conflict.inc b/mysql-test/suite/galera/t/galera_ddl_fk_conflict.inc new file mode 100644 index 00000000000..06b7bbe41c4 --- /dev/null +++ b/mysql-test/suite/galera/t/galera_ddl_fk_conflict.inc @@ -0,0 +1,192 @@ +# +# Test for MDL BF-BF lock conflict +# There are some DDL statements, which take extensive MDL lock for +# a table referenced by foreign key constraint from the actual affetec table. +# This extensive MDL lock may cause MDL BF-BF confclict situations, if the +# FK parent table is not listed as certification key in the replication write set. +# i.e. if replication allows such DDL to apply in parallel with regular DML operating +# on the FK parent table. +# +# This test has two scenarios, where DML modifies FK parent table in node 1, +# and offending DDL for FK child table is sent from node 2. +# +# param: $table_admin_command +# DDL table command to test, script will build full SQL statement: +# $table_admin_command TABLE c; +# +# param: $table_admin_command_end +# Optional additional SQL syntax to end the SQL statement, if any +# $table_admin_command TABLE c $table_admin_command_end; +# +# scenario 1, can be used to test if a DDL statement causes such MDL locking vulnerability. +# call this test script with some table DDL command in $table_admin_command +# if scenario 1 passes (especially COMMIT does fail for ER_LOCK_DEADLOCK), +# then this particular DDL is vulnerable. scenraio 2 should fail for this DDL +# unless code has not been fixed to append parent table certification keys for it. +# + +--echo ###################################################################### +--echo # Test for $table_admin_command $table_admin_command_end +--echo ###################################################################### + + +--echo ###################################################################### +--echo # +--echo # Scenario #1: DML working on FK parent table BF aborted by DDL +--echo # over child table +--echo # +--echo ###################################################################### + +--connection node_1 +SET SESSION wsrep_sync_wait=0; + +CREATE TABLE p1 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p1 VALUES (1, 'INITIAL VALUE'); + + +CREATE TABLE p2 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p2 VALUES (1, 'INITIAL VALUE'); +INSERT INTO p2 VALUES (2, 'INITIAL VALUE'); + +CREATE TABLE c1 (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p1(pk)); +INSERT INTO c1 VALUES (1,1); + +CREATE TABLE c2 (pk INTEGER PRIMARY KEY, fk1 INTEGER, fk2 INTEGER, FOREIGN KEY (fk1) REFERENCES p1(pk), FOREIGN KEY (fk2) REFERENCES p2(pk)); +INSERT INTO c2 VALUES (1,1,1), (2,1,2); + +--connection node_1 +SET AUTOCOMMIT=ON; +START TRANSACTION; + +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; + +--connection node_2 +SET SESSION wsrep_sync_wait=0; +# wait for tables to be created in node 2 and all rows inserted as well +--let $wait_condition = SELECT COUNT(*) = 2 FROM INFORMATION_SCHEMA.INNODB_SYS_TABLES WHERE NAME LIKE 'test/c%' +--source include/wait_condition.inc +--let $wait_condition = SELECT COUNT(*) = 2 FROM c2 +--source include/wait_condition.inc + +# replicate the DDL to be tested +--eval $table_admin_command TABLE c1 $table_admin_command_end + +--connection node_1 +--error ER_LOCK_DEADLOCK +COMMIT; + +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; + +--connection node_2 +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; + +--echo ###################################################################### +--echo # +--echo # Scenario #2: DML working on FK parent table tries to replicate, but +--echo # fails in certification for earlier DDL on child table +--echo # +--echo ###################################################################### + +--connection node_1 +BEGIN; + +# Block the applier on node #1 and issue DDL on node 2 +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_set_sync_point.inc + +--connection node_2 +--eval $table_admin_command TABLE c1 $table_admin_command_end + +--connection node_1a +--source include/galera_wait_sync_point.inc +--source include/galera_clear_sync_point.inc +--let $expected_cert_failures = `SELECT VARIABLE_VALUE+1 FROM information_schema.global_status WHERE VARIABLE_NAME = 'wsrep_local_cert_failures'` + +--connection node_1 +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +--send COMMIT + +--connection node_1a +--let $wait_condition = SELECT VARIABLE_VALUE = $expected_cert_failures FROM information_schema.global_status WHERE VARIABLE_NAME = 'wsrep_local_cert_failures' +--source include/wait_condition.inc + +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_signal_sync_point.inc + +--connection node_1 +--error ER_LOCK_DEADLOCK +--reap + +SELECT 'I deadlocked'; + +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; + +--connection node_2 +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; + + +--echo ###################################################################### +--echo # +--echo # Scenario #3: 2 DMLs working on two FK parent tables try to replicate, +--echo # but fails in certification for earlier DDL on child table +--echo # which is child to both FK parents +--echo # +--echo ###################################################################### + +--connection node_1 +BEGIN; + +--connection node_1b +BEGIN; + +--connection node_1a +# Block the applier on node #1 and issue DDL on node 2 +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_set_sync_point.inc + +--connection node_2 +--eval $table_admin_command TABLE c2 $table_admin_command_end + +--connection node_1a +--source include/galera_wait_sync_point.inc +--source include/galera_clear_sync_point.inc +--let $expected_cert_failures = `SELECT VARIABLE_VALUE+2 FROM information_schema.global_status WHERE VARIABLE_NAME = 'wsrep_local_cert_failures'` + +--connection node_1 +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +--send COMMIT + +--connection node_1b +UPDATE p2 SET f2 = 'TO DEADLOCK' WHERE pk = 2; +--send COMMIT + +--connection node_1a +--let $wait_condition = SELECT VARIABLE_VALUE = $expected_cert_failures FROM information_schema.global_status WHERE VARIABLE_NAME = 'wsrep_local_cert_failures' +--source include/wait_condition.inc + +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_signal_sync_point.inc + +--connection node_1 +--error ER_LOCK_DEADLOCK +--reap +SELECT 'I deadlocked'; + +--connection node_1b +--error ER_LOCK_DEADLOCK +--reap +SELECT 'I deadlocked'; + +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; + +--connection node_2 +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; +SELECT COUNT(*) AS EXPECT_2 FROM p2 WHERE f2 = 'INITIAL VALUE'; + +DROP TABLE c1, c2; +DROP TABLE p1, p2; diff --git a/mysql-test/suite/galera/t/galera_ddl_fk_conflict.test b/mysql-test/suite/galera/t/galera_ddl_fk_conflict.test new file mode 100644 index 00000000000..88837933e5a --- /dev/null +++ b/mysql-test/suite/galera/t/galera_ddl_fk_conflict.test @@ -0,0 +1,37 @@ +# +# MDL BF-BF lock conflict +# + +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/have_debug_sync.inc +--source include/galera_have_debug_sync.inc + +# sync point controlling session +--connect node_1a, 127.0.0.1, root, , test, $NODE_MYPORT_1 +--connection node_1a +SET SESSION wsrep_sync_wait=0; + +# secondary conflicting DML victim session +--connect node_1b, 127.0.0.1, root, , test, $NODE_MYPORT_1 +--connection node_1b +SET SESSION wsrep_sync_wait=0; + +--let $table_admin_command = OPTIMIZE +--source galera_ddl_fk_conflict.inc +--source galera_ddl_fk_conflict_with_tmp.inc + +--let $table_admin_command = REPAIR +--source galera_ddl_fk_conflict.inc +--source galera_ddl_fk_conflict_with_tmp.inc + +--let $table_admin_command = ALTER +--let $table_admin_command_end = ENGINE=INNODB +--source galera_ddl_fk_conflict.inc + +--let $table_admin_command = TRUNCATE +--let $table_admin_command_end = +--source galera_ddl_fk_conflict.inc + +# CHECK and ANALYZE are not affected + diff --git a/mysql-test/suite/galera/t/galera_ddl_fk_conflict_with_tmp.inc b/mysql-test/suite/galera/t/galera_ddl_fk_conflict_with_tmp.inc new file mode 100644 index 00000000000..acf3c54180b --- /dev/null +++ b/mysql-test/suite/galera/t/galera_ddl_fk_conflict_with_tmp.inc @@ -0,0 +1,69 @@ +--echo ###################################################################### +--echo # Test for $table_admin_command $table_admin_command_end +--echo ###################################################################### + + +--connection node_1 +SET SESSION wsrep_sync_wait=0; + +CREATE TABLE p1 (pk INTEGER PRIMARY KEY, f2 CHAR(30)); +INSERT INTO p1 VALUES (1, 'INITIAL VALUE'); + + +CREATE TABLE c1 (pk INTEGER PRIMARY KEY, fk INTEGER, FOREIGN KEY (fk) REFERENCES p1(pk)); +INSERT INTO c1 VALUES (1,1); + +--echo ###################################################################### +--echo # +--echo # Scenario #4: DML working on FK parent table tries to replicate, but +--echo # fails in certification for earlier DDL on child table +--echo # and another temporary table. TMP table should be skipped +--echo # but FK child table should be replicated with proper keys +--echo # +--echo ###################################################################### + +--connection node_1 +BEGIN; + +# Block the applier on node #1 and issue DDL on node 2 +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_set_sync_point.inc + +--connection node_2 +# wait for tables to be created in node 2 and all rows inserted as well +--let $wait_condition = SELECT COUNT(*) = 1 FROM INFORMATION_SCHEMA.INNODB_SYS_TABLES WHERE NAME LIKE 'test/c1' +--source include/wait_condition.inc +--let $wait_condition = SELECT COUNT(*) = 1 FROM c1 +--source include/wait_condition.inc +CREATE TEMPORARY TABLE tmp (i int); +--eval $table_admin_command TABLE c1, tmp $table_admin_command_end +DROP TABLE tmp; + +--connection node_1a +--source include/galera_wait_sync_point.inc +--source include/galera_clear_sync_point.inc +--let $expected_cert_failures = `SELECT VARIABLE_VALUE+1 FROM information_schema.global_status WHERE VARIABLE_NAME = 'wsrep_local_cert_failures'` + +--connection node_1 +UPDATE p1 SET f2 = 'TO DEADLOCK' WHERE pk = 1; +--send COMMIT + +--connection node_1a +--let $wait_condition = SELECT VARIABLE_VALUE = $expected_cert_failures FROM information_schema.global_status WHERE VARIABLE_NAME = 'wsrep_local_cert_failures' +--source include/wait_condition.inc + +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_signal_sync_point.inc + +--connection node_1 +--error ER_LOCK_DEADLOCK +--reap + +SELECT 'I deadlocked'; +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; + +--connection node_2 +SELECT COUNT(*) AS EXPECT_1 FROM p1 WHERE f2 = 'INITIAL VALUE'; + +DROP TABLE c1; +DROP TABLE p1; diff --git a/mysql-test/suite/galera/t/galera_trigger.test b/mysql-test/suite/galera/t/galera_trigger.test index f85a75d0ff1..3c66b941e32 100644 --- a/mysql-test/suite/galera/t/galera_trigger.test +++ b/mysql-test/suite/galera/t/galera_trigger.test @@ -55,18 +55,18 @@ insert into t1(value) values (2); set session wsrep_sync_wait=15; insert into t1(value) values (3); insert into t1(value) values (4); -select * from t2; +select tbl, action from t2; --connection node_1 drop trigger if exists log_insert; insert into t1(value) values (5); -select * from t2; +select tbl, action from t2; --connection node_2 insert into t1(value) values (6); -select * from t2; +select tbl, action from t2; --connection node_1 -select * from t2; +select tbl, action from t2; drop table t1, t2; diff --git a/mysql-test/suite/galera/t/mysql-wsrep#198-master.opt b/mysql-test/suite/galera/t/mysql-wsrep#198-master.opt deleted file mode 100644 index beae84b3862..00000000000 --- a/mysql-test/suite/galera/t/mysql-wsrep#198-master.opt +++ /dev/null @@ -1 +0,0 @@ ---log-bin diff --git a/mysql-test/suite/galera/t/mysql-wsrep#198.cnf b/mysql-test/suite/galera/t/mysql-wsrep#198.cnf new file mode 100644 index 00000000000..d599f3940e4 --- /dev/null +++ b/mysql-test/suite/galera/t/mysql-wsrep#198.cnf @@ -0,0 +1,9 @@ +!include ../galera_2nodes.cnf + +[mysqld.1] +log-bin +wsrep-debug=1 + +[mysqld.1] +log-bin +wsrep-debug=1 diff --git a/mysql-test/suite/galera/t/mysql-wsrep#198.test b/mysql-test/suite/galera/t/mysql-wsrep#198.test index a80d030a8b0..a8190f2ae61 100644 --- a/mysql-test/suite/galera/t/mysql-wsrep#198.test +++ b/mysql-test/suite/galera/t/mysql-wsrep#198.test @@ -1,5 +1,6 @@ --source include/galera_cluster.inc --source include/have_innodb.inc +--source include/force_restart.inc CREATE TABLE t1 (id INT PRIMARY KEY) ENGINE=InnoDB; CREATE TABLE t2 (id INT PRIMARY KEY) ENGINE=InnoDB; diff --git a/mysql-test/suite/maria/repair.result b/mysql-test/suite/maria/repair.result index 6bb9e1b5b9e..296f251aa36 100644 --- a/mysql-test/suite/maria/repair.result +++ b/mysql-test/suite/maria/repair.result @@ -22,3 +22,19 @@ i 1 UNLOCK TABLES; DROP TABLE t1; +# +# MDEV-23824 SIGSEGV in end_io_cache on REPAIR LOCAL TABLE for Aria table +# +CREATE TABLE t1 (i INT) ENGINE=Aria; +INSERT INTO t1 VALUES (1); +SET max_session_mem_used=50000; +REPAIR LOCAL TABLE t1 USE_FRM; +Table Op Msg_type Msg_text +t1 repair error Failed to open partially repaired table +Warnings: +Error 1290 The MariaDB server is running with the --max-thread-mem-used=50000 option so it cannot execute this statement +REPAIR LOCAL TABLE t1; +Table Op Msg_type Msg_text +test.t1 repair Error The MariaDB server is running with the --max-thread-mem-used=50000 option so it cannot execute this statement +test.t1 repair error Corrupt +DROP TABLE t1; diff --git a/mysql-test/suite/maria/repair.test b/mysql-test/suite/maria/repair.test index 2f713950d3e..9603a949f9b 100644 --- a/mysql-test/suite/maria/repair.test +++ b/mysql-test/suite/maria/repair.test @@ -22,3 +22,14 @@ SELECT * FROM INFORMATION_SCHEMA.TABLES; SELECT * FROM t1; UNLOCK TABLES; DROP TABLE t1; + +--echo # +--echo # MDEV-23824 SIGSEGV in end_io_cache on REPAIR LOCAL TABLE for Aria table +--echo # + +CREATE TABLE t1 (i INT) ENGINE=Aria; +INSERT INTO t1 VALUES (1); +SET max_session_mem_used=50000; +REPAIR LOCAL TABLE t1 USE_FRM; +REPAIR LOCAL TABLE t1; +DROP TABLE t1; diff --git a/mysql-test/suite/perfschema/r/sxlock_func.result b/mysql-test/suite/perfschema/r/sxlock_func.result index ad544f78a28..b51234feda6 100644 --- a/mysql-test/suite/perfschema/r/sxlock_func.result +++ b/mysql-test/suite/perfschema/r/sxlock_func.result @@ -16,7 +16,6 @@ wait/synch/sxlock/innodb/dict_operation_lock wait/synch/sxlock/innodb/fil_space_latch wait/synch/sxlock/innodb/fts_cache_init_rw_lock wait/synch/sxlock/innodb/fts_cache_rw_lock -wait/synch/sxlock/innodb/index_online_log wait/synch/sxlock/innodb/index_tree_rw_lock wait/synch/sxlock/innodb/trx_i_s_cache_lock wait/synch/sxlock/innodb/trx_purge_latch diff --git a/mysql-test/suite/period/r/create.result b/mysql-test/suite/period/r/create.result index b454661afc9..69d7e918804 100644 --- a/mysql-test/suite/period/r/create.result +++ b/mysql-test/suite/period/r/create.result @@ -45,6 +45,10 @@ ERROR 42000: Incorrect column specifier for column 's' create or replace table t (id int primary key, s date, e date, period for mytime(s,x)); ERROR 42S22: Unknown column 'x' in 'mytime' +# MDEV-18842: Unfortunate error message when the same column is used +# for application period start and end +create or replace table t (s date, t date, period for apt(s,s)); +ERROR 42000: Column 's' specified twice create or replace table t (id int primary key, s date, e date, period for mytime(s,e), period for mytime2(s,e)); diff --git a/mysql-test/suite/period/t/create.test b/mysql-test/suite/period/t/create.test index 2e3de795698..49dcc6ad3c7 100644 --- a/mysql-test/suite/period/t/create.test +++ b/mysql-test/suite/period/t/create.test @@ -31,6 +31,12 @@ create or replace table t (id int primary key, s time, e time, --error ER_BAD_FIELD_ERROR create or replace table t (id int primary key, s date, e date, period for mytime(s,x)); + +--echo # MDEV-18842: Unfortunate error message when the same column is used +--echo # for application period start and end +--error ER_FIELD_SPECIFIED_TWICE +create or replace table t (s date, t date, period for apt(s,s)); + --error ER_MORE_THAN_ONE_PERIOD create or replace table t (id int primary key, s date, e date, period for mytime(s,e), diff --git a/mysql-test/suite/rpl/r/rpl_gtid_delete_domain.result b/mysql-test/suite/rpl/r/rpl_gtid_delete_domain.result index 74648501fbe..9c36973427a 100644 --- a/mysql-test/suite/rpl/r/rpl_gtid_delete_domain.result +++ b/mysql-test/suite/rpl/r/rpl_gtid_delete_domain.result @@ -22,7 +22,7 @@ START SLAVE IO_THREAD; include/wait_for_slave_io_error.inc [errno=1236] connection master; FLUSH BINARY LOGS; -PURGE BINARY LOGS TO 'master-bin.000002';; +include/wait_for_purge.inc "master-bin.000002" FLUSH BINARY LOGS DELETE_DOMAIN_ID=(11); SELECT @@global.gtid_binlog_pos, @@global.gtid_binlog_state; @@global.gtid_binlog_pos @@global.gtid_binlog_state diff --git a/mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test b/mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test index 0262998798a..5537b6fbf86 100644 --- a/mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test +++ b/mysql-test/suite/rpl/t/rpl_gtid_delete_domain.test @@ -53,17 +53,11 @@ START SLAVE IO_THREAD; # adjust the master binlog state FLUSH BINARY LOGS; --let $purge_to_binlog= query_get_value(SHOW MASTER STATUS, File, 1) ---eval PURGE BINARY LOGS TO '$purge_to_binlog'; +--let $purge_binlogs_to=$purge_to_binlog +--source include/wait_for_purge.inc + # with final removal of the extra domain -###adding to debug info to catch the failure (1076): ---error 0,1076 --eval FLUSH BINARY LOGS DELETE_DOMAIN_ID=($extra_domain_id) - -if ($mysql_errno == 1076) { - --echo ### Failure "Could not delete gtid domain" - --source include/show_rpl_debug_info.inc - } - SELECT @@global.gtid_binlog_pos, @@global.gtid_binlog_state; --connection slave diff --git a/scripts/mysqld_safe.sh b/scripts/mysqld_safe.sh index c284e75a248..85c9363081c 100644 --- a/scripts/mysqld_safe.sh +++ b/scripts/mysqld_safe.sh @@ -449,7 +449,8 @@ mysqld_ld_preload_text() { set_malloc_lib() { malloc_lib="$1" if expr "$malloc_lib" : "\(tcmalloc\|jemalloc\)" > /dev/null ; then - if ! my_which ldconfig > /dev/null 2>&1 + export PATH=$PATH:/sbin + if ! command -v ldconfig > /dev/null 2>&1 then log_error "ldconfig command not found, required for ldconfig -p" exit 1 diff --git a/sql/log.h b/sql/log.h index 58e681985eb..eaf7cde1c07 100644 --- a/sql/log.h +++ b/sql/log.h @@ -728,7 +728,7 @@ public: { bytes_written = 0; } - void harvest_bytes_written(ulonglong* counter) + void harvest_bytes_written(Atomic_counter<uint64> *counter) { #ifndef DBUG_OFF char buf1[22],buf2[22]; diff --git a/sql/rpl_rli.cc b/sql/rpl_rli.cc index 941616a26d5..b7127d86fcd 100644 --- a/sql/rpl_rli.cc +++ b/sql/rpl_rli.cc @@ -461,7 +461,7 @@ static inline int add_relay_log(Relay_log_info* rli,LOG_INFO* linfo) DBUG_RETURN(1); } rli->log_space_total += s.st_size; - DBUG_PRINT("info",("log_space_total: %llu", rli->log_space_total)); + DBUG_PRINT("info",("log_space_total: %llu", uint64(rli->log_space_total))); DBUG_RETURN(0); } @@ -1254,7 +1254,7 @@ int purge_relay_logs(Relay_log_info* rli, THD *thd, bool just_reset, mysql_mutex_unlock(rli->relay_log.get_log_lock()); } err: - DBUG_PRINT("info",("log_space_total: %llu",rli->log_space_total)); + DBUG_PRINT("info",("log_space_total: %llu", uint64(rli->log_space_total))); mysql_mutex_unlock(&rli->data_lock); DBUG_RETURN(error); } diff --git a/sql/rpl_rli.h b/sql/rpl_rli.h index 4223e015ecd..e3b59aa615c 100644 --- a/sql/rpl_rli.h +++ b/sql/rpl_rli.h @@ -240,7 +240,8 @@ public: threads, the SQL thread sets it to unblock the I/O thread and make it temporarily forget about the constraint. */ - ulonglong log_space_limit,log_space_total; + ulonglong log_space_limit; + Atomic_counter<uint64> log_space_total; bool ignore_log_space_limit; /* diff --git a/sql/slave.cc b/sql/slave.cc index c302ba1de64..cfefe5746d4 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -2819,7 +2819,7 @@ static bool wait_for_relay_log_space(Relay_log_info* rli) DBUG_PRINT("info", ("log_space_limit=%llu log_space_total=%llu " "ignore_log_space_limit=%d " "sql_force_rotate_relay=%d", - rli->log_space_limit, rli->log_space_total, + rli->log_space_limit, uint64(rli->log_space_total), (int) rli->ignore_log_space_limit, (int) rli->sql_force_rotate_relay)); } @@ -5085,7 +5085,7 @@ Stopping slave I/O thread due to out-of-memory error from master"); { DBUG_PRINT("info", ("log_space_limit=%llu log_space_total=%llu " "ignore_log_space_limit=%d", - rli->log_space_limit, rli->log_space_total, + rli->log_space_limit, uint64(rli->log_space_total), (int) rli->ignore_log_space_limit)); } #endif diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index ef96e8f2484..483be6e39d6 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -9087,6 +9087,9 @@ bool mysql_show_create_user(THD *thd, LEX_USER *lex_user) append_identifier(thd, &result, username, strlen(username)); add_user_parameters(thd, &result, acl_user, false); + if (acl_user->account_locked) + result.append(STRING_WITH_LEN(" ACCOUNT LOCK")); + if (acl_user->password_expired) result.append(STRING_WITH_LEN(" PASSWORD EXPIRE")); else if (!acl_user->password_lifetime) @@ -9098,9 +9101,6 @@ bool mysql_show_create_user(THD *thd, LEX_USER *lex_user) result.append(STRING_WITH_LEN(" DAY")); } - if (acl_user->account_locked) - result.append(STRING_WITH_LEN(" ACCOUNT LOCK")); - protocol->prepare_for_resend(); protocol->store(result.ptr(), result.length(), result.charset()); if (protocol->write()) diff --git a/sql/sql_admin.cc b/sql/sql_admin.cc index dc9b971f2ed..2d6f891f56f 100644 --- a/sql/sql_admin.cc +++ b/sql/sql_admin.cc @@ -1,5 +1,5 @@ /* Copyright (c) 2010, 2015, Oracle and/or its affiliates. - Copyright (c) 2011, 2019, MariaDB + Copyright (c) 2011, 2020, MariaDB This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -31,7 +31,7 @@ #include "strfunc.h" #include "sql_admin.h" #include "sql_statistics.h" - +#include "wsrep_mysqld.h" /* Prepare, run and cleanup for mysql_recreate_table() */ static bool admin_recreate_table(THD *thd, TABLE_LIST *table_list) @@ -90,10 +90,10 @@ static int send_check_errmsg(THD *thd, TABLE_LIST* table, static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, HA_CHECK_OPT *check_opt) { - int error= 0; + int error= 0, create_error= 0; TABLE tmp_table, *table; TABLE_LIST *pos_in_locked_tables= 0; - TABLE_SHARE *share; + TABLE_SHARE *share= 0; bool has_mdl_lock= FALSE; char from[FN_REFLEN],tmp[FN_REFLEN+32]; const char **ext; @@ -206,6 +206,17 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, HA_EXTRA_NOT_USED, NULL); table_list->table= 0; } + else + { + /* + Table open failed, maybe because we run out of memory. + Close all open tables and relaese all MDL locks + */ + tdc_release_share(share); + share->tdc->flush(thd, true); + share= 0; + } + /* After this point we have an exclusive metadata lock on our table in both cases when table was successfully open in mysql_admin_table() @@ -219,11 +230,8 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, goto end; } if (dd_recreate_table(thd, table_list->db.str, table_list->table_name.str)) - { - error= send_check_errmsg(thd, table_list, "repair", - "Failed generating table from .frm file"); - goto end; - } + create_error= send_check_errmsg(thd, table_list, "repair", + "Failed generating table from .frm file"); /* 'FALSE' for 'using_transactions' means don't postpone invalidation till the end of a transaction, but do it @@ -236,6 +244,8 @@ static int prepare_for_repair(THD *thd, TABLE_LIST *table_list, "Failed restoring .MYD file"); goto end; } + if (create_error) + goto end; if (thd->locked_tables_list.locked_tables()) { @@ -263,7 +273,8 @@ end: if (table == &tmp_table) { closefrm(table); - tdc_release_share(table->s); + if (share) + tdc_release_share(share); } /* In case of a temporary table there will be no metadata lock. */ if (unlikely(error) && has_mdl_lock) @@ -418,6 +429,50 @@ dbug_err: return open_error; } +#ifdef WITH_WSREP + /* + OPTIMIZE, REPAIR and ALTER may take MDL locks not only for the affected table, but + also for the table referenced by foreign key constraint. + This wsrep_toi_replication() function handles TOI replication for OPTIMIZE and REPAIR + so that certification keys for potential FK parent tables are also appended in the + write set. + ALTER TABLE case is handled elsewhere. + */ +static bool wsrep_toi_replication(THD *thd, TABLE_LIST *tables) +{ + if (!WSREP(thd) || !WSREP_CLIENT(thd)) return false; + + LEX *lex= thd->lex; + /* only handle OPTIMIZE and REPAIR here */ + switch (lex->sql_command) + { + case SQLCOM_OPTIMIZE: + case SQLCOM_REPAIR: + break; + default: + return false; + } + + close_thread_tables(thd); + wsrep::key_array keys; + + wsrep_append_fk_parent_table(thd, tables, &keys); + + /* now TOI replication, with no locks held */ + if (keys.empty()) + { + WSREP_TO_ISOLATION_BEGIN_WRTCHK(NULL, NULL, tables); + } else { + WSREP_TO_ISOLATION_BEGIN_FK_TABLES(NULL, NULL, tables, &keys) { + return true; + } + } + return false; + + wsrep_error_label: + return true; +} +#endif /* WITH_WSREP */ /* RETURN VALUES @@ -486,6 +541,13 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, close_thread_tables(thd); for (table= tables; table; table= table->next_local) table->table= NULL; +#ifdef WITH_WSREP + if (wsrep_toi_replication(thd, tables)) + { + WSREP_INFO("wsrep TOI replication of has failed, skipping OPTIMIZE"); + goto err; + } +#endif /* WITH_WSREP */ for (table= tables; table; table= table->next_local) { @@ -592,6 +654,12 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, #endif DBUG_PRINT("admin", ("table: %p", table->table)); + if (table->schema_table) + { + result_code= HA_ADMIN_NOT_IMPLEMENTED; + goto send_result; + } + if (prepare_func) { DBUG_PRINT("admin", ("calling prepare_func")); @@ -650,12 +718,6 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables, goto send_result; } - if (table->schema_table) - { - result_code= HA_ADMIN_NOT_IMPLEMENTED; - goto send_result; - } - if ((table->table->db_stat & HA_READ_ONLY) && open_for_modify) { /* purecov: begin inspected */ @@ -1338,10 +1400,10 @@ bool Sql_cmd_analyze_table::execute(THD *thd) m_lex->first_select_lex()->table_list.first= first_table; m_lex->query_tables= first_table; -error: #ifdef WITH_WSREP -wsrep_error_label: -#endif + wsrep_error_label: +#endif /* WITH_WSREP */ +error: DBUG_RETURN(res); } @@ -1380,7 +1442,6 @@ bool Sql_cmd_optimize_table::execute(THD *thd) if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table, FALSE, UINT_MAX, FALSE)) goto error; /* purecov: inspected */ - WSREP_TO_ISOLATION_BEGIN_WRTCHK(NULL, NULL, first_table); res= (specialflag & SPECIAL_NO_NEW_FUNC) ? mysql_recreate_table(thd, first_table, true) : @@ -1399,9 +1460,6 @@ bool Sql_cmd_optimize_table::execute(THD *thd) m_lex->query_tables= first_table; error: -#ifdef WITH_WSREP -wsrep_error_label: -#endif DBUG_RETURN(res); } @@ -1416,7 +1474,6 @@ bool Sql_cmd_repair_table::execute(THD *thd) if (check_table_access(thd, SELECT_ACL | INSERT_ACL, first_table, FALSE, UINT_MAX, FALSE)) goto error; /* purecov: inspected */ - WSREP_TO_ISOLATION_BEGIN_WRTCHK(NULL, NULL, first_table); res= mysql_admin_table(thd, first_table, &m_lex->check_opt, "repair", TL_WRITE, 1, MY_TEST(m_lex->check_opt.sql_flags & TT_USEFRM), @@ -1435,8 +1492,5 @@ bool Sql_cmd_repair_table::execute(THD *thd) m_lex->query_tables= first_table; error: -#ifdef WITH_WSREP -wsrep_error_label: -#endif DBUG_RETURN(res); } diff --git a/sql/sql_alter.cc b/sql/sql_alter.cc index ede30263e48..0184f6beaf4 100644 --- a/sql/sql_alter.cc +++ b/sql/sql_alter.cc @@ -490,6 +490,25 @@ bool Sql_cmd_alter_table::execute(THD *thd) if (check_grant(thd, priv_needed, first_table, FALSE, UINT_MAX, FALSE)) DBUG_RETURN(TRUE); /* purecov: inspected */ +#ifdef WITH_WSREP + if (WSREP(thd) && WSREP_CLIENT(thd) && + (!thd->is_current_stmt_binlog_format_row() || + !thd->find_temporary_table(first_table))) + { + wsrep::key_array keys; + wsrep_append_fk_parent_table(thd, first_table, &keys); + + WSREP_TO_ISOLATION_BEGIN_ALTER(lex->name.str ? select_lex->db.str + : first_table->db.str, + lex->name.str ? lex->name.str + : first_table->table_name.str, + first_table, &alter_info, &keys, + used_engine ? &create_info : nullptr); + + thd->variables.auto_increment_offset = 1; + thd->variables.auto_increment_increment = 1; + } +#endif if (lex->name.str && !test_all_bits(priv, INSERT_ACL | CREATE_ACL)) { @@ -517,20 +536,6 @@ bool Sql_cmd_alter_table::execute(THD *thd) thd->work_part_info= 0; #endif -#ifdef WITH_WSREP - if (WSREP(thd) && - (!thd->is_current_stmt_binlog_format_row() || - !thd->find_temporary_table(first_table))) - { - WSREP_TO_ISOLATION_BEGIN_ALTER((lex->name.str ? select_lex->db.str : first_table->db.str), - (lex->name.str ? lex->name.str : first_table->table_name.str), - first_table, &alter_info, used_engine ? &create_info : NULL); - - thd->variables.auto_increment_offset = 1; - thd->variables.auto_increment_increment = 1; - } -#endif - result= mysql_alter_table(thd, &select_lex->db, &lex->name, &create_info, first_table, diff --git a/sql/sql_class.h b/sql/sql_class.h index 87773b8338a..e3e3b18472b 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -3448,7 +3448,7 @@ public: void awake_no_mutex(killed_state state_to_set); void awake(killed_state state_to_set) { - bool wsrep_on_local= WSREP_NNULL(this); + bool wsrep_on_local= variables.wsrep_on; /* mutex locking order (LOCK_thd_data - LOCK_thd_kill)) requires to grab LOCK_thd_data here diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 3e59ad3afc9..861eb614592 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -4469,6 +4469,12 @@ public: int add_period(Lex_ident name, Lex_ident_sys_st start, Lex_ident_sys_st end) { + if (lex_string_cmp(system_charset_info, &start, &end) == 0) + { + my_error(ER_FIELD_SPECIFIED_TWICE, MYF(0), start.str); + return 1; + } + Table_period_info &info= create_info.period_info; if (check_exists && info.name.streq(name)) diff --git a/sql/sql_truncate.cc b/sql/sql_truncate.cc index 8ed996c0a8d..5e89c4d19ee 100644 --- a/sql/sql_truncate.cc +++ b/sql/sql_truncate.cc @@ -427,9 +427,23 @@ bool Sql_cmd_truncate_table::truncate_table(THD *thd, TABLE_LIST *table_ref) bool hton_can_recreate; #ifdef WITH_WSREP - if (WSREP(thd) && - wsrep_to_isolation_begin(thd, table_ref->db.str, table_ref->table_name.str, NULL)) - DBUG_RETURN(TRUE); + if (WSREP(thd)) + { + wsrep::key_array keys; + wsrep_append_fk_parent_table(thd, table_ref, &keys); + if (keys.empty()) + { + WSREP_TO_ISOLATION_BEGIN_IF(table_ref->db.str, table_ref->table_name.str, NULL) + { + DBUG_RETURN(TRUE); + } + } else { + WSREP_TO_ISOLATION_BEGIN_FK_TABLES(NULL, NULL, table_ref, &keys) + { + DBUG_RETURN(TRUE); + } + } + } #endif /* WITH_WSREP */ if (lock_table(thd, table_ref, &hton_can_recreate)) diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 1b37896f18b..607a68caf43 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -2501,7 +2501,7 @@ create: Lex->pop_select(); //main select } | create_or_replace USER_SYM opt_if_not_exists clear_privileges - grant_list opt_require_clause opt_resource_options opt_account_locking opt_password_expiration + grant_list opt_require_clause opt_resource_options opt_account_locking_and_opt_password_expiration { if (unlikely(Lex->set_command_with_check(SQLCOM_CREATE_USER, $1 | $3))) @@ -7312,7 +7312,7 @@ alter: } OPTIONS_SYM '(' server_options_list ')' { } /* ALTER USER foo is allowed for MySQL compatibility. */ | ALTER USER_SYM opt_if_exists clear_privileges grant_list - opt_require_clause opt_resource_options opt_account_locking opt_password_expiration + opt_require_clause opt_resource_options opt_account_locking_and_opt_password_expiration { Lex->create_info.set($3); Lex->sql_command= SQLCOM_ALTER_USER; @@ -7348,39 +7348,46 @@ alter: } stmt_end {} ; -opt_account_locking: - /* Nothing */ {} - | ACCOUNT_SYM LOCK_SYM +account_locking_option: + LOCK_SYM { Lex->account_options.account_locked= ACCOUNTLOCK_LOCKED; } - | ACCOUNT_SYM UNLOCK_SYM + | UNLOCK_SYM { Lex->account_options.account_locked= ACCOUNTLOCK_UNLOCKED; } ; -opt_password_expiration: - /* Nothing */ {} - | PASSWORD_SYM EXPIRE_SYM + +opt_password_expire_option: + /* empty */ { Lex->account_options.password_expire= PASSWORD_EXPIRE_NOW; } - | PASSWORD_SYM EXPIRE_SYM NEVER_SYM + | NEVER_SYM { Lex->account_options.password_expire= PASSWORD_EXPIRE_NEVER; } - | PASSWORD_SYM EXPIRE_SYM DEFAULT + | DEFAULT { Lex->account_options.password_expire= PASSWORD_EXPIRE_DEFAULT; } - | PASSWORD_SYM EXPIRE_SYM INTERVAL_SYM NUM DAY_SYM + | INTERVAL_SYM NUM DAY_SYM { Lex->account_options.password_expire= PASSWORD_EXPIRE_INTERVAL; - if (!(Lex->account_options.num_expiration_days= atoi($4.str))) - my_yyabort_error((ER_WRONG_VALUE, MYF(0), "DAY", $4.str)); + if (!(Lex->account_options.num_expiration_days= atoi($2.str))) + my_yyabort_error((ER_WRONG_VALUE, MYF(0), "DAY", $2.str)); } ; +opt_account_locking_and_opt_password_expiration: + /* empty */ + | ACCOUNT_SYM account_locking_option + | PASSWORD_SYM EXPIRE_SYM opt_password_expire_option + | ACCOUNT_SYM account_locking_option PASSWORD_SYM EXPIRE_SYM opt_password_expire_option + | PASSWORD_SYM EXPIRE_SYM opt_password_expire_option ACCOUNT_SYM account_locking_option + ; + ev_alter_on_schedule_completion: /* empty */ { $$= 0;} | ON SCHEDULE_SYM ev_schedule_time { $$= 1; } diff --git a/sql/wsrep_client_service.cc b/sql/wsrep_client_service.cc index b9c59623e6c..c01c256e872 100644 --- a/sql/wsrep_client_service.cc +++ b/sql/wsrep_client_service.cc @@ -245,6 +245,16 @@ void Wsrep_client_service::will_replay() mysql_mutex_unlock(&LOCK_wsrep_replaying); } +void Wsrep_client_service::signal_replayed() +{ + DBUG_ASSERT(m_thd == current_thd); + mysql_mutex_lock(&LOCK_wsrep_replaying); + --wsrep_replaying; + DBUG_ASSERT(wsrep_replaying >= 0); + mysql_cond_broadcast(&COND_wsrep_replaying); + mysql_mutex_unlock(&LOCK_wsrep_replaying); +} + enum wsrep::provider::status Wsrep_client_service::replay() { @@ -273,14 +283,15 @@ enum wsrep::provider::status Wsrep_client_service::replay() } delete replayer_thd; - - mysql_mutex_lock(&LOCK_wsrep_replaying); - --wsrep_replaying; - mysql_cond_broadcast(&COND_wsrep_replaying); - mysql_mutex_unlock(&LOCK_wsrep_replaying); DBUG_RETURN(ret); } +enum wsrep::provider::status Wsrep_client_service::replay_unordered() +{ + DBUG_ASSERT(0); + return wsrep::provider::error_not_implemented; +} + void Wsrep_client_service::wait_for_replayers(wsrep::unique_lock<wsrep::mutex>& lock) { DBUG_ASSERT(m_thd == current_thd); @@ -300,6 +311,12 @@ void Wsrep_client_service::wait_for_replayers(wsrep::unique_lock<wsrep::mutex>& lock.lock(); } +enum wsrep::provider::status Wsrep_client_service::commit_by_xid() +{ + DBUG_ASSERT(0); + return wsrep::provider::error_not_implemented; +} + void Wsrep_client_service::debug_sync(const char* sync_point) { DBUG_ASSERT(m_thd == current_thd); diff --git a/sql/wsrep_client_service.h b/sql/wsrep_client_service.h index aa58d99c3cf..253d2f43ac3 100644 --- a/sql/wsrep_client_service.h +++ b/sql/wsrep_client_service.h @@ -48,8 +48,19 @@ public: void emergency_shutdown() { throw wsrep::not_implemented_error(); } void will_replay(); + void signal_replayed(); enum wsrep::provider::status replay(); + enum wsrep::provider::status replay_unordered(); void wait_for_replayers(wsrep::unique_lock<wsrep::mutex>&); + enum wsrep::provider::status commit_by_xid(); + bool is_explicit_xa() + { + return false; + } + bool is_xa_rollback() + { + return false; + } void debug_sync(const char*); void debug_crash(const char*); int bf_rollback(); diff --git a/sql/wsrep_mysqld.cc b/sql/wsrep_mysqld.cc index a6b0991e82b..1dfeb782bbe 100644 --- a/sql/wsrep_mysqld.cc +++ b/sql/wsrep_mysqld.cc @@ -1261,6 +1261,51 @@ void wsrep_keys_free(wsrep_key_arr_t* key_arr) key_arr->keys_len= 0; } +void +wsrep_append_fk_parent_table(THD* thd, TABLE_LIST* tables, wsrep::key_array* keys) +{ + if (!WSREP(thd) || !WSREP_CLIENT(thd)) return; + TABLE_LIST *table; + + thd->mdl_context.release_transactional_locks(); + uint counter; + MDL_savepoint mdl_savepoint= thd->mdl_context.mdl_savepoint(); + + if (thd->open_temporary_tables(tables) || + open_tables(thd, &tables, &counter, MYSQL_OPEN_FORCE_SHARED_HIGH_PRIO_MDL)) + { + WSREP_DEBUG("unable to open table for FK checks for %s", thd->query()); + } + + for (table= tables; table; table= table->next_local) + { + if (!is_temporary_table(table) && table->table) + { + FOREIGN_KEY_INFO *f_key_info; + List<FOREIGN_KEY_INFO> f_key_list; + + table->table->file->get_foreign_key_list(thd, &f_key_list); + List_iterator_fast<FOREIGN_KEY_INFO> it(f_key_list); + while ((f_key_info=it++)) + { + WSREP_DEBUG("appended fkey %s", f_key_info->referenced_table->str); + keys->push_back(wsrep_prepare_key_for_toi(f_key_info->referenced_db->str, + f_key_info->referenced_table->str, + wsrep::key::shared)); + } + } + } + + /* close the table and release MDL locks */ + close_thread_tables(thd); + thd->mdl_context.rollback_to_savepoint(mdl_savepoint); + for (table= tables; table; table= table->next_local) + { + table->table= NULL; + table->mdl_request.ticket= NULL; + } +} + /*! * @param db Database string * @param table Table string @@ -1511,10 +1556,11 @@ wsrep_prepare_keys_for_alter_add_fk(const char* child_table_db, return ret; } -wsrep::key_array wsrep_prepare_keys_for_toi(const char* db, - const char* table, - const TABLE_LIST* table_list, - const Alter_info* alter_info) +wsrep::key_array wsrep_prepare_keys_for_toi(const char *db, + const char *table, + const TABLE_LIST *table_list, + const Alter_info *alter_info, + const wsrep::key_array *fk_tables) { wsrep::key_array ret; if (db || table) @@ -1534,8 +1580,13 @@ wsrep::key_array wsrep_prepare_keys_for_toi(const char* db, ret.insert(ret.end(), fk.begin(), fk.end()); } } + if (fk_tables && !fk_tables->empty()) + { + ret.insert(ret.end(), fk_tables->begin(), fk_tables->end()); + } return ret; } + /* * Construct Query_log_Event from thd query and serialize it * into buffer. @@ -2069,9 +2120,10 @@ fail: -1: TOI replication failed */ static int wsrep_TOI_begin(THD *thd, const char *db, const char *table, - const TABLE_LIST* table_list, - const Alter_info* alter_info, - const HA_CREATE_INFO* create_info) + const TABLE_LIST *table_list, + const Alter_info *alter_info, + const wsrep::key_array *fk_tables, + const HA_CREATE_INFO *create_info) { DBUG_ASSERT(wsrep_OSU_method_get(thd) == WSREP_OSU_TOI); @@ -2102,7 +2154,7 @@ static int wsrep_TOI_begin(THD *thd, const char *db, const char *table, struct wsrep_buf buff= { buf, buf_len }; wsrep::key_array key_array= - wsrep_prepare_keys_for_toi(db, table, table_list, alter_info); + wsrep_prepare_keys_for_toi(db, table, table_list, alter_info, fk_tables); if (thd->has_read_only_protection()) { @@ -2250,8 +2302,9 @@ static void wsrep_RSU_end(THD *thd) int wsrep_to_isolation_begin(THD *thd, const char *db_, const char *table_, const TABLE_LIST* table_list, - const Alter_info* alter_info, - const HA_CREATE_INFO* create_info) + const Alter_info *alter_info, + const wsrep::key_array *fk_tables, + const HA_CREATE_INFO *create_info) { /* No isolation for applier or replaying threads. @@ -2305,7 +2358,8 @@ int wsrep_to_isolation_begin(THD *thd, const char *db_, const char *table_, { switch (wsrep_OSU_method_get(thd)) { case WSREP_OSU_TOI: - ret= wsrep_TOI_begin(thd, db_, table_, table_list, alter_info, create_info); + ret= wsrep_TOI_begin(thd, db_, table_, table_list, alter_info, fk_tables, + create_info); break; case WSREP_OSU_RSU: ret= wsrep_RSU_begin(thd, db_, table_); diff --git a/sql/wsrep_mysqld.h b/sql/wsrep_mysqld.h index 4461d73a928..eb32636d67f 100644 --- a/sql/wsrep_mysqld.h +++ b/sql/wsrep_mysqld.h @@ -215,6 +215,7 @@ wsrep_sync_wait_upto (THD* thd, wsrep_gtid_t* upto, int timeout); extern void wsrep_last_committed_id (wsrep_gtid_t* gtid); extern int wsrep_check_opts(); extern void wsrep_prepend_PATH (const char* path); +void wsrep_append_fk_parent_table(THD* thd, TABLE_LIST* table, wsrep::key_array* keys); /* Other global variables */ extern wsrep_seqno_t wsrep_locked_seqno; @@ -362,8 +363,9 @@ struct HA_CREATE_INFO; int wsrep_to_isolation_begin(THD *thd, const char *db_, const char *table_, const TABLE_LIST* table_list, - const Alter_info* alter_info= NULL, - const HA_CREATE_INFO* create_info= NULL); + const Alter_info* alter_info= nullptr, + const wsrep::key_array *fk_tables= nullptr, + const HA_CREATE_INFO* create_info= nullptr); bool wsrep_should_replicate_ddl(THD* thd, const enum legacy_db_type db_type); bool wsrep_should_replicate_ddl_iterate(THD* thd, const TABLE_LIST* table_list); @@ -607,6 +609,9 @@ void wsrep_deinit_server(); */ enum wsrep::streaming_context::fragment_unit wsrep_fragment_unit(ulong unit); +wsrep::key wsrep_prepare_key_for_toi(const char* db, const char* table, + enum wsrep::key::type type); + #else /* !WITH_WSREP */ /* These macros are needed to compile MariaDB without WSREP support diff --git a/storage/innobase/buf/buf0buf.cc b/storage/innobase/buf/buf0buf.cc index 9a180614afd..3bbc499bab5 100644 --- a/storage/innobase/buf/buf0buf.cc +++ b/storage/innobase/buf/buf0buf.cc @@ -3483,7 +3483,7 @@ re_evict: if (fix_block->page.ibuf_exist) { fix_block->page.ibuf_exist = false; ibuf_merge_or_delete_for_page(fix_block, page_id, - zip_size, true); + zip_size); } if (rw_latch == RW_X_LATCH) { @@ -3550,7 +3550,7 @@ buf_page_get_gen( { rw_lock_x_lock_inline(&block->lock, 0, file, line); block->page.ibuf_exist= false; - ibuf_merge_or_delete_for_page(block, page_id, block->zip_size(), true); + ibuf_merge_or_delete_for_page(block, page_id, block->zip_size()); if (rw_latch == RW_X_LATCH) { @@ -3823,7 +3823,7 @@ loop: if (block->page.ibuf_exist) { if (!recv_recovery_is_on()) - ibuf_merge_or_delete_for_page(nullptr, page_id, zip_size, true); + ibuf_merge_or_delete_for_page(nullptr, page_id, zip_size); block->page.ibuf_exist= false; } @@ -3885,7 +3885,7 @@ loop: /* Delete possible entries for the page from the insert buffer: such can exist if the page belonged to an index which was dropped */ if (!recv_recovery_is_on()) - ibuf_merge_or_delete_for_page(nullptr, page_id, zip_size, true); + ibuf_merge_or_delete_for_page(nullptr, page_id, zip_size); static_assert(FIL_PAGE_PREV + 4 == FIL_PAGE_NEXT, "adjacent"); memset_aligned<8>(block->frame + FIL_PAGE_PREV, 0xff, 8); diff --git a/storage/innobase/dict/dict0dict.cc b/storage/innobase/dict/dict0dict.cc index ca4081e90e9..168636cc965 100644 --- a/storage/innobase/dict/dict0dict.cc +++ b/storage/innobase/dict/dict0dict.cc @@ -5268,87 +5268,3 @@ dict_tf_to_row_format_string( ut_error; return(0); } - -/** Look for any dictionary objects that are found in the given tablespace. -@param[in] space_id Tablespace ID to search for. -@return true if tablespace is empty. */ -bool -dict_space_is_empty( - ulint space_id) -{ - btr_pcur_t pcur; - const rec_t* rec; - mtr_t mtr; - bool found = false; - - dict_sys_lock(); - mtr_start(&mtr); - - for (rec = dict_startscan_system(&pcur, &mtr, SYS_TABLES); - rec != NULL; - rec = dict_getnext_system(&pcur, &mtr)) { - const byte* field; - ulint len; - ulint space_id_for_table; - - field = rec_get_nth_field_old( - rec, DICT_FLD__SYS_TABLES__SPACE, &len); - ut_ad(len == 4); - space_id_for_table = mach_read_from_4(field); - - if (space_id_for_table == space_id) { - found = true; - } - } - - mtr_commit(&mtr); - dict_sys_unlock(); - - return(!found); -} - -/** Find the space_id for the given name in sys_tablespaces. -@param[in] name Tablespace name to search for. -@return the tablespace ID. */ -ulint -dict_space_get_id( - const char* name) -{ - btr_pcur_t pcur; - const rec_t* rec; - mtr_t mtr; - ulint name_len = strlen(name); - ulint id = ULINT_UNDEFINED; - - dict_sys_lock(); - mtr_start(&mtr); - - for (rec = dict_startscan_system(&pcur, &mtr, SYS_TABLESPACES); - rec != NULL; - rec = dict_getnext_system(&pcur, &mtr)) { - const byte* field; - ulint len; - - field = rec_get_nth_field_old( - rec, DICT_FLD__SYS_TABLESPACES__NAME, &len); - ut_ad(len > 0); - ut_ad(len < OS_FILE_MAX_PATH); - - if (len == name_len && !memcmp(name, field, len)) { - field = rec_get_nth_field_old( - rec, DICT_FLD__SYS_TABLESPACES__SPACE, &len); - ut_ad(len == 4); - id = mach_read_from_4(field); - - /* This is normally called by dict_getnext_system() - at the end of the index. */ - btr_pcur_close(&pcur); - break; - } - } - - mtr_commit(&mtr); - dict_sys_unlock(); - - return(id); -} diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index ccd46dd88b6..6fd1e99023a 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -541,6 +541,7 @@ static PSI_mutex_info all_innodb_mutexes[] = { PSI_KEY(ibuf_bitmap_mutex), PSI_KEY(ibuf_mutex), PSI_KEY(ibuf_pessimistic_insert_mutex), + PSI_KEY(index_online_log), PSI_KEY(log_sys_mutex), PSI_KEY(page_zip_stat_per_index_mutex), PSI_KEY(purge_sys_pq_mutex), @@ -587,7 +588,6 @@ static PSI_rwlock_info all_innodb_rwlocks[] = { PSI_RWLOCK_KEY(trx_i_s_cache_lock), PSI_RWLOCK_KEY(trx_purge_latch), PSI_RWLOCK_KEY(index_tree_rw_lock), - PSI_RWLOCK_KEY(index_online_log), }; # endif /* UNIV_PFS_RWLOCK */ @@ -15264,24 +15264,6 @@ struct table_list_item { const char* name; }; -/** Structure to compare two st_tablename objects using their -db and tablename. It is used in the ordering of cascade_fk_set. -It returns true if the first argument precedes the second argument -and false otherwise. */ -struct tablename_compare { - - bool operator()(const st_handler_tablename lhs, - const st_handler_tablename rhs) const - { - int cmp = strcmp(lhs.db, rhs.db); - if (cmp == 0) { - cmp = strcmp(lhs.tablename, rhs.tablename); - } - - return(cmp < 0); - } -}; - /*****************************************************************//** Checks if ALTER TABLE may change the storage engine of the table. Changing storage engines is not allowed for tables for which there diff --git a/storage/innobase/handler/ha_innodb.h b/storage/innobase/handler/ha_innodb.h index 2e06272e43a..c5c5e8cba31 100644 --- a/storage/innobase/handler/ha_innodb.h +++ b/storage/innobase/handler/ha_innodb.h @@ -51,12 +51,7 @@ struct ha_table_option_struct uint encryption; /*!< DEFAULT, ON, OFF */ ulonglong encryption_key_id; /*!< encryption key id */ }; -/* JAN: TODO: MySQL 5.7 handler.h */ -struct st_handler_tablename -{ - const char *db; - const char *tablename; -}; + /** The class defining a handle to an Innodb table */ class ha_innobase final : public handler { diff --git a/storage/innobase/ibuf/ibuf0ibuf.cc b/storage/innobase/ibuf/ibuf0ibuf.cc index 91a450b0711..0d81f81135c 100644 --- a/storage/innobase/ibuf/ibuf0ibuf.cc +++ b/storage/innobase/ibuf/ibuf0ibuf.cc @@ -4170,19 +4170,11 @@ insert buffer. If the page is not read, but created in the buffer pool, this function deletes its buffered entries from the insert buffer; there can exist entries for such a page if the page belonged to an index which subsequently was dropped. -@param[in,out] block if page has been read from disk, -pointer to the page x-latched, else NULL -@param[in] page_id page id of the index page -@param[in] zip_size ROW_FORMAT=COMPRESSED page size, or 0 -@param[in] update_ibuf_bitmap normally this is set, but -if we have deleted or are deleting the tablespace, then we naturally do not -want to update a non-existent bitmap page */ -void -ibuf_merge_or_delete_for_page( - buf_block_t* block, - const page_id_t page_id, - ulint zip_size, - bool update_ibuf_bitmap) +@param block X-latched page to try to apply changes to, or NULL to discard +@param page_id page identifier +@param zip_size ROW_FORMAT=COMPRESSED page size, or 0 */ +void ibuf_merge_or_delete_for_page(buf_block_t *block, const page_id_t page_id, + ulint zip_size) { btr_pcur_t pcur; #ifdef UNIV_IBUF_DEBUG @@ -4211,47 +4203,33 @@ ibuf_merge_or_delete_for_page( return; } - fil_space_t* space; - - if (update_ibuf_bitmap) { - space = fil_space_t::get(page_id.space()); - - if (UNIV_UNLIKELY(!space)) { - /* Do not try to read the bitmap page from the - non-existent tablespace, delete the ibuf records */ - block = NULL; - update_ibuf_bitmap = false; - } else { - ulint bitmap_bits = 0; + fil_space_t* space = fil_space_t::get(page_id.space()); - ibuf_mtr_start(&mtr); + if (UNIV_UNLIKELY(!space)) { + block = NULL; + } else { + ulint bitmap_bits = 0; - buf_block_t* bitmap_page = ibuf_bitmap_get_map_page( - page_id, zip_size, &mtr); + ibuf_mtr_start(&mtr); - if (bitmap_page - && fil_page_get_type(bitmap_page->frame) - != FIL_PAGE_TYPE_ALLOCATED) { - bitmap_bits = ibuf_bitmap_page_get_bits( - bitmap_page->frame, page_id, zip_size, - IBUF_BITMAP_BUFFERED, &mtr); - } + buf_block_t* bitmap_page = ibuf_bitmap_get_map_page( + page_id, zip_size, &mtr); + + if (bitmap_page + && fil_page_get_type(bitmap_page->frame) + != FIL_PAGE_TYPE_ALLOCATED) { + bitmap_bits = ibuf_bitmap_page_get_bits( + bitmap_page->frame, page_id, zip_size, + IBUF_BITMAP_BUFFERED, &mtr); + } - ibuf_mtr_commit(&mtr); + ibuf_mtr_commit(&mtr); - if (!bitmap_bits) { - /* No changes are buffered for this page. */ - space->release(); - return; - } + if (!bitmap_bits) { + /* No changes are buffered for this page. */ + space->release(); + return; } - } else if (block != NULL - && (ibuf_fixed_addr_page(page_id, physical_size) - || fsp_descr_page(page_id, physical_size))) { - - return; - } else { - space = NULL; } mem_heap_t* heap = mem_heap_create(512); @@ -4297,12 +4275,11 @@ loop: ibuf.index, search_tuple, PAGE_CUR_GE, BTR_MODIFY_LEAF, &pcur, &mtr); - if (block != NULL) { + if (block) { ut_ad(rw_lock_own(&block->lock, RW_LOCK_X)); buf_block_buf_fix_inc(block, __FILE__, __LINE__); rw_lock_x_lock(&block->lock); - mtr.set_named_space(space); mtr.memo_push(block, MTR_MEMO_PAGE_X_FIX); /* This is a user page (secondary index leaf page), but we pretend that it is a change buffer page in @@ -4311,7 +4288,9 @@ loop: the block is io-fixed. Other threads must not try to latch an io-fixed block. */ buf_block_dbg_add_level(block, SYNC_IBUF_TREE_NODE); - } else if (update_ibuf_bitmap) { + } + + if (space) { mtr.set_named_space(space); } @@ -4467,7 +4446,7 @@ loop: } reset_bit: - if (!update_ibuf_bitmap) { + if (!space) { } else if (buf_block_t* bitmap = ibuf_bitmap_get_map_page( page_id, zip_size, &mtr)) { /* FIXME: update the bitmap byte only once! */ diff --git a/storage/innobase/include/dict0dict.h b/storage/innobase/include/dict0dict.h index 276252e625c..f7c4d5dca29 100644 --- a/storage/innobase/include/dict0dict.h +++ b/storage/innobase/include/dict0dict.h @@ -910,19 +910,6 @@ inline ulint dict_tf_get_zip_size(ulint flags) : 0; } -/** Determine the extent size (in pages) for the given table -@param[in] table the table whose extent size is being - calculated. -@return extent size in pages (256, 128 or 64) */ -inline ulint dict_table_extent_size(const dict_table_t* table) -{ - if (ulint zip_size = table->space->zip_size()) { - return (1U << 20) / zip_size; - } - - return FSP_EXTENT_SIZE; -} - /********************************************************************//** Checks if a column is in the ordering columns of the clustered index of a table. Column prefixes are treated like whole columns. @@ -1803,20 +1790,6 @@ dict_table_decode_n_col( ulint* n_col, ulint* n_v_col); -/** Look for any dictionary objects that are found in the given tablespace. -@param[in] space_id Tablespace ID to search for. -@return true if tablespace is empty. */ -bool -dict_space_is_empty( - ulint space_id); - -/** Find the space_id for the given name in sys_tablespaces. -@param[in] name Tablespace name to search for. -@return the tablespace ID. */ -ulint -dict_space_get_id( - const char* name); - /** Free the virtual column template @param[in,out] vc_templ virtual column template */ UNIV_INLINE diff --git a/storage/innobase/include/ibuf0ibuf.h b/storage/innobase/include/ibuf0ibuf.h index 061f1bc4e8e..cb418e57532 100644 --- a/storage/innobase/include/ibuf0ibuf.h +++ b/storage/innobase/include/ibuf0ibuf.h @@ -329,19 +329,11 @@ insert buffer. If the page is not read, but created in the buffer pool, this function deletes its buffered entries from the insert buffer; there can exist entries for such a page if the page belonged to an index which subsequently was dropped. -@param[in,out] block if page has been read from disk, -pointer to the page x-latched, else NULL -@param[in] page_id page id of the index page -@param[in] zip_size ROW_FORMAT=COMPRESSED page size, or 0 -@param[in] update_ibuf_bitmap normally this is set, but -if we have deleted or are deleting the tablespace, then we naturally do not -want to update a non-existent bitmap page */ -void -ibuf_merge_or_delete_for_page( - buf_block_t* block, - const page_id_t page_id, - ulint zip_size, - bool update_ibuf_bitmap); +@param block X-latched page to try to apply changes to, or NULL to discard +@param page_id page identifier +@param zip_size ROW_FORMAT=COMPRESSED page size, or 0 */ +void ibuf_merge_or_delete_for_page(buf_block_t *block, const page_id_t page_id, + ulint zip_size); /** Delete all change buffer entries for a tablespace, in DISCARD TABLESPACE, IMPORT TABLESPACE, or crash recovery. diff --git a/storage/innobase/include/trx0trx.h b/storage/innobase/include/trx0trx.h index 2294025a1aa..2aa0ed1e0e3 100644 --- a/storage/innobase/include/trx0trx.h +++ b/storage/innobase/include/trx0trx.h @@ -985,20 +985,6 @@ public: /*------------------------------*/ char* detailed_error; /*!< detailed error message for last error, or empty. */ - /* Lock wait statistics */ - ulint n_rec_lock_waits; - /*!< Number of record lock waits, - might not be exactly correct. */ - ulint n_table_lock_waits; - /*!< Number of table lock waits, - might not be exactly correct. */ - ulint total_rec_lock_wait_time; - /*!< Total rec lock wait time up - to this moment. */ - ulint total_table_lock_wait_time; - /*!< Total table lock wait time - up to this moment. */ - rw_trx_hash_element_t *rw_trx_hash_element; LF_PINS *rw_trx_hash_pins; ulint magic_n; diff --git a/storage/innobase/os/os0file.cc b/storage/innobase/os/os0file.cc index 24134479d8e..ee7f09ec17f 100644 --- a/storage/innobase/os/os0file.cc +++ b/storage/innobase/os/os0file.cc @@ -4259,72 +4259,152 @@ os_file_set_umask(ulint umask) } #ifdef _WIN32 -static int win32_get_block_size(HANDLE volume_handle, const char *volume_name) + +/* Checks whether physical drive is on SSD.*/ +static bool is_drive_on_ssd(DWORD nr) { - STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR disk_alignment; - STORAGE_PROPERTY_QUERY storage_query; - DWORD tmp; - - memset(&storage_query, 0, sizeof(storage_query)); - storage_query.PropertyId = StorageAccessAlignmentProperty; - storage_query.QueryType = PropertyStandardQuery; - - if (os_win32_device_io_control(volume_handle, - IOCTL_STORAGE_QUERY_PROPERTY, - &storage_query, - sizeof storage_query, - &disk_alignment, - sizeof disk_alignment, - &tmp) && tmp == sizeof disk_alignment) { - return disk_alignment.BytesPerPhysicalSector; - } + char physical_drive_path[32]; + snprintf(physical_drive_path, sizeof(physical_drive_path), + "\\\\.\\PhysicalDrive%lu", nr); + + HANDLE h= CreateFile(physical_drive_path, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (h == INVALID_HANDLE_VALUE) + return false; - switch (GetLastError()) { - case ERROR_INVALID_FUNCTION: - case ERROR_NOT_SUPPORTED: - break; - default: - os_file_handle_error_no_exit( - volume_name, - "DeviceIoControl(IOCTL_STORAGE_QUERY_PROPERTY / StorageAccessAlignmentProperty)", - FALSE); - } - return 512; + DEVICE_SEEK_PENALTY_DESCRIPTOR seek_penalty; + STORAGE_PROPERTY_QUERY storage_query{}; + storage_query.PropertyId= StorageDeviceSeekPenaltyProperty; + storage_query.QueryType= PropertyStandardQuery; + + bool on_ssd= false; + DWORD bytes_written; + if (DeviceIoControl(h, IOCTL_STORAGE_QUERY_PROPERTY, &storage_query, + sizeof storage_query, &seek_penalty, sizeof seek_penalty, + &bytes_written, nullptr)) + { + on_ssd= seek_penalty.IncursSeekPenalty; + } + else + { + on_ssd= false; + } + CloseHandle(h); + return on_ssd; } -static bool win32_is_ssd(HANDLE volume_handle) +/* + Checks whether volume is on SSD, by checking all physical drives + in that volume. +*/ +static bool is_volume_on_ssd(const char *volume_mount_point) { - DWORD tmp; - DEVICE_SEEK_PENALTY_DESCRIPTOR seek_penalty; - STORAGE_PROPERTY_QUERY storage_query; - memset(&storage_query, 0, sizeof(storage_query)); - - storage_query.PropertyId = StorageDeviceSeekPenaltyProperty; - storage_query.QueryType = PropertyStandardQuery; - - if (os_win32_device_io_control(volume_handle, - IOCTL_STORAGE_QUERY_PROPERTY, - &storage_query, - sizeof storage_query, - &seek_penalty, - sizeof seek_penalty, - &tmp) && tmp == sizeof(seek_penalty)){ - return !seek_penalty.IncursSeekPenalty; + char volume_name[MAX_PATH]; + + if (!GetVolumeNameForVolumeMountPoint(volume_mount_point, volume_name, + array_elements(volume_name))) + { + /* This can fail, e.g if file is on network share */ + return false; } - DEVICE_TRIM_DESCRIPTOR trim; - storage_query.PropertyId = StorageDeviceTrimProperty; - if (os_win32_device_io_control(volume_handle, - IOCTL_STORAGE_QUERY_PROPERTY, - &storage_query, - sizeof storage_query, - &trim, - sizeof trim, - &tmp) && tmp == sizeof trim) { - return trim.TrimEnabled; + /* Chomp last backslash, this is needed to open volume.*/ + size_t length= strlen(volume_name); + if (length && volume_name[length - 1] == '\\') + volume_name[length - 1]= 0; + + /* Open volume handle */ + HANDLE volume_handle= CreateFile( + volume_name, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + + if (volume_handle == INVALID_HANDLE_VALUE) + return false; + + /* + Enumerate all volume extends, check whether all of them are on SSD + */ + + /* Anticipate common case where there is only one extent.*/ + VOLUME_DISK_EXTENTS single_extent; + + /* But also have a place to manage allocated data.*/ + std::unique_ptr<BYTE[]> lifetime; + + DWORD bytes_written; + VOLUME_DISK_EXTENTS *extents= nullptr; + if (DeviceIoControl(volume_handle, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, + nullptr, 0, &single_extent, sizeof(single_extent), + &bytes_written, nullptr)) + { + /* Worked on the first try. Use the preallocated buffer.*/ + extents= &single_extent; } - return false; + else + { + VOLUME_DISK_EXTENTS *last_query= &single_extent; + while (GetLastError() == ERROR_MORE_DATA) + { + DWORD extentCount= last_query->NumberOfDiskExtents; + DWORD allocatedSize= + FIELD_OFFSET(VOLUME_DISK_EXTENTS, Extents[extentCount]); + lifetime.reset(new BYTE[allocatedSize]); + last_query= (VOLUME_DISK_EXTENTS *) lifetime.get(); + if (DeviceIoControl(volume_handle, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, + nullptr, 0, last_query, allocatedSize, + &bytes_written, nullptr)) + { + extents= last_query; + break; + } + } + } + CloseHandle(volume_handle); + if (!extents) + return false; + + for (DWORD i= 0; i < extents->NumberOfDiskExtents; i++) + if (!is_drive_on_ssd(extents->Extents[i].DiskNumber)) + return false; + + return true; +} + +#include <unordered_map> +static bool is_file_on_ssd(char *file_path) +{ + /* Cache of volume_path => volume_info, protected by rwlock.*/ + static std::unordered_map<std::string, bool> cache; + static SRWLOCK lock= SRWLOCK_INIT; + + /* Preset result, in case something fails, e.g we're on network drive.*/ + char volume_path[MAX_PATH]; + if (!GetVolumePathName(file_path, volume_path, array_elements(volume_path))) + return false; + + /* Try cached volume info first.*/ + std::string volume_path_str(volume_path); + bool found; + bool result; + AcquireSRWLockShared(&lock); + auto e= cache.find(volume_path_str); + if ((found= e != cache.end())) + result= e->second; + ReleaseSRWLockShared(&lock); + + if (found) + return result; + + result= is_volume_on_ssd(volume_path); + + /* Update cache */ + AcquireSRWLockExclusive(&lock); + cache[volume_path_str]= result; + ReleaseSRWLockExclusive(&lock); + return result; } + #endif /** Determine some file metadata when creating or reading the file. @@ -4361,48 +4441,12 @@ void fil_node_t::find_metadata(os_file_t file space->atomic_write_supported = space->purpose == FIL_TYPE_TEMPORARY || space->purpose == FIL_TYPE_IMPORT; #ifdef _WIN32 - block_size = 512; - on_ssd = false; - // Open volume for this file, find out it "physical bytes per sector" - char volume[MAX_PATH + 4]; - if (!GetVolumePathName(name, volume + 4, MAX_PATH)) { - os_file_handle_error_no_exit(name, - "GetVolumePathName()", FALSE); - return; - } - // Special prefix required for volume names. - memcpy(volume, "\\\\.\\", 4); - - size_t len = strlen(volume); - if (volume[len - 1] == '\\') { - // Trim trailing backslash from volume name. - volume[len - 1] = 0; - } - - HANDLE volume_handle = CreateFile(volume, FILE_READ_ATTRIBUTES, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - 0, OPEN_EXISTING, 0, 0); - - if (volume_handle != INVALID_HANDLE_VALUE) { - block_size = win32_get_block_size(volume_handle, volume); - on_ssd = win32_is_ssd(volume_handle); - CloseHandle(volume_handle); + on_ssd = is_file_on_ssd(name); + FILE_STORAGE_INFO info; + if (GetFileInformationByHandleEx( + file, FileStorageInfo, &info, sizeof(info))) { + block_size = info.PhysicalBytesPerSectorForAtomicity; } else { - /* - Report error, unless it is expected, e.g - missing permissions, or error when trying to - open volume for UNC share. - */ - if (GetLastError() != ERROR_ACCESS_DENIED - && GetDriveType(volume) == DRIVE_FIXED) { - os_file_handle_error_no_exit(volume, "CreateFile()", FALSE); - } - } - - /* Currently we support file block size up to 4KiB */ - if (block_size > 4096) { - block_size = 4096; - } else if (block_size < 512) { block_size = 512; } #else diff --git a/storage/innobase/trx/trx0rec.cc b/storage/innobase/trx/trx0rec.cc index 1b1cabb6963..49b0a21330d 100644 --- a/storage/innobase/trx/trx0rec.cc +++ b/storage/innobase/trx/trx0rec.cc @@ -59,12 +59,12 @@ const dtuple_t trx_undo_metadata = { static ulint trx_undo_left(const buf_block_t *undo_block, const byte *ptr) { ut_ad(ptr >= &undo_block->frame[TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_HDR_SIZE]); - ut_ad(ptr <= &undo_block->frame[srv_page_size - 10 - FIL_PAGE_DATA_END]); - /* The 10 is supposed to be an extra safety margin (and needed for compatibility with older versions) */ - return srv_page_size - ulint(ptr - undo_block->frame) - + lint left= srv_page_size - (ptr - undo_block->frame) - (10 + FIL_PAGE_DATA_END); + ut_ad(left >= 0); + return left < 0 ? 0 : static_cast<ulint>(left); } /**********************************************************************//** diff --git a/storage/innobase/trx/trx0trx.cc b/storage/innobase/trx/trx0trx.cc index 074667a22f9..bd54af76d9d 100644 --- a/storage/innobase/trx/trx0trx.cc +++ b/storage/innobase/trx/trx0trx.cc @@ -460,10 +460,6 @@ void trx_t::free() MEM_NOACCESS(&xid, sizeof xid); MEM_NOACCESS(&mod_tables, sizeof mod_tables); MEM_NOACCESS(&detailed_error, sizeof detailed_error); - MEM_NOACCESS(&n_rec_lock_waits, sizeof n_rec_lock_waits); - MEM_NOACCESS(&n_table_lock_waits, sizeof n_table_lock_waits); - MEM_NOACCESS(&total_rec_lock_wait_time, sizeof total_rec_lock_wait_time); - MEM_NOACCESS(&total_table_lock_wait_time, sizeof total_table_lock_wait_time); MEM_NOACCESS(&magic_n, sizeof magic_n); trx_pools->mem_free(this); } diff --git a/storage/maria/ma_check.c b/storage/maria/ma_check.c index 2b390c6c267..a0c755b3a7d 100644 --- a/storage/maria/ma_check.c +++ b/storage/maria/ma_check.c @@ -2360,6 +2360,14 @@ static int initialize_variables_for_repair(HA_CHECK *param, { MARIA_SHARE *share= info->s; + /* + We have to clear these variables first, as the cleanup-in-case-of-error + handling may touch these. + */ + bzero((char*) sort_info, sizeof(*sort_info)); + bzero((char*) sort_param, sizeof(*sort_param)); + bzero(&info->rec_cache, sizeof(info->rec_cache)); + if (share->data_file_type == NO_RECORD) { _ma_check_print_error(param, @@ -2374,9 +2382,6 @@ static int initialize_variables_for_repair(HA_CHECK *param, if (share->lock.update_status) (*share->lock.update_status)(info->lock.status_param); - bzero((char*) sort_info, sizeof(*sort_info)); - bzero((char*) sort_param, sizeof(*sort_param)); - param->testflag|= T_REP; /* for easy checking */ if (share->options & (HA_OPTION_CHECKSUM | HA_OPTION_COMPRESS_RECORD)) param->testflag|= T_CALC_CHECKSUM; @@ -2404,7 +2409,6 @@ static int initialize_variables_for_repair(HA_CHECK *param, set_data_file_type(sort_info, info->s); sort_info->org_data_file_type= share->data_file_type; - bzero(&info->rec_cache, sizeof(info->rec_cache)); info->rec_cache.file= info->dfile.file; info->update= (short) (HA_STATE_CHANGED | HA_STATE_ROW_CHANGED); @@ -2891,9 +2895,13 @@ err: _ma_reset_state(info); end_io_cache(¶m->read_cache); - end_io_cache(&sort_info.new_info->rec_cache); + if (sort_info.new_info) + { + end_io_cache(&sort_info.new_info->rec_cache); + sort_info.new_info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); + } info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); - sort_info.new_info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); + sort_param.sort_info->info->in_check_table= 0; /* this below could fail, shouldn't we detect error? */ if (got_error) @@ -4110,10 +4118,13 @@ err: maria_scan_end(sort_info.info); _ma_reset_state(info); - end_io_cache(&sort_info.new_info->rec_cache); + if (sort_info.new_info) + { + end_io_cache(&sort_info.new_info->rec_cache); + sort_info.new_info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); + } end_io_cache(¶m->read_cache); info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); - sort_info.new_info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); if (got_error) { if (! param->error_printed) @@ -4642,10 +4653,13 @@ err: the share by remove_io_thread() or it was not yet started (if the error happend before creating the thread). */ - end_io_cache(&sort_info.new_info->rec_cache); + if (sort_info.new_info) + { + end_io_cache(&sort_info.new_info->rec_cache); + sort_info.new_info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); + } end_io_cache(¶m->read_cache); info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); - sort_info.new_info->opt_flag&= ~(READ_CACHE_USED | WRITE_CACHE_USED); /* Destroy the new data cache in case of non-quick repair. All slave threads did either detach from the share by remove_io_thread() diff --git a/storage/maria/ma_delete_table.c b/storage/maria/ma_delete_table.c index 90e6b5250c1..fa93a7100b0 100644 --- a/storage/maria/ma_delete_table.c +++ b/storage/maria/ma_delete_table.c @@ -30,6 +30,7 @@ int maria_delete_table(const char *name) { MARIA_HA *info; myf sync_dir; + int got_error= 0, error; DBUG_ENTER("maria_delete_table"); #ifdef EXTRA_DEBUG @@ -41,9 +42,13 @@ int maria_delete_table(const char *name) Unfortunately it is necessary to open the table just to check this. We use 'open_for_repair' to be able to open even a crashed table. */ + my_errno= 0; if (!(info= maria_open(name, O_RDONLY, HA_OPEN_FOR_REPAIR, 0))) { sync_dir= 0; + /* Ignore not found errors and wrong symlink errors */ + if (my_errno != ENOENT && my_errno != HA_WRONG_CREATE_OPTION) + got_error= my_errno;; } else { @@ -78,7 +83,9 @@ int maria_delete_table(const char *name) DBUG_RETURN(1); } - DBUG_RETURN(maria_delete_table_files(name, 0, sync_dir | MY_WME)); + if (!(error= maria_delete_table_files(name, 0, sync_dir | MY_WME))) + error= got_error; + DBUG_RETURN(error); } /** diff --git a/storage/maria/ma_open.c b/storage/maria/ma_open.c index f32fd6dd19d..28c24dc6ed1 100644 --- a/storage/maria/ma_open.c +++ b/storage/maria/ma_open.c @@ -1476,7 +1476,7 @@ static void setup_key_functions(register MARIA_KEYDEF *keyinfo) /** - @brief Function to save and store the header in the index file (.MYI) + @brief Function to save and store the header in the index file (.MAI) Operates under MARIA_SHARE::intern_lock if requested. Sets MARIA_SHARE::MARIA_STATE_INFO::is_of_horizon if transactional table. diff --git a/wsrep-lib b/wsrep-lib -Subproject 2da6e4894e1df5d1db51db2bbc49255e02251b9 +Subproject 41a6e9dad79c921134e44cf974b6b7ca3b84e53 |