summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYuchen Pei <yuchen.pei@mariadb.com>2023-03-22 10:33:45 +1100
committerYuchen Pei <yuchen.pei@mariadb.com>2023-03-22 10:33:45 +1100
commitf4cbd42401dde5d023bf618b5531a73b4b059af5 (patch)
tree86c8414b00b2715ff4c40b28fba713118887a839
parentb4f3c9c8c5c9bc95c8e142b2fe8f3345650554a6 (diff)
downloadmariadb-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.result129
-rw-r--r--mysql-test/suite/innodb/t/import_recovery.test269
-rw-r--r--mysql-test/suite/innodb/t/import_run_once.test4
-rw-r--r--storage/innobase/handler/ha_innodb.cc122
-rw-r--r--storage/innobase/handler/ha_innodb.h5
-rw-r--r--storage/innobase/include/row0import.h317
-rw-r--r--storage/innobase/row/row0import.cc458
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);
+}