diff options
author | Yuchen Pei <yuchen.pei@mariadb.com> | 2023-03-22 10:33:45 +1100 |
---|---|---|
committer | Yuchen Pei <yuchen.pei@mariadb.com> | 2023-03-22 10:33:45 +1100 |
commit | f4cbd42401dde5d023bf618b5531a73b4b059af5 (patch) | |
tree | 86c8414b00b2715ff4c40b28fba713118887a839 | |
parent | b4f3c9c8c5c9bc95c8e142b2fe8f3345650554a6 (diff) | |
download | mariadb-git-bb-11.1-mdev-26137-round-9.tar.gz |
Addressing review commentsbb-11.1-mdev-26137-round-9
-rw-r--r-- | mysql-test/suite/innodb/r/import_recovery.result | 129 | ||||
-rw-r--r-- | mysql-test/suite/innodb/t/import_recovery.test | 269 | ||||
-rw-r--r-- | mysql-test/suite/innodb/t/import_run_once.test | 4 | ||||
-rw-r--r-- | storage/innobase/handler/ha_innodb.cc | 122 | ||||
-rw-r--r-- | storage/innobase/handler/ha_innodb.h | 5 | ||||
-rw-r--r-- | storage/innobase/include/row0import.h | 317 | ||||
-rw-r--r-- | storage/innobase/row/row0import.cc | 458 |
7 files changed, 593 insertions, 711 deletions
diff --git a/mysql-test/suite/innodb/r/import_recovery.result b/mysql-test/suite/innodb/r/import_recovery.result index 961c9d1ab95..ad9afa8cb12 100644 --- a/mysql-test/suite/innodb/r/import_recovery.result +++ b/mysql-test/suite/innodb/r/import_recovery.result @@ -1,92 +1,71 @@ # # MDEV-26137 ALTER TABLE IMPORT enhancement # +call mtr.add_suppression('InnoDB: Tablespace for table `test`.`t1` is set as discarded.'); call mtr.add_suppression('InnoDB: Tablespace for table `test`.`t2` is set as discarded.'); +call mtr.add_suppression('InnoDB: Tablespace for table `test`.`t3` is set as discarded.'); +call mtr.add_suppression('InnoDB: ./test/t3.ibd: Page 0 at offset 0 looks corrupted.'); +call mtr.add_suppression("mariadbd.*: Index for table 't3' is corrupt; try to repair it"); +call mtr.add_suppression("InnoDB: Corrupted page \\[page id: space=.*, page number=0\\] of datafile './test/t3.ibd' could not be found in the doublewrite buffer."); +call mtr.add_suppression('InnoDB: Tablespace for table `test`.`t4` is set as discarded.'); +call mtr.add_suppression('InnoDB: ./test/t4.ibd: Page 0 at offset 0 looks corrupted.'); +call mtr.add_suppression("mariadbd.*: Index for table 't4' is corrupt; try to repair it"); +call mtr.add_suppression("InnoDB: Corrupted page \\[page id: space=.*, page number=0\\] of datafile './test/t4.ibd' could not be found in the doublewrite buffer."); # Recovery from crashes -## Creation of stub succeeds; server crashes; second import attempt succeeds -CREATE TABLE t1 (a int) ENGINE=InnoDB; -INSERT INTO t1 VALUES(42); -FLUSH TABLES t1 FOR EXPORT; +## t1: Creation of stub succeeds; server crashes; second import attempt succeeds +## t2: Creation of stub succeeds; server crashes; drop table +## t3: Creation of stub succeeds; server crashes; ibd corrupted; second import attempt fails; drop table +## t4: Did not copy .cfg; creation of stub succeeds; server crashes; ibd corrupted; second import attempt fails; drop table +CREATE TABLE t (a int) ENGINE=InnoDB; +INSERT INTO t VALUES(42); +FLUSH TABLES t FOR EXPORT; UNLOCK TABLES; -connect hang,localhost,root; +connect hang1,localhost,root; SET DEBUG_SYNC='ib_after_create_stub_for_import SIGNAL hung WAIT_FOR ever'; -ALTER TABLE t2 IMPORT TABLESPACE; -connection default; -SET DEBUG_SYNC='now WAIT_FOR hung'; -# restart -disconnect hang; -ALTER TABLE t2 IMPORT TABLESPACE; -SELECT * FROM t2; -a -42 -DROP TABLE t1, t2; -## Creation of stub succeeds; server crashes; drop stub succeeds -CREATE TABLE t1 (a int) ENGINE=InnoDB; -INSERT INTO t1 VALUES(42); -FLUSH TABLES t1 FOR EXPORT; -UNLOCK TABLES; -connect hang,localhost,root; +ALTER TABLE t1 IMPORT TABLESPACE; +connect hang2,localhost,root; SET DEBUG_SYNC='ib_after_create_stub_for_import SIGNAL hung WAIT_FOR ever'; ALTER TABLE t2 IMPORT TABLESPACE; -connection default; -SET DEBUG_SYNC='now WAIT_FOR hung'; -# restart -disconnect hang; -DROP TABLE t1, t2; -# Recovery from corruption -call mtr.add_suppression('InnoDB: ./test/t2.ibd: Page 0 at offset 0 looks corrupted.'); -call mtr.add_suppression("mariadbd.*: Index for table 't2' is corrupt; try to repair it"); -call mtr.add_suppression("InnoDB: Corrupted page \\[page id: space=.*, page number=0\\] of datafile './test/t2.ibd' could not be found in the doublewrite buffer."); -## Recovery from corruption, with cfg -CREATE TABLE t1 (a int) ENGINE=InnoDB; -INSERT INTO t1 VALUES(42); -FLUSH TABLES t1 FOR EXPORT; -UNLOCK TABLES; -# corrupting the 0th page -ALTER TABLE t2 IMPORT TABLESPACE; -ERROR HY000: Internal error: Error importing tablespace for table `test`.`t2` : Data structure corruption -DROP TABLE t1, t2; -## Recovery from corruption, without cfg -CREATE TABLE t1 (a int) ENGINE=InnoDB; -INSERT INTO t1 VALUES(42); -FLUSH TABLES t1 FOR EXPORT; -UNLOCK TABLES; -# corrupting the 0th page -ALTER TABLE t2 IMPORT TABLESPACE; -ERROR HY000: Schema mismatch (Expected FSP_SPACE_FLAGS=0x15, .ibd file contains 0x1010115.) -DROP TABLE t1, t2; -#recovery from crash and corruption -## Creation of stub succeeds; server crashes; ibd corrupted; second import attempt fails; drops table -CREATE TABLE t1 (a int) ENGINE=InnoDB; -INSERT INTO t1 VALUES(42); -FLUSH TABLES t1 FOR EXPORT; -UNLOCK TABLES; -connect hang,localhost,root; +connect hang3,localhost,root; SET DEBUG_SYNC='ib_after_create_stub_for_import SIGNAL hung WAIT_FOR ever'; -ALTER TABLE t2 IMPORT TABLESPACE; +ALTER TABLE t3 IMPORT TABLESPACE; +connect hang4,localhost,root; +SET DEBUG_SYNC='ib_after_create_stub_for_import SIGNAL hung WAIT_FOR ever'; +ALTER TABLE t4 IMPORT TABLESPACE; connection default; -SET DEBUG_SYNC='now WAIT_FOR hung'; -disconnect hang; +SET DEBUG_SYNC='now WAIT_FOR hung EXECUTE 4'; # corrupting the 0th page # Restart mysqld after the crash and reconnect. # restart -ALTER TABLE t2 IMPORT TABLESPACE; -ERROR HY000: Internal error: Error importing tablespace for table `test`.`t2` : Data structure corruption -DROP TABLE t1, t2; -## Creation of stub without .cfg succeeds; server crashes; ibd corrupted; second import attempt fails; drops table -CREATE TABLE t1 (a int) ENGINE=InnoDB; -INSERT INTO t1 VALUES(42); -FLUSH TABLES t1 FOR EXPORT; +ALTER TABLE t1 IMPORT TABLESPACE; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` int(11) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci +SELECT * FROM t1; +a +42 +ALTER TABLE t3 IMPORT TABLESPACE; +ERROR HY000: Internal error: Error importing tablespace for table `test`.`t3` : Data structure corruption +ALTER TABLE t4 IMPORT TABLESPACE; +ERROR HY000: Schema mismatch (Expected FSP_SPACE_FLAGS=0x15, .ibd file contains 0x1010115.) +DROP TABLE t, t1, t2, t3, t4; +# Recovery from corruption only, no server restart +## t5: Recovery from corruption, with cfg +## t6: Recovery from corruption, without cfg +call mtr.add_suppression('InnoDB: ./test/t5.ibd: Page 0 at offset 0 looks corrupted.'); +call mtr.add_suppression("mariadbd.*: Index for table 't5' is corrupt; try to repair it"); +call mtr.add_suppression("InnoDB: Corrupted page \\[page id: space=.*, page number=0\\] of datafile './test/t5.ibd' could not be found in the doublewrite buffer."); +call mtr.add_suppression("InnoDB: Corrupted page \\[page id: space=.*, page number=0\\] of datafile './test/t6.ibd' could not be found in the doublewrite buffer."); +call mtr.add_suppression("mariadbd.*: Index for table 't6' is corrupt; try to repair it"); +CREATE TABLE t (a int) ENGINE=InnoDB; +INSERT INTO t VALUES(42); +FLUSH TABLES t FOR EXPORT; UNLOCK TABLES; -connect hang,localhost,root; -SET DEBUG_SYNC='ib_after_create_stub_for_import SIGNAL hung WAIT_FOR ever'; -ALTER TABLE t2 IMPORT TABLESPACE; -connection default; -SET DEBUG_SYNC='now WAIT_FOR hung'; -disconnect hang; # corrupting the 0th page -# Restart mysqld after the crash and reconnect. -# restart -ALTER TABLE t2 IMPORT TABLESPACE; +ALTER TABLE t5 IMPORT TABLESPACE; +ERROR HY000: Internal error: Error importing tablespace for table `test`.`t5` : Data structure corruption +ALTER TABLE t6 IMPORT TABLESPACE; ERROR HY000: Schema mismatch (Expected FSP_SPACE_FLAGS=0x15, .ibd file contains 0x1010115.) -DROP TABLE t1, t2; +DROP TABLE t, t5, t6; diff --git a/mysql-test/suite/innodb/t/import_recovery.test b/mysql-test/suite/innodb/t/import_recovery.test index 69913658088..8657c1dc751 100644 --- a/mysql-test/suite/innodb/t/import_recovery.test +++ b/mysql-test/suite/innodb/t/import_recovery.test @@ -7,211 +7,142 @@ --echo # let $MYSQLD_DATADIR = `SELECT @@datadir`; +let MYSQLD_DATADIR = `SELECT @@datadir`; +let INNODB_PAGE_SIZE=`select @@innodb_page_size`; +call mtr.add_suppression('InnoDB: Tablespace for table `test`.`t1` is set as discarded.'); call mtr.add_suppression('InnoDB: Tablespace for table `test`.`t2` is set as discarded.'); +call mtr.add_suppression('InnoDB: Tablespace for table `test`.`t3` is set as discarded.'); +call mtr.add_suppression('InnoDB: ./test/t3.ibd: Page 0 at offset 0 looks corrupted.'); +call mtr.add_suppression("mariadbd.*: Index for table 't3' is corrupt; try to repair it"); +# In Windows etc. +call mtr.add_suppression("InnoDB: Corrupted page \\[page id: space=.*, page number=0\\] of datafile './test/t3.ibd' could not be found in the doublewrite buffer."); +call mtr.add_suppression('InnoDB: Tablespace for table `test`.`t4` is set as discarded.'); +call mtr.add_suppression('InnoDB: ./test/t4.ibd: Page 0 at offset 0 looks corrupted.'); +call mtr.add_suppression("mariadbd.*: Index for table 't4' is corrupt; try to repair it"); +# In Windows etc. +call mtr.add_suppression("InnoDB: Corrupted page \\[page id: space=.*, page number=0\\] of datafile './test/t4.ibd' could not be found in the doublewrite buffer."); --echo # Recovery from crashes ---echo ## Creation of stub succeeds; server crashes; second import attempt succeeds -CREATE TABLE t1 (a int) ENGINE=InnoDB; -INSERT INTO t1 VALUES(42); -FLUSH TABLES t1 FOR EXPORT; ---copy_file $MYSQLD_DATADIR/test/t1.cfg $MYSQLD_DATADIR/test/t2.cfg ---copy_file $MYSQLD_DATADIR/test/t1.frm $MYSQLD_DATADIR/test/t2.frm ---copy_file $MYSQLD_DATADIR/test/t1.ibd $MYSQLD_DATADIR/test/t2.ibd +--echo ## t1: Creation of stub succeeds; server crashes; second import attempt succeeds +--echo ## t2: Creation of stub succeeds; server crashes; drop table +--echo ## t3: Creation of stub succeeds; server crashes; ibd corrupted; second import attempt fails; drop table +--echo ## t4: Did not copy .cfg; creation of stub succeeds; server crashes; ibd corrupted; second import attempt fails; drop table +CREATE TABLE t (a int) ENGINE=InnoDB; +INSERT INTO t VALUES(42); +FLUSH TABLES t FOR EXPORT; +--copy_file $MYSQLD_DATADIR/test/t.cfg $MYSQLD_DATADIR/test/t1.cfg +--copy_file $MYSQLD_DATADIR/test/t.frm $MYSQLD_DATADIR/test/t1.frm +--copy_file $MYSQLD_DATADIR/test/t.ibd $MYSQLD_DATADIR/test/t1.ibd +--copy_file $MYSQLD_DATADIR/test/t.cfg $MYSQLD_DATADIR/test/t2.cfg +--copy_file $MYSQLD_DATADIR/test/t.frm $MYSQLD_DATADIR/test/t2.frm +--copy_file $MYSQLD_DATADIR/test/t.ibd $MYSQLD_DATADIR/test/t2.ibd +--copy_file $MYSQLD_DATADIR/test/t.cfg $MYSQLD_DATADIR/test/t3.cfg +--copy_file $MYSQLD_DATADIR/test/t.frm $MYSQLD_DATADIR/test/t3.frm +--copy_file $MYSQLD_DATADIR/test/t.ibd $MYSQLD_DATADIR/test/t3.ibd +--copy_file $MYSQLD_DATADIR/test/t.frm $MYSQLD_DATADIR/test/t4.frm +--copy_file $MYSQLD_DATADIR/test/t.ibd $MYSQLD_DATADIR/test/t4.ibd UNLOCK TABLES; -connect (hang,localhost,root); +connect (hang1,localhost,root); SET DEBUG_SYNC='ib_after_create_stub_for_import SIGNAL hung WAIT_FOR ever'; -send +send ALTER TABLE t1 IMPORT TABLESPACE; -ALTER TABLE t2 IMPORT TABLESPACE; - -connection default; -SET DEBUG_SYNC='now WAIT_FOR hung'; -let $shutdown_timeout=0; ---source include/restart_mysqld.inc -disconnect hang; - -ALTER TABLE t2 IMPORT TABLESPACE; -SELECT * FROM t2; -DROP TABLE t1, t2; - ---echo ## Creation of stub succeeds; server crashes; drop stub succeeds -CREATE TABLE t1 (a int) ENGINE=InnoDB; -INSERT INTO t1 VALUES(42); -FLUSH TABLES t1 FOR EXPORT; ---copy_file $MYSQLD_DATADIR/test/t1.cfg $MYSQLD_DATADIR/test/t2.cfg ---copy_file $MYSQLD_DATADIR/test/t1.frm $MYSQLD_DATADIR/test/t2.frm ---copy_file $MYSQLD_DATADIR/test/t1.ibd $MYSQLD_DATADIR/test/t2.ibd -UNLOCK TABLES; - -connect (hang,localhost,root); +connect (hang2,localhost,root); SET DEBUG_SYNC='ib_after_create_stub_for_import SIGNAL hung WAIT_FOR ever'; -send - -ALTER TABLE t2 IMPORT TABLESPACE; - -connection default; -SET DEBUG_SYNC='now WAIT_FOR hung'; -let $shutdown_timeout=0; ---source include/restart_mysqld.inc -disconnect hang; - -DROP TABLE t1, t2; - ---echo # Recovery from corruption - -let MYSQLD_DATADIR = `SELECT @@datadir`; -let INNODB_PAGE_SIZE=`select @@innodb_page_size`; - -call mtr.add_suppression('InnoDB: ./test/t2.ibd: Page 0 at offset 0 looks corrupted.'); -call mtr.add_suppression("mariadbd.*: Index for table 't2' is corrupt; try to repair it"); -# In Windows etc. -call mtr.add_suppression("InnoDB: Corrupted page \\[page id: space=.*, page number=0\\] of datafile './test/t2.ibd' could not be found in the doublewrite buffer."); - ---echo ## Recovery from corruption, with cfg -CREATE TABLE t1 (a int) ENGINE=InnoDB; -INSERT INTO t1 VALUES(42); -FLUSH TABLES t1 FOR EXPORT; ---copy_file $MYSQLD_DATADIR/test/t1.cfg $MYSQLD_DATADIR/test/t2.cfg ---copy_file $MYSQLD_DATADIR/test/t1.frm $MYSQLD_DATADIR/test/t2.frm ---copy_file $MYSQLD_DATADIR/test/t1.ibd $MYSQLD_DATADIR/test/t2.ibd -UNLOCK TABLES; - ---echo # corrupting the 0th page -perl; -my $ps = $ENV{INNODB_PAGE_SIZE}; - -my $file = "$ENV{MYSQLD_DATADIR}/test/t2.ibd"; -open(FILE, "+<$file") || die "Unable to open $file"; -binmode FILE; -sysseek(FILE, 0, 0) || die "Unable to seek $file\n"; -die "Unable to read $file" unless sysread(FILE, $page, $ps) == $ps; -# Replace all 0 bits with 1 bits. -$page =~ s/\x0/\x1/g; -sysseek(FILE, 0, 0) || die "Unable to seek $file\n"; -syswrite(FILE, $page, $ps)==$ps || die "Unable to write $file\n"; -close FILE or die "close"; -EOF - ---error ER_INTERNAL_ERROR -ALTER TABLE t2 IMPORT TABLESPACE; -DROP TABLE t1, t2; +send ALTER TABLE t2 IMPORT TABLESPACE; - ---echo ## Recovery from corruption, without cfg -CREATE TABLE t1 (a int) ENGINE=InnoDB; -INSERT INTO t1 VALUES(42); -FLUSH TABLES t1 FOR EXPORT; ---copy_file $MYSQLD_DATADIR/test/t1.frm $MYSQLD_DATADIR/test/t2.frm ---copy_file $MYSQLD_DATADIR/test/t1.ibd $MYSQLD_DATADIR/test/t2.ibd -UNLOCK TABLES; - ---echo # corrupting the 0th page -perl; -my $ps = $ENV{INNODB_PAGE_SIZE}; - -my $file = "$ENV{MYSQLD_DATADIR}/test/t2.ibd"; -open(FILE, "+<$file") || die "Unable to open $file"; -binmode FILE; -sysseek(FILE, 0, 0) || die "Unable to seek $file\n"; -die "Unable to read $file" unless sysread(FILE, $page, $ps) == $ps; -# Replace all 0 bits with 1 bits. -$page =~ s/\x0/\x1/g; -sysseek(FILE, 0, 0) || die "Unable to seek $file\n"; -syswrite(FILE, $page, $ps)==$ps || die "Unable to write $file\n"; -close FILE or die "close"; -EOF - ---error ER_TABLE_SCHEMA_MISMATCH -ALTER TABLE t2 IMPORT TABLESPACE; -DROP TABLE t1, t2; - ---echo #recovery from crash and corruption ---echo ## Creation of stub succeeds; server crashes; ibd corrupted; second import attempt fails; drops table -CREATE TABLE t1 (a int) ENGINE=InnoDB; -INSERT INTO t1 VALUES(42); -FLUSH TABLES t1 FOR EXPORT; ---copy_file $MYSQLD_DATADIR/test/t1.cfg $MYSQLD_DATADIR/test/t2.cfg ---copy_file $MYSQLD_DATADIR/test/t1.frm $MYSQLD_DATADIR/test/t2.frm ---copy_file $MYSQLD_DATADIR/test/t1.ibd $MYSQLD_DATADIR/test/t2.ibd -UNLOCK TABLES; - -connect (hang,localhost,root); +connect (hang3,localhost,root); SET DEBUG_SYNC='ib_after_create_stub_for_import SIGNAL hung WAIT_FOR ever'; -send +send ALTER TABLE t3 IMPORT TABLESPACE; -ALTER TABLE t2 IMPORT TABLESPACE; +connect (hang4,localhost,root); +SET DEBUG_SYNC='ib_after_create_stub_for_import SIGNAL hung WAIT_FOR ever'; +send ALTER TABLE t4 IMPORT TABLESPACE; connection default; -SET DEBUG_SYNC='now WAIT_FOR hung'; +SET DEBUG_SYNC='now WAIT_FOR hung EXECUTE 4'; let $shutdown_timeout=0; --source include/shutdown_mysqld.inc -disconnect hang; --echo # corrupting the 0th page perl; my $ps = $ENV{INNODB_PAGE_SIZE}; -my $file = "$ENV{MYSQLD_DATADIR}/test/t2.ibd"; -open(FILE, "+<$file") || die "Unable to open $file"; -binmode FILE; -sysseek(FILE, 0, 0) || die "Unable to seek $file\n"; -die "Unable to read $file" unless sysread(FILE, $page, $ps) == $ps; -# Replace all 0 bits with 1 bits. -$page =~ s/\x0/\x1/g; -sysseek(FILE, 0, 0) || die "Unable to seek $file\n"; -syswrite(FILE, $page, $ps)==$ps || die "Unable to write $file\n"; -close FILE or die "close"; +@tables= ('t3', 't4'); +foreach $table (@tables) { + my $file = "$ENV{MYSQLD_DATADIR}/test/$table.ibd"; + open(FILE, "+<$file") || die "Unable to open $file"; + binmode FILE; + sysseek(FILE, 0, 0) || die "Unable to seek $file\n"; + die "Unable to read $file" unless sysread(FILE, $page, $ps) == $ps; + # Replace all NUL bytes with SOH bytes. + $page =~ tr/\x0/\x1/; + sysseek(FILE, 0, 0) || die "Unable to seek $file\n"; + syswrite(FILE, $page, $ps)==$ps || die "Unable to write $file\n"; + close FILE or die "close"; +} EOF --echo # Restart mysqld after the crash and reconnect. --source include/start_mysqld.inc ---error ER_INTERNAL_ERROR -ALTER TABLE t2 IMPORT TABLESPACE; -DROP TABLE t1, t2; - +ALTER TABLE t1 IMPORT TABLESPACE; +SHOW CREATE TABLE t1; +SELECT * FROM t1; ---echo ## Creation of stub without .cfg succeeds; server crashes; ibd corrupted; second import attempt fails; drops table -CREATE TABLE t1 (a int) ENGINE=InnoDB; -INSERT INTO t1 VALUES(42); -FLUSH TABLES t1 FOR EXPORT; ---copy_file $MYSQLD_DATADIR/test/t1.frm $MYSQLD_DATADIR/test/t2.frm ---copy_file $MYSQLD_DATADIR/test/t1.ibd $MYSQLD_DATADIR/test/t2.ibd -UNLOCK TABLES; +--error ER_INTERNAL_ERROR +ALTER TABLE t3 IMPORT TABLESPACE; -connect (hang,localhost,root); -SET DEBUG_SYNC='ib_after_create_stub_for_import SIGNAL hung WAIT_FOR ever'; -send +--error ER_TABLE_SCHEMA_MISMATCH +ALTER TABLE t4 IMPORT TABLESPACE; -ALTER TABLE t2 IMPORT TABLESPACE; +DROP TABLE t, t1, t2, t3, t4; -connection default; -SET DEBUG_SYNC='now WAIT_FOR hung'; -let $shutdown_timeout=0; ---source include/shutdown_mysqld.inc -disconnect hang; +--echo # Recovery from corruption only, no server restart +--echo ## t5: Recovery from corruption, with cfg +--echo ## t6: Recovery from corruption, without cfg +call mtr.add_suppression('InnoDB: ./test/t5.ibd: Page 0 at offset 0 looks corrupted.'); +call mtr.add_suppression("mariadbd.*: Index for table 't5' is corrupt; try to repair it"); +# In Windows etc. +call mtr.add_suppression("InnoDB: Corrupted page \\[page id: space=.*, page number=0\\] of datafile './test/t5.ibd' could not be found in the doublewrite buffer."); +# In Windows etc. +call mtr.add_suppression("InnoDB: Corrupted page \\[page id: space=.*, page number=0\\] of datafile './test/t6.ibd' could not be found in the doublewrite buffer."); +call mtr.add_suppression("mariadbd.*: Index for table 't6' is corrupt; try to repair it"); + +CREATE TABLE t (a int) ENGINE=InnoDB; +INSERT INTO t VALUES(42); +FLUSH TABLES t FOR EXPORT; +--copy_file $MYSQLD_DATADIR/test/t.cfg $MYSQLD_DATADIR/test/t5.cfg +--copy_file $MYSQLD_DATADIR/test/t.frm $MYSQLD_DATADIR/test/t5.frm +--copy_file $MYSQLD_DATADIR/test/t.ibd $MYSQLD_DATADIR/test/t5.ibd +--copy_file $MYSQLD_DATADIR/test/t.frm $MYSQLD_DATADIR/test/t6.frm +--copy_file $MYSQLD_DATADIR/test/t.ibd $MYSQLD_DATADIR/test/t6.ibd +UNLOCK TABLES; --echo # corrupting the 0th page perl; my $ps = $ENV{INNODB_PAGE_SIZE}; -my $file = "$ENV{MYSQLD_DATADIR}/test/t2.ibd"; -open(FILE, "+<$file") || die "Unable to open $file"; -binmode FILE; -sysseek(FILE, 0, 0) || die "Unable to seek $file\n"; -die "Unable to read $file" unless sysread(FILE, $page, $ps) == $ps; -# Replace all 0 bits with 1 bits. -$page =~ s/\x0/\x1/g; -sysseek(FILE, 0, 0) || die "Unable to seek $file\n"; -syswrite(FILE, $page, $ps)==$ps || die "Unable to write $file\n"; -close FILE or die "close"; +@tables= ('t5', 't6'); +foreach $table (@tables) { + my $file = "$ENV{MYSQLD_DATADIR}/test/$table.ibd"; + open(FILE, "+<$file") || die "Unable to open $file"; + binmode FILE; + sysseek(FILE, 0, 0) || die "Unable to seek $file\n"; + die "Unable to read $file" unless sysread(FILE, $page, $ps) == $ps; + # Replace all NUL bytes with SOH bytes. + $page =~ tr/\x0/\x1/; + sysseek(FILE, 0, 0) || die "Unable to seek $file\n"; + syswrite(FILE, $page, $ps)==$ps || die "Unable to write $file\n"; + close FILE or die "close"; +} EOF ---echo # Restart mysqld after the crash and reconnect. ---source include/start_mysqld.inc +--error ER_INTERNAL_ERROR +ALTER TABLE t5 IMPORT TABLESPACE; --error ER_TABLE_SCHEMA_MISMATCH -ALTER TABLE t2 IMPORT TABLESPACE; -DROP TABLE t1, t2; +ALTER TABLE t6 IMPORT TABLESPACE; + +DROP TABLE t, t5, t6; diff --git a/mysql-test/suite/innodb/t/import_run_once.test b/mysql-test/suite/innodb/t/import_run_once.test index 916851982ec..867f6434fe7 100644 --- a/mysql-test/suite/innodb/t/import_run_once.test +++ b/mysql-test/suite/innodb/t/import_run_once.test @@ -26,10 +26,6 @@ INSERT INTO t1 VALUES(42); CREATE TABLE t2 LIKE t1; FLUSH TABLES t1 FOR EXPORT; --copy_file $MYSQLD_DATADIR/test/t1.cfg $MYSQLD_DATADIR/test/t2.cfg ---error 1 ---copy_file $MYSQLD_DATADIR/test/t1.frm $MYSQLD_DATADIR/test/t2.frm ---error 1 ---copy_file $MYSQLD_DATADIR/test/t1.ibd $MYSQLD_DATADIR/test/t2.ibd UNLOCK TABLES; DROP TABLE t1; call mtr.add_suppression("InnoDB: Unable to import tablespace"); diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 48b2c668372..fee3964cc0a 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -292,27 +292,6 @@ get_row_format( } } -/** Convert the InnoDB ROW_FORMAT from rec_format_enum to row_type. -@param[in] from ROW_FORMAT as a rec_format_enum -@return the row_type representation of ROW_FORMAT. */ -static enum row_type from_rec_format(const rec_format_enum from) -{ - switch (from) - { - case REC_FORMAT_COMPACT: - return ROW_TYPE_COMPACT; - case REC_FORMAT_DYNAMIC: - return ROW_TYPE_DYNAMIC; - case REC_FORMAT_REDUNDANT: - return ROW_TYPE_REDUNDANT; - case REC_FORMAT_COMPRESSED: - return ROW_TYPE_COMPRESSED; - /* Impossible. */ - default: - return ROW_TYPE_DEFAULT; - } -} - static ulong innodb_default_row_format = DEFAULT_ROW_FORMAT_DYNAMIC; /** Possible values for system variable "innodb_stats_method". The values @@ -1898,61 +1877,6 @@ static int innobase_wsrep_get_checkpoint(handlerton* hton, XID* xid); #define normalize_table_name(a,b) \ normalize_table_name_c_low(a,b,IF_WIN(true,false)) - -/** Prepare the create info to create a new stub table for import. -@param[in] thd Connection -@param[in] name Table name, format: "db/table_name". -@param[in,out] create_info The create info for creating a stub. -@return 0 if success else error number. */ -static int prepare_create_stub_for_import(THD *thd, const char *name, - HA_CREATE_INFO& create_info) -{ - DBUG_ENTER("prepare_create_stub_for_import"); - FetchIndexRootPages fetchIndexRootPages; - if (fil_tablespace_iterate(fil_path_to_mysql_datadir, name, - IO_BUFFER_SIZE(srv_page_size), - fetchIndexRootPages) - != DB_SUCCESS) - { - const char *ibd_path = fil_make_filepath( - fil_path_to_mysql_datadir, table_name_t(const_cast<char*>(name)), IBD, true); - if (!ibd_path) - return(ER_ENGINE_OUT_OF_MEMORY); - sql_print_error("InnoDB: failed to get row format from %s.\n", - ibd_path); - DBUG_RETURN(ER_INNODB_IMPORT_ERROR); - } - create_info.init(); - /* get the row format from ibd. */ - create_info.row_type = fetchIndexRootPages.m_row_format; - /* if .cfg exists, get the row format from cfg, and compare with - ibd, report error if different, except when cfg reports - compact/dynamic and ibd reports not_used (indicating either compact - or dynamic but not sure) */ - enum rec_format_enum rec_format_from_cfg; - if (get_row_type_from_cfg(fil_path_to_mysql_datadir, name, thd, - rec_format_from_cfg) - == DB_SUCCESS) - { - /* if ibd reports not_used but cfg reports compact or dynamic, go - with cfg. */ - if (create_info.row_type != from_rec_format(rec_format_from_cfg) && - !((rec_format_from_cfg == REC_FORMAT_COMPACT || - rec_format_from_cfg == REC_FORMAT_DYNAMIC) && - create_info.row_type == ROW_TYPE_NOT_USED)) - { - sql_print_error( - "InnoDB: cfg and ibd disagree on row format for table %s.\n", - name); - DBUG_RETURN(ER_INNODB_IMPORT_ERROR); - } - else - create_info.row_type= from_rec_format(rec_format_from_cfg); - } else if (create_info.row_type == ROW_TYPE_NOT_USED) - create_info.row_type = ROW_TYPE_DYNAMIC; - DBUG_RETURN(0); -} - ulonglong ha_innobase::table_version() const { /* This is either "garbage" or something that was assigned @@ -5301,7 +5225,8 @@ create_table_info_t::create_table_info_t( m_create_info(create_info), m_table_name(table_name), m_table(NULL), m_remote_path(remote_path), - m_innodb_file_per_table(file_per_table) + m_innodb_file_per_table(file_per_table), + m_creating_stub(thd_ddl_options(thd)->import_tablespace()) { } @@ -5927,18 +5852,18 @@ ha_innobase::open(const char* name, int, uint) "stub" table similar to the effects of CREATE TABLE followed by ALTER TABLE ... DISCARD TABLESPACE. */ if (!ib_table && thd_ddl_options(thd)->import_tablespace()) - { - HA_CREATE_INFO create_info; - if (int err= prepare_create_stub_for_import(thd, norm_name, - create_info)) - DBUG_RETURN(err); - create(norm_name, table, &create_info, true, nullptr); - DEBUG_SYNC(thd, "ib_after_create_stub_for_import"); - ib_table = open_dict_table(name, norm_name, is_part, - DICT_ERR_IGNORE_FK_NOKEY); - } + { + HA_CREATE_INFO create_info; + if (int err= prepare_create_stub_for_import(thd, norm_name, + create_info)) + DBUG_RETURN(err); + create(norm_name, table, &create_info, true, nullptr); + DEBUG_SYNC(thd, "ib_after_create_stub_for_import"); + ib_table = open_dict_table(name, norm_name, is_part, + DICT_ERR_IGNORE_FK_NOKEY); + } - if (NULL == ib_table) { + if (NULL == ib_table) { if (is_part) { sql_print_error("Failed to open table %s.\n", norm_name); @@ -10661,10 +10586,10 @@ create_table_info_t::create_table_def() ? doc_id_col : n_cols - num_v; } - /* Assume the tablespace is not available until we are able to - import it.*/ - if (thd_ddl_options(m_thd)->import_tablespace()) - table->file_unreadable = true; + /* Assume the tablespace is not available until we are able to + import it.*/ + if (m_creating_stub) + table->file_unreadable = true; if (DICT_TF_HAS_DATA_DIR(m_flags)) { ut_a(strlen(m_remote_path)); @@ -11679,10 +11604,10 @@ index_bad: } } - /* If we are trying to import a tablespace, mark tablespace as - discarded. */ - if (thd_ddl_options(m_thd)->import_tablespace()) - m_flags2 |= DICT_TF2_DISCARDED; + /* If we are trying to import a tablespace, mark tablespace as + discarded. */ + if (m_creating_stub) + m_flags2 |= DICT_TF2_DISCARDED; row_type = m_create_info->row_type; @@ -13234,12 +13159,11 @@ ha_innobase::create(const char *name, TABLE *form, HA_CREATE_INFO *create_info, row_mysql_lock_data_dictionary(trx); } - const bool importing= thd_ddl_options(ha_thd())->import_tablespace(); if (!error) /* We can't possibly have foreign key information when creating a stub table for importing .frm / .cfg / .ibd because it is not stored in any of these files. */ - error= info.create_table(own_trx && !importing); + error= info.create_table(own_trx && !info.creating_stub()); if (own_trx || (info.flags2() & DICT_TF2_TEMPORARY)) { @@ -13264,7 +13188,7 @@ ha_innobase::create(const char *name, TABLE *form, HA_CREATE_INFO *create_info, /* Skip stats update when creating a stub table for importing, as it is not needed and would report error due to the table not being readable yet. */ - if (!importing) + if (!info.creating_stub()) dict_stats_update(info.table(), DICT_STATS_EMPTY_TABLE); if (!info.table()->is_temporary()) log_write_up_to(trx->commit_lsn, true); diff --git a/storage/innobase/handler/ha_innodb.h b/storage/innobase/handler/ha_innodb.h index aedc7e097c4..8ed1dc8d3e4 100644 --- a/storage/innobase/handler/ha_innodb.h +++ b/storage/innobase/handler/ha_innodb.h @@ -701,6 +701,8 @@ public: ulint flags2() const { return(m_flags2); } + bool creating_stub() const { return m_creating_stub; } + /** Get trx. */ trx_t* trx() const { return(m_trx); } @@ -767,6 +769,9 @@ private: /** Table flags2 */ ulint m_flags2; + + /** Whether we are creating a stub table for importing. */ + bool m_creating_stub; }; /** diff --git a/storage/innobase/include/row0import.h b/storage/innobase/include/row0import.h index 7019ab2a900..c65df81cedb 100644 --- a/storage/innobase/include/row0import.h +++ b/storage/innobase/include/row0import.h @@ -28,296 +28,12 @@ Created 2012-02-08 by Sunny Bains #define row0import_h #include "dict0types.h" -#include "ut0new.h" -#include "os0file.h" -#include "buf0buf.h" -#include "trx0trx.h" -#include "page0page.h" +#include <handler.h> // Forward declarations struct trx_t; struct dict_table_t; struct row_prebuilt_t; -struct fil_iterator_t; -struct row_import; - -/** The size of the buffer to use for IO. -@param n physical page size -@return number of pages */ -#define IO_BUFFER_SIZE(n) ((1024 * 1024) / (n)) - -/** Functor that is called for each physical page that is read from the -tablespace file. */ -class AbstractCallback -{ -public: - /** Constructor - @param trx covering transaction */ - AbstractCallback(trx_t* trx, uint32_t space_id) - : - m_zip_size(0), - m_trx(trx), - m_space(space_id), - m_xdes(), - m_xdes_page_no(UINT32_MAX), - m_space_flags(UINT32_MAX) UNIV_NOTHROW { } - - /** Free any extent descriptor instance */ - virtual ~AbstractCallback() - { - UT_DELETE_ARRAY(m_xdes); - } - - /** Determine the page size to use for traversing the tablespace - @param file_size size of the tablespace file in bytes - @param block contents of the first page in the tablespace file. - @retval DB_SUCCESS or error code. */ - virtual dberr_t init( - os_offset_t file_size, - const buf_block_t* block) UNIV_NOTHROW; - - /** @return true if compressed table. */ - bool is_compressed_table() const UNIV_NOTHROW - { - return get_zip_size(); - } - - /** @return the tablespace flags */ - uint32_t get_space_flags() const { return m_space_flags; } - - /** - Set the name of the physical file and the file handle that is used - to open it for the file that is being iterated over. - @param filename the physical name of the tablespace file - @param file OS file handle */ - void set_file(const char* filename, pfs_os_file_t file) UNIV_NOTHROW - { - m_file = file; - m_filepath = filename; - } - - ulint get_zip_size() const { return m_zip_size; } - ulint physical_size() const - { - return m_zip_size ? m_zip_size : srv_page_size; - } - - const char* filename() const { return m_filepath; } - - /** - Called for every page in the tablespace. If the page was not - updated then its state must be set to BUF_PAGE_NOT_USED. For - compressed tables the page descriptor memory will be at offset: - block->page.frame + srv_page_size; - @param block block read from file, note it is not from the buffer pool - @retval DB_SUCCESS or error code. */ - virtual dberr_t operator()(buf_block_t* block) UNIV_NOTHROW = 0; - - /** @return the tablespace identifier */ - uint32_t get_space_id() const { return m_space; } - - bool is_interrupted() const { return trx_is_interrupted(m_trx); } - - /** - Get the data page depending on the table type, compressed or not. - @param block - block read from disk - @retval the buffer frame */ - static byte* get_frame(const buf_block_t* block) - { - return block->page.zip.data - ? block->page.zip.data : block->page.frame; - } - - /** Invoke the functionality for the callback */ - virtual dberr_t run(const fil_iterator_t& iter, - buf_block_t* block) UNIV_NOTHROW = 0; - -protected: - /** Get the physical offset of the extent descriptor within the page. - @param page_no page number of the extent descriptor - @param page contents of the page containing the extent descriptor. - @return the start of the xdes array in a page */ - const xdes_t* xdes( - ulint page_no, - const page_t* page) const UNIV_NOTHROW - { - ulint offset; - - offset = xdes_calc_descriptor_index(get_zip_size(), page_no); - - return(page + XDES_ARR_OFFSET + XDES_SIZE * offset); - } - - /** Set the current page directory (xdes). If the extent descriptor is - marked as free then free the current extent descriptor and set it to - 0. This implies that all pages that are covered by this extent - descriptor are also freed. - - @param page_no offset of page within the file - @param page page contents - @return DB_SUCCESS or error code. */ - dberr_t set_current_xdes( - uint32_t page_no, - const page_t* page) UNIV_NOTHROW - { - m_xdes_page_no = page_no; - - UT_DELETE_ARRAY(m_xdes); - m_xdes = NULL; - - if (mach_read_from_4(XDES_ARR_OFFSET + XDES_STATE + page) - != XDES_FREE) { - const ulint physical_size = m_zip_size - ? m_zip_size : srv_page_size; - - m_xdes = UT_NEW_ARRAY_NOKEY(xdes_t, physical_size); - - /* Trigger OOM */ - DBUG_EXECUTE_IF( - "ib_import_OOM_13", - UT_DELETE_ARRAY(m_xdes); - m_xdes = NULL; - ); - - if (m_xdes == NULL) { - return(DB_OUT_OF_MEMORY); - } - - memcpy(m_xdes, page, physical_size); - } - - return(DB_SUCCESS); - } - - /** Check if the page is marked as free in the extent descriptor. - @param page_no page number to check in the extent descriptor. - @return true if the page is marked as free */ - bool is_free(uint32_t page_no) const UNIV_NOTHROW - { - ut_a(xdes_calc_descriptor_page(get_zip_size(), page_no) - == m_xdes_page_no); - - if (m_xdes != 0) { - const xdes_t* xdesc = xdes(page_no, m_xdes); - ulint pos = page_no % FSP_EXTENT_SIZE; - - return xdes_is_free(xdesc, pos); - } - - /* If the current xdes was free, the page must be free. */ - return(true); - } - -protected: - /** The ROW_FORMAT=COMPRESSED page size, or 0. */ - ulint m_zip_size; - - /** File handle to the tablespace */ - pfs_os_file_t m_file; - - /** Physical file path. */ - const char* m_filepath; - - /** Covering transaction. */ - trx_t* m_trx; - - /** Space id of the file being iterated over. */ - uint32_t m_space; - - /** Current extent descriptor page */ - xdes_t* m_xdes; - - /** Physical page offset in the file of the extent descriptor */ - uint32_t m_xdes_page_no; - - /** Flags value read from the header page */ - uint32_t m_space_flags; -}; - -/** -Try and determine the index root pages by checking if the next/prev -pointers are both FIL_NULL. We need to ensure that skip deleted pages. */ -struct FetchIndexRootPages : public AbstractCallback { - - /** Index information gathered from the .ibd file. */ - struct Index { - - Index(index_id_t id, uint32_t page_no) - : - m_id(id), - m_page_no(page_no) { } - - index_id_t m_id; /*!< Index id */ - uint32_t m_page_no; /*!< Root page number */ - }; - - /** Constructor - @param trx covering (user) transaction - @param table table definition in server .*/ - FetchIndexRootPages(const dict_table_t* table, trx_t* trx) - : - AbstractCallback(trx, UINT32_MAX), - m_table(table), m_index(0, 0) UNIV_NOTHROW { } - - FetchIndexRootPages() - : - AbstractCallback(nullptr, UINT32_MAX), - m_table(nullptr), m_index(0, 0) UNIV_NOTHROW { } - - /** Destructor */ - ~FetchIndexRootPages() UNIV_NOTHROW override = default; - - /** Fetch the clustered index root page in the tablespace - @param iter Tablespace iterator - @param block Block to use for IO - @retval DB_SUCCESS or error code */ - dberr_t run(const fil_iterator_t& iter, - buf_block_t* block) UNIV_NOTHROW override; - - /** Check that fsp flags and row formats match. - @param block block to convert, it is not from the buffer pool. - @retval DB_SUCCESS or error code. */ - dberr_t operator()(buf_block_t* block) UNIV_NOTHROW override; - - /** Get row format from the header and the root index page. */ - enum row_type get_row_format(buf_block_t *block) - { - if (!page_is_comp(block->page.frame)) - return ROW_TYPE_REDUNDANT; - /* With full_crc32 we cannot tell between dynamic or compact, and - return not_used. We cannot simply return dynamic or compact, as - the client of this function will not be able to tell whether it is - dynamic because of this or the other branch below. Returning - default would also work if it is immediately handled, but is still - more ambiguous than not_used, which is not a row_format at all. */ - if (fil_space_t::full_crc32(m_space_flags)) - return ROW_TYPE_NOT_USED; - if (m_space_flags & FSP_FLAGS_MASK_ATOMIC_BLOBS) - { - if (FSP_FLAGS_GET_ZIP_SSIZE(m_space_flags)) - return ROW_TYPE_COMPRESSED; - else - return ROW_TYPE_DYNAMIC; - } - return ROW_TYPE_COMPACT; - } - - /** Update the import configuration that will be used to import - the tablespace. */ - dberr_t build_row_import(row_import* cfg) const UNIV_NOTHROW; - - /** Table definition in server. When the table is being created, - there's no table yet so m_table is nullptr */ - const dict_table_t* m_table; - - /** Table row format. Only used when a (stub) table is being created - in which case m_table is null, for obtaining row format from the - .ibd for the stub table. */ - enum row_type m_row_format; - - /** Index information */ - Index m_index; -}; /*****************************************************************//** Imports a tablespace. The space id in the .ibd file must match the space id @@ -349,29 +65,12 @@ dberr_t row_import_update_index_root(trx_t* trx, dict_table_t* table, bool reset) MY_ATTRIBUTE((nonnull, warn_unused_result)); -/********************************************************************//** -Iterate over all or some pages in the tablespace. -@param dir_path - the path to data dir storing the tablespace -@param name - the table name -@param n_io_buffers - number of blocks to read and write together -@param callback - functor that will do the page queries or updates -@return DB_SUCCESS or error code */ -dberr_t -fil_tablespace_iterate( -/*===================*/ - const char* dir_path, - const char* name, - ulint n_io_buffers, - AbstractCallback& callback); - -/** -Read the row type from a .cfg file. -@param[in] dir_path Path to the data directory containing the .cfg file -@param[in] name Name of the table -@param[in] thd Session -@param[out] result The row format read from the .cfg file -@return DB_SUCCESS or error code. */ -dberr_t get_row_type_from_cfg(const char* dir_path, const char* name, THD* thd, - rec_format_enum& result); +/** Prepare the create info to create a new stub table for import. +@param[in] thd Connection +@param[in] name Table name, format: "db/table_name". +@param[in,out] create_info The create info for creating a stub. +@return 0 if success else error number. */ +int prepare_create_stub_for_import(THD *thd, const char *name, + HA_CREATE_INFO& create_info); #endif /* row0import_h */ diff --git a/storage/innobase/row/row0import.cc b/storage/innobase/row/row0import.cc index 817c14e0087..a7d2d6a7f22 100644 --- a/storage/innobase/row/row0import.cc +++ b/storage/innobase/row/row0import.cc @@ -55,6 +55,11 @@ Created 2012-02-08 by Sunny Bains. using st_::span; +/** The size of the buffer to use for IO. +@param n physical page size +@return number of pages */ +#define IO_BUFFER_SIZE(n) ((1024 * 1024) / (n)) + /** For gathering stats on records during phase I */ struct row_stats_t { ulint m_n_deleted; /*!< Number of deleted records @@ -391,6 +396,194 @@ private: ulint m_n_rows; /*!< Records in index */ }; +/** Functor that is called for each physical page that is read from the +tablespace file. */ +class AbstractCallback +{ +public: + /** Constructor + @param trx covering transaction */ + AbstractCallback(trx_t* trx, uint32_t space_id) + : + m_zip_size(0), + m_trx(trx), + m_space(space_id), + m_xdes(), + m_xdes_page_no(UINT32_MAX), + m_space_flags(UINT32_MAX) UNIV_NOTHROW { } + + /** Free any extent descriptor instance */ + virtual ~AbstractCallback() + { + UT_DELETE_ARRAY(m_xdes); + } + + /** Determine the page size to use for traversing the tablespace + @param file_size size of the tablespace file in bytes + @param block contents of the first page in the tablespace file. + @retval DB_SUCCESS or error code. */ + virtual dberr_t init( + os_offset_t file_size, + const buf_block_t* block) UNIV_NOTHROW; + + /** @return true if compressed table. */ + bool is_compressed_table() const UNIV_NOTHROW + { + return get_zip_size(); + } + + /** @return the tablespace flags */ + uint32_t get_space_flags() const { return m_space_flags; } + + /** + Set the name of the physical file and the file handle that is used + to open it for the file that is being iterated over. + @param filename the physical name of the tablespace file + @param file OS file handle */ + void set_file(const char* filename, pfs_os_file_t file) UNIV_NOTHROW + { + m_file = file; + m_filepath = filename; + } + + ulint get_zip_size() const { return m_zip_size; } + ulint physical_size() const + { + return m_zip_size ? m_zip_size : srv_page_size; + } + + const char* filename() const { return m_filepath; } + + /** + Called for every page in the tablespace. If the page was not + updated then its state must be set to BUF_PAGE_NOT_USED. For + compressed tables the page descriptor memory will be at offset: + block->page.frame + srv_page_size; + @param block block read from file, note it is not from the buffer pool + @retval DB_SUCCESS or error code. */ + virtual dberr_t operator()(buf_block_t* block) UNIV_NOTHROW = 0; + + /** @return the tablespace identifier */ + uint32_t get_space_id() const { return m_space; } + + bool is_interrupted() const { return trx_is_interrupted(m_trx); } + + /** + Get the data page depending on the table type, compressed or not. + @param block - block read from disk + @retval the buffer frame */ + static byte* get_frame(const buf_block_t* block) + { + return block->page.zip.data + ? block->page.zip.data : block->page.frame; + } + + /** Invoke the functionality for the callback */ + virtual dberr_t run(const fil_iterator_t& iter, + buf_block_t* block) UNIV_NOTHROW = 0; + +protected: + /** Get the physical offset of the extent descriptor within the page. + @param page_no page number of the extent descriptor + @param page contents of the page containing the extent descriptor. + @return the start of the xdes array in a page */ + const xdes_t* xdes( + ulint page_no, + const page_t* page) const UNIV_NOTHROW + { + ulint offset; + + offset = xdes_calc_descriptor_index(get_zip_size(), page_no); + + return(page + XDES_ARR_OFFSET + XDES_SIZE * offset); + } + + /** Set the current page directory (xdes). If the extent descriptor is + marked as free then free the current extent descriptor and set it to + 0. This implies that all pages that are covered by this extent + descriptor are also freed. + + @param page_no offset of page within the file + @param page page contents + @return DB_SUCCESS or error code. */ + dberr_t set_current_xdes( + uint32_t page_no, + const page_t* page) UNIV_NOTHROW + { + m_xdes_page_no = page_no; + + UT_DELETE_ARRAY(m_xdes); + m_xdes = NULL; + + if (mach_read_from_4(XDES_ARR_OFFSET + XDES_STATE + page) + != XDES_FREE) { + const ulint physical_size = m_zip_size + ? m_zip_size : srv_page_size; + + m_xdes = UT_NEW_ARRAY_NOKEY(xdes_t, physical_size); + + /* Trigger OOM */ + DBUG_EXECUTE_IF( + "ib_import_OOM_13", + UT_DELETE_ARRAY(m_xdes); + m_xdes = NULL; + ); + + if (m_xdes == NULL) { + return(DB_OUT_OF_MEMORY); + } + + memcpy(m_xdes, page, physical_size); + } + + return(DB_SUCCESS); + } + + /** Check if the page is marked as free in the extent descriptor. + @param page_no page number to check in the extent descriptor. + @return true if the page is marked as free */ + bool is_free(uint32_t page_no) const UNIV_NOTHROW + { + ut_a(xdes_calc_descriptor_page(get_zip_size(), page_no) + == m_xdes_page_no); + + if (m_xdes != 0) { + const xdes_t* xdesc = xdes(page_no, m_xdes); + ulint pos = page_no % FSP_EXTENT_SIZE; + + return xdes_is_free(xdesc, pos); + } + + /* If the current xdes was free, the page must be free. */ + return(true); + } + +protected: + /** The ROW_FORMAT=COMPRESSED page size, or 0. */ + ulint m_zip_size; + + /** File handle to the tablespace */ + pfs_os_file_t m_file; + + /** Physical file path. */ + const char* m_filepath; + + /** Covering transaction. */ + trx_t* m_trx; + + /** Space id of the file being iterated over. */ + uint32_t m_space; + + /** Current extent descriptor page */ + xdes_t* m_xdes; + + /** Physical page offset in the file of the extent descriptor */ + uint32_t m_xdes_page_no; + + /** Flags value read from the header page */ + uint32_t m_space_flags; +}; + ATTRIBUTE_COLD static dberr_t invalid_space_flags(uint32_t flags) { if (fsp_flags_is_incompatible_mysql(flags)) @@ -472,19 +665,101 @@ static dberr_t fil_iterate( buf_block_t* block, AbstractCallback& callback); -/** Check the fsp flags from the table and header file match. Also -check against row format mismatch between the table and the index root -page, as we can't get that from the tablespace header flags alone. +/** +Try and determine the index root pages by checking if the next/prev +pointers are both FIL_NULL. We need to ensure that skip deleted pages. */ +struct FetchIndexRootPages : public AbstractCallback { + + /** Index information gathered from the .ibd file. */ + struct Index { + + Index(index_id_t id, uint32_t page_no) + : + m_id(id), + m_page_no(page_no) { } + + index_id_t m_id; /*!< Index id */ + uint32_t m_page_no; /*!< Root page number */ + }; + + /** Constructor + @param trx covering (user) transaction + @param table table definition in server .*/ + FetchIndexRootPages(const dict_table_t* table, trx_t* trx) + : + AbstractCallback(trx, UINT32_MAX), + m_table(table), m_index(0, 0) UNIV_NOTHROW { } + + FetchIndexRootPages() + : + AbstractCallback(nullptr, UINT32_MAX), + m_table(nullptr), m_index(0, 0) UNIV_NOTHROW { } + + /** Destructor */ + ~FetchIndexRootPages() UNIV_NOTHROW override = default; + + /** Fetch the clustered index root page in the tablespace + @param iter Tablespace iterator + @param block Block to use for IO + @retval DB_SUCCESS or error code */ + dberr_t run(const fil_iterator_t& iter, + buf_block_t* block) UNIV_NOTHROW override; + + /** Check that fsp flags and row formats match. + @param block block to convert, it is not from the buffer pool. + @retval DB_SUCCESS or error code. */ + dberr_t operator()(buf_block_t* block) UNIV_NOTHROW override; + + /** Get row format from the header and the root index page. */ + enum row_type get_row_format(buf_block_t *block) + { + if (!page_is_comp(block->page.frame)) + return ROW_TYPE_REDUNDANT; + /* With full_crc32 we cannot tell between dynamic or + compact, and return not_used. We cannot simply + return dynamic or compact, as the client of this + function will not be able to tell whether it is + dynamic because of this or the other branch + below. Returning default would also work if it is + immediately handled, but is still more ambiguous + than not_used, which is not a row_format at all. */ + if (fil_space_t::full_crc32(m_space_flags)) + return ROW_TYPE_NOT_USED; + if (m_space_flags & FSP_FLAGS_MASK_ATOMIC_BLOBS) + { + if (FSP_FLAGS_GET_ZIP_SSIZE(m_space_flags)) + return ROW_TYPE_COMPRESSED; + else + return ROW_TYPE_DYNAMIC; + } + return ROW_TYPE_COMPACT; + } + + /** Update the import configuration that will be used to import + the tablespace. */ + dberr_t build_row_import(row_import* cfg) const UNIV_NOTHROW; + + /** Table definition in server. When the table is being + created, there's no table yet so m_table is nullptr */ + const dict_table_t* m_table; + + /** Table row format. Only used when a (stub) table is being + created in which case m_table is null, for obtaining row + format from the .ibd for the stub table. */ + enum row_type m_row_format; + + /** Index information */ + Index m_index; +}; + +/** Called for each block as it is read from the file. Check index pages to +determine the exact row format. We can't get that from the tablespace +header flags alone. @param block block to convert, it is not from the buffer pool. @retval DB_SUCCESS or error code. */ dberr_t FetchIndexRootPages::operator()(buf_block_t* block) UNIV_NOTHROW { - /* m_table is non-null iff we are trying to create a (stub) - table, and there's no table to compare the fsp flags and row - format against. */ - ut_ad(m_table); - if (is_interrupted()) return DB_INTERRUPTED; const page_t* page = get_frame(block); @@ -3101,7 +3376,7 @@ static dberr_t handle_instant_metadata(dict_table_t *table, /** Read the contents of a .cfg file. @param[in] filename Path to the cfg file -@param[in] thd Session +@param[in] thd Connection @param[out] cfg Contents of the .cfg file. @return DB_SUCCESS or error code. */ static dberr_t row_import_read_cfg_internal(const char* filename, THD* thd, @@ -3152,31 +3427,52 @@ row_import_read_cfg( srv_get_meta_data_filename(table, name, sizeof(name)); - return row_import_read_cfg_internal(name, thd, cfg); + return row_import_read_cfg_internal(name, thd, cfg); +} + + +/** Convert the InnoDB ROW_FORMAT from rec_format_enum to row_type. +@param[in] from ROW_FORMAT as a rec_format_enum +@return the row_type representation of ROW_FORMAT. */ +static enum row_type from_rec_format(const rec_format_enum from) +{ + switch (from) { + case REC_FORMAT_COMPACT: + return ROW_TYPE_COMPACT; + case REC_FORMAT_DYNAMIC: + return ROW_TYPE_DYNAMIC; + case REC_FORMAT_REDUNDANT: + return ROW_TYPE_REDUNDANT; + case REC_FORMAT_COMPRESSED: + return ROW_TYPE_COMPRESSED; + /* Impossible. */ + default: + return ROW_TYPE_DEFAULT; + } } /** Read the row type from a .cfg file. -@param[in] dir_path Path to the data directory containing the .cfg file -@param[in] name Name of the table -@param[in] thd Session -@param[out] result The row format read from the .cfg file -@return DB_SUCCESS or error code. */ -dberr_t get_row_type_from_cfg(const char* dir_path, const char* name, - THD* thd, rec_format_enum& result) +@param dir_path Path to the data directory containing the .cfg file +@param name Name of the table +@param thd Connection +@return one of ROW_TYPE_COMPACT, ROW_TYPE_DYNAMIC, ROW_TYPE_REDUNDANT, + ROW_TYPE_COMPRESSED, ROW_TYPE_DEFAULT, or ROW_TYPE_NOT_USED to + signal error. */ +static enum row_type get_row_type_from_cfg(const char* dir_path, + const char* name, THD* thd) { char* filename = fil_make_filepath(dir_path, table_name_t(const_cast<char*>(name)), CFG, dir_path != nullptr); if (!filename) - return DB_OUT_OF_MEMORY; + return ROW_TYPE_NOT_USED; row_import cfg; dberr_t err = row_import_read_cfg_internal(filename, thd, cfg); - // ASAN ut_free(filename); if (err == DB_SUCCESS) - result = dict_tf_get_rec_format(cfg.m_flags); - return err; + return from_rec_format(dict_tf_get_rec_format(cfg.m_flags)); + return ROW_TYPE_NOT_USED; } /** Update the root page numbers and tablespace ID of a table. @@ -3516,9 +3812,8 @@ page_corrupted: && buf_page_is_corrupted(false, readptr, m_space_flags)) goto page_corrupted; - /* m_table_name is non-null iff we are trying to create a (stub) - table, in which case we want to get row format for the table - creation. */ + /* m_table is null iff we are trying to create a (stub) table, in + which case we want to get row format for the table creation. */ if (m_table) err= this->operator()(block); else @@ -3844,20 +4139,21 @@ func_exit: return err; } -/********************************************************************//** +/** Iterate over all or some pages in the tablespace. -@param dir_path - the path to data dir storing the tablespace -@param name - the table name -@param n_io_buffers - number of blocks to read and write together -@param callback - functor that will do the page queries or updates +@param dir_path the path to data dir storing the tablespace +@param name the table name +@param n_io_buffers number of blocks to read and write together +@param callback functor that will do the page queries or updates @return DB_SUCCESS or error code */ +static dberr_t fil_tablespace_iterate( /*===================*/ - const char* dir_path, - const char* name, - ulint n_io_buffers, - AbstractCallback& callback) + const char *name, + ulint n_io_buffers, + AbstractCallback &callback, + const char *dir_path) { dberr_t err; pfs_os_file_t file; @@ -3869,8 +4165,9 @@ fil_tablespace_iterate( DBUG_EXECUTE_IF("ib_import_trigger_corruption_1", return(DB_CORRUPTION);); - table_name_t table_name(const_cast<char*>(name)); - filepath = fil_make_filepath(dir_path, table_name, IBD, dir_path != nullptr); + table_name_t table_name(const_cast<char*>(name)); + filepath = fil_make_filepath(dir_path, table_name, IBD, + dir_path != nullptr); if (!filepath) { return(DB_OUT_OF_MEMORY); } else { @@ -3883,8 +4180,9 @@ fil_tablespace_iterate( if (!success) { /* The following call prints an error message */ os_file_get_last_error(true); - ib::error() << "could not open the tablespace file " - << filepath; + sql_print_error("InnoDB: could not open the " + "tablespace file %s.\n", + filepath); ut_free(filepath); return DB_TABLESPACE_NOT_FOUND; } else { @@ -3996,30 +4294,27 @@ fil_tablespace_iterate( return(err); } -/********************************************************************//** +/** Iterate over all or some pages in the tablespace. -@param table - the table definiton in the server -@param n_io_buffers - number of blocks to read and write together -@param callback - functor that will do the page queries or updates -@return DB_SUCCESS or error code */ +@param table the table definiton in the server +@param n_io_buffers number of blocks to read and write together +@param callback functor that will do the page queries or updates +@return DB_SUCCESS or error code */ static dberr_t fil_tablespace_iterate( /*===================*/ - dict_table_t* table, - ulint n_io_buffers, - AbstractCallback& callback) + dict_table_t *table, + ulint n_io_buffers, + AbstractCallback &callback) { - /* Make sure the data_dir_path is set. */ - dict_get_and_save_data_dir_path(table); - - ut_ad(!DICT_TF_HAS_DATA_DIR(table->flags) || table->data_dir_path); - - const char *data_dir_path = DICT_TF_HAS_DATA_DIR(table->flags) - ? table->data_dir_path : nullptr; - - return fil_tablespace_iterate(data_dir_path, table->name.m_name, - n_io_buffers, callback); + /* Make sure the data_dir_path is set. */ + dict_get_and_save_data_dir_path(table); + ut_ad(!DICT_TF_HAS_DATA_DIR(table->flags) || table->data_dir_path); + const char *data_dir_path = DICT_TF_HAS_DATA_DIR(table->flags) + ? table->data_dir_path : nullptr; + return fil_tablespace_iterate(table->name.m_name, n_io_buffers, callback, + data_dir_path); } /*****************************************************************//** @@ -4355,3 +4650,56 @@ row_import_for_mysql( return row_import_cleanup(prebuilt, err); } + +/** Prepare the create info to create a new stub table for import. +@param[in] thd Connection +@param[in] name Table name, format: "db/table_name". +@param[in,out] create_info The create info for creating a stub. +@return 0 if success else error number. */ +int prepare_create_stub_for_import(THD *thd, const char *name, + HA_CREATE_INFO& create_info) +{ + DBUG_ENTER("prepare_create_stub_for_import"); + FetchIndexRootPages fetchIndexRootPages; + if (fil_tablespace_iterate(name, IO_BUFFER_SIZE(srv_page_size), + fetchIndexRootPages, fil_path_to_mysql_datadir) + != DB_SUCCESS) + { + const char *ibd_path = fil_make_filepath( + fil_path_to_mysql_datadir, table_name_t(const_cast<char*>(name)), IBD, + true); + if (!ibd_path) + return(ER_ENGINE_OUT_OF_MEMORY); + sql_print_error("InnoDB: failed to get row format from %s.\n", + ibd_path); + DBUG_RETURN(ER_INNODB_IMPORT_ERROR); + } + create_info.init(); + /* get the row format from ibd. */ + create_info.row_type = fetchIndexRootPages.m_row_format; + /* if .cfg exists, get the row format from cfg, and compare with + ibd, report error if different, except when cfg reports + compact/dynamic and ibd reports not_used (indicating either compact + or dynamic but not sure) */ + const enum row_type row_type_from_cfg= + get_row_type_from_cfg(fil_path_to_mysql_datadir, name, thd); + if (row_type_from_cfg != ROW_TYPE_NOT_USED) + { + /* if ibd reports not_used but cfg reports compact or dynamic, go + with cfg. */ + if (create_info.row_type != row_type_from_cfg && + !((row_type_from_cfg == ROW_TYPE_COMPACT || + row_type_from_cfg == ROW_TYPE_DYNAMIC) && + create_info.row_type == ROW_TYPE_NOT_USED)) + { + sql_print_error( + "InnoDB: cfg and ibd disagree on row format for table %s.\n", + name); + DBUG_RETURN(ER_INNODB_IMPORT_ERROR); + } + else + create_info.row_type= row_type_from_cfg; + } else if (create_info.row_type == ROW_TYPE_NOT_USED) + create_info.row_type = ROW_TYPE_DYNAMIC; + DBUG_RETURN(0); +} |