From 6a2afb42ba86188ccda0972f9c2df363f34e10a0 Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Fri, 6 Jun 2025 18:42:30 +0530 Subject: [PATCH] MDEV-36487 Fix ha_innobase::check() for sequences InnoDB does the following check for sequence table during check table command: - There should be only one index should exist on sequence table - There should be only one row should exist on sequence table - The leaf page must be the root page for the sequence table - Delete marked record should not exist - DB_TRX_ID and DB_ROLL_PTR of the record should be 0 and 1U << 55 --- .../suite/innodb/r/check_sequence,debug.rdiff | 17 +++ .../suite/innodb/r/check_sequence.result | 114 +++++++++++++++ mysql-test/suite/innodb/t/check_sequence.test | 136 ++++++++++++++++++ mysql-test/suite/sql_sequence/check.result | 10 +- mysql-test/suite/sql_sequence/check.test | 5 + storage/innobase/handler/ha_innodb.cc | 116 +++++++++++++++ 6 files changed, 395 insertions(+), 3 deletions(-) create mode 100644 mysql-test/suite/innodb/r/check_sequence,debug.rdiff create mode 100644 mysql-test/suite/innodb/r/check_sequence.result create mode 100644 mysql-test/suite/innodb/t/check_sequence.test diff --git a/mysql-test/suite/innodb/r/check_sequence,debug.rdiff b/mysql-test/suite/innodb/r/check_sequence,debug.rdiff new file mode 100644 index 00000000000..6a86da5ad89 --- /dev/null +++ b/mysql-test/suite/innodb/r/check_sequence,debug.rdiff @@ -0,0 +1,17 @@ +--- check_sequence.result ++++ check_sequence,debug.result +@@ -112,3 +112,14 @@ + 3 + disconnect prevent_purge; + DROP SEQUENCE s1; ++CREATE SEQUENCE s ENGINE=InnoDB; ++ALTER TABLE s SEQUENCE=0; ++FLUSH TABLES; ++SET STATEMENT DEBUG_DBUG="+d,fail_root_page" FOR ++CHECK TABLE s; ++Table Op Msg_type Msg_text ++test.s check Warning InnoDB: Sequence table test/s is corrupted. ++test.s check error Corrupt ++ALTER TABLE s SEQUENCE=1; ++ERROR HY000: InnoDB: Table `test`.`s` is corrupted. ++DROP SEQUENCE s; diff --git a/mysql-test/suite/innodb/r/check_sequence.result b/mysql-test/suite/innodb/r/check_sequence.result new file mode 100644 index 00000000000..410d6a72375 --- /dev/null +++ b/mysql-test/suite/innodb/r/check_sequence.result @@ -0,0 +1,114 @@ +# +# MDEV-36487 Fix ha_innobase::check() for sequences +# +call mtr.add_suppression("InnoDB: Table test/s2 contains 1 indexes .*"); +call mtr.add_suppression("Table test/s2 has a primary key in InnoDB .*"); +CREATE SEQUENCE s ENGINE=InnoDB; +ALTER TABLE s SEQUENCE=0, ALGORITHM=INPLACE; +ERROR 0A000: ALGORITHM=INPLACE is not supported. Reason: SEQUENCE. Try ALGORITHM=COPY +ALTER TABLE s SEQUENCE=0, ALGORITHM=COPY; +FLUSH TABLES; +CHECK TABLE s; +Table Op Msg_type Msg_text +test.s check Warning InnoDB: Sequence table test/s has ROLLBACK enabled. +test.s check error Corrupt +ALTER TABLE s SEQUENCE=1; +ERROR HY000: InnoDB: Table `test`.`s` is corrupted. +DROP SEQUENCE s; +CREATE SEQUENCE s ENGINE=InnoDB; +CREATE TABLE s2 LIKE s; +ALTER TABLE s2 sequence=0; +INSERT INTO s2 VALUES (3,1,9223372036854775806,1,1,1000,0,0); +ALTER TABLE s2 ADD INDEX idx(start_value); +FLUSH TABLES; +CHECK TABLE s2; +Table Op Msg_type Msg_text +test.s2 check Warning InnoDB: Table test/s2 contains 1 indexes inside InnoDB, which is different from the number of indexes 0 defined in the MariaDB +test.s2 check Warning InnoDB: Sequence table test/s2 does have more than one indexes. +test.s2 check error Corrupt +ALTER TABLE s2 SEQUENCE=1; +ERROR HY000: InnoDB: Table `test`.`s2` is corrupted. +DROP SEQUENCE s; +DROP SEQUENCE s2; +CREATE SEQUENCE s ENGINE=InnoDB; +CREATE TABLE s2 LIKE s; +ALTER TABLE s2 sequence=0; +INSERT INTO s2 VALUES (3,2,9223372036854775806,2,2,1000,0,0); +ALTER TABLE s2 ADD PRIMARY KEY(start_value); +FLUSH TABLES; +CHECK TABLE s2; +Table Op Msg_type Msg_text +test.s2 check Warning InnoDB: Table test/s2 has a primary key in InnoDB data dictionary, but not in MariaDB! +test.s2 check Warning InnoDB: Table test/s2 contains 1 indexes inside InnoDB, which is different from the number of indexes 0 defined in the MariaDB +test.s2 check Warning InnoDB: Sequence table test/s2 does not have generated clustered index. +test.s2 check error Corrupt +ALTER TABLE s2 SEQUENCE=1; +ERROR HY000: InnoDB: Table `test`.`s2` is corrupted. +DROP SEQUENCE s; +DROP SEQUENCE s2; +CREATE SEQUENCE s ENGINE=InnoDB; +CREATE TABLE s2 LIKE s; +ALTER TABLE s2 sequence=0; +INSERT INTO s2 VALUES (3,1,9223372036854775806,1,1,1000,0,0); +DELETE FROM s2; +InnoDB 0 transactions not purged +FLUSH TABLES; +CHECK TABLE s2; +Table Op Msg_type Msg_text +test.s2 check Warning InnoDB: Should have only one record in sequence table test/s2. But it has 0 records. +test.s2 check error Corrupt +ALTER TABLE s2 SEQUENCE=1; +ERROR HY000: InnoDB: Table `test`.`s2` is corrupted. +DROP SEQUENCE s; +DROP SEQUENCE s2; +CREATE SEQUENCE s ENGINE=InnoDB; +CREATE TABLE s2 LIKE s; +ALTER TABLE s2 sequence=0; +INSERT INTO s2 select seq, seq, seq, seq, seq, seq, 1, seq from +seq_1_to_200; +FLUSH TABLES; +CHECK TABLE s2; +Table Op Msg_type Msg_text +test.s2 check Warning InnoDB: Non leaf page exists for sequence table test/s2. +test.s2 check error Corrupt +ALTER TABLE s2 SEQUENCE=1; +ERROR HY000: InnoDB: Table `test`.`s2` is corrupted. +DROP SEQUENCE s; +DROP SEQUENCE s2; +CREATE SEQUENCE s ENGINE=InnoDB; +CREATE TABLE s2 LIKE s; +ALTER TABLE s2 sequence=0; +DELETE FROM s2; +InnoDB 0 transactions not purged +connect prevent_purge,localhost,root; +START TRANSACTION WITH CONSISTENT SNAPSHOT; +connection default; +INSERT INTO s2 VALUES (3,1,9223372036854775806,1,1,1000,0,0); +FLUSH TABLES; +CHECK TABLE s2; +Table Op Msg_type Msg_text +test.s2 check Warning InnoDB: Record in sequence table test/s2 is corrupted. +test.s2 check error Corrupt +ALTER TABLE s2 SEQUENCE=1; +ERROR HY000: InnoDB: Table `test`.`s2` is corrupted. +DROP SEQUENCE s; +DROP SEQUENCE s2; +CREATE SEQUENCE s1 ENGINE=InnoDB; +CHECK TABLE s1; +Table Op Msg_type Msg_text +test.s1 check status OK +connection prevent_purge; +START TRANSACTION WITH CONSISTENT SNAPSHOT; +connection default; +INSERT INTO s1 VALUES (3,1,9223372036854775806,1,1,1000,0,0); +SELECT * FROM s1; +next_not_cached_value minimum_value maximum_value start_value increment cache_size cycle_option cycle_count +3 1 9223372036854775806 1 1 1000 0 0 +CHECK TABLE s1; +Table Op Msg_type Msg_text +test.s1 check status OK +select nextval(s1); +nextval(s1) +3 +disconnect prevent_purge; +DROP SEQUENCE s1; diff --git a/mysql-test/suite/innodb/t/check_sequence.test b/mysql-test/suite/innodb/t/check_sequence.test new file mode 100644 index 00000000000..a8b26b3fa61 --- /dev/null +++ b/mysql-test/suite/innodb/t/check_sequence.test @@ -0,0 +1,136 @@ +--source include/have_innodb.inc +--source include/have_sequence.inc +--source include/maybe_debug.inc +--echo # +--echo # MDEV-36487 Fix ha_innobase::check() for sequences +--echo # + +call mtr.add_suppression("InnoDB: Table test/s2 contains 1 indexes .*"); +call mtr.add_suppression("Table test/s2 has a primary key in InnoDB .*"); +# Sequence table which has NO_ROLLBACK flag set +let $datadir=`select @@datadir`; +CREATE SEQUENCE s ENGINE=InnoDB; +copy_file $datadir/test/s.frm $datadir/test/s1.frm; +--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON +ALTER TABLE s SEQUENCE=0, ALGORITHM=INPLACE; +ALTER TABLE s SEQUENCE=0, ALGORITHM=COPY; +FLUSH TABLES; +remove_file $datadir/test/s.frm; +move_file $datadir/test/s1.frm $datadir/test/s.frm; +CHECK TABLE s; +--error ER_TABLE_CORRUPT +ALTER TABLE s SEQUENCE=1; +DROP SEQUENCE s; + +# Checks for more than one index +CREATE SEQUENCE s ENGINE=InnoDB; +copy_file $datadir/test/s.frm $datadir/test/orig.frm; +CREATE TABLE s2 LIKE s; +ALTER TABLE s2 sequence=0; +INSERT INTO s2 VALUES (3,1,9223372036854775806,1,1,1000,0,0); +ALTER TABLE s2 ADD INDEX idx(start_value); +FLUSH TABLES; +move_file $datadir/test/orig.frm $datadir/test/s2.frm; +CHECK TABLE s2; +--error ER_TABLE_CORRUPT +ALTER TABLE s2 SEQUENCE=1; +DROP SEQUENCE s; +DROP SEQUENCE s2; + +# Checks for generated clustered index +CREATE SEQUENCE s ENGINE=InnoDB; +copy_file $datadir/test/s.frm $datadir/test/orig.frm; +CREATE TABLE s2 LIKE s; +ALTER TABLE s2 sequence=0; +INSERT INTO s2 VALUES (3,2,9223372036854775806,2,2,1000,0,0); +ALTER TABLE s2 ADD PRIMARY KEY(start_value); +FLUSH TABLES; +move_file $datadir/test/orig.frm $datadir/test/s2.frm; +CHECK TABLE s2; +--error ER_TABLE_CORRUPT +ALTER TABLE s2 SEQUENCE=1; +DROP SEQUENCE s; +DROP SEQUENCE s2; + +# Should contain only one record +CREATE SEQUENCE s ENGINE=InnoDB; +copy_file $datadir/test/s.frm $datadir/test/orig.frm; +CREATE TABLE s2 LIKE s; +ALTER TABLE s2 sequence=0; +INSERT INTO s2 VALUES (3,1,9223372036854775806,1,1,1000,0,0); +DELETE FROM s2; +--source include/wait_all_purged.inc +FLUSH TABLES; +move_file $datadir/test/orig.frm $datadir/test/s2.frm; +CHECK TABLE s2; +--error ER_TABLE_CORRUPT +ALTER TABLE s2 SEQUENCE=1; +DROP SEQUENCE s; +DROP SEQUENCE s2; + +# More than one page +CREATE SEQUENCE s ENGINE=InnoDB; +copy_file $datadir/test/s.frm $datadir/test/orig.frm; +CREATE TABLE s2 LIKE s; +ALTER TABLE s2 sequence=0; +INSERT INTO s2 select seq, seq, seq, seq, seq, seq, 1, seq from +seq_1_to_200; +FLUSH TABLES; +move_file $datadir/test/orig.frm $datadir/test/s2.frm; +CHECK TABLE s2; +--error ER_TABLE_CORRUPT +ALTER TABLE s2 SEQUENCE=1; +DROP SEQUENCE s; +DROP SEQUENCE s2; + +# Checks for DB_TRX_ID & DB_ROLL_PTR in the record +CREATE SEQUENCE s ENGINE=InnoDB; +copy_file $datadir/test/s.frm $datadir/test/orig.frm; +CREATE TABLE s2 LIKE s; +ALTER TABLE s2 sequence=0; +DELETE FROM s2; +--source include/wait_all_purged.inc +--connect (prevent_purge,localhost,root) +START TRANSACTION WITH CONSISTENT SNAPSHOT; +--connection default +INSERT INTO s2 VALUES (3,1,9223372036854775806,1,1,1000,0,0); +FLUSH TABLES; +move_file $datadir/test/orig.frm $datadir/test/s2.frm; +CHECK TABLE s2; +--error ER_TABLE_CORRUPT +ALTER TABLE s2 SEQUENCE=1; +DROP SEQUENCE s; +DROP SEQUENCE s2; + +# Insert a row into a sequence table updates that row +CREATE SEQUENCE s1 ENGINE=InnoDB; +CHECK TABLE s1; + +--connection prevent_purge +START TRANSACTION WITH CONSISTENT SNAPSHOT; + +--connection default +INSERT INTO s1 VALUES (3,1,9223372036854775806,1,1,1000,0,0); +SELECT * FROM s1; +CHECK TABLE s1; +--disable_ps2_protocol +select nextval(s1); +--enable_ps2_protocol +--disconnect prevent_purge +DROP SEQUENCE s1; + +if ($have_debug) +{ +# Root page is corrupted +CREATE SEQUENCE s ENGINE=InnoDB; +copy_file $datadir/test/s.frm $datadir/test/s1.frm; +ALTER TABLE s SEQUENCE=0; +FLUSH TABLES; +remove_file $datadir/test/s.frm; +move_file $datadir/test/s1.frm $datadir/test/s.frm; +SET STATEMENT DEBUG_DBUG="+d,fail_root_page" FOR +CHECK TABLE s; +--error ER_TABLE_CORRUPT +ALTER TABLE s SEQUENCE=1; +DROP SEQUENCE s; +} diff --git a/mysql-test/suite/sql_sequence/check.result b/mysql-test/suite/sql_sequence/check.result index 9439bd5ff22..4fc012b208b 100644 --- a/mysql-test/suite/sql_sequence/check.result +++ b/mysql-test/suite/sql_sequence/check.result @@ -120,12 +120,16 @@ drop sequence s; # CREATE SEQUENCE s engine=innodb; ALTER TABLE s sequence=0; +connect prevent_purge,localhost,root; +START TRANSACTION WITH CONSISTENT SNAPSHOT; +connection default; delete from s; FLUSH TABLES; CHECK TABLE s; Table Op Msg_type Msg_text -test.s check Error Fewer than one row in the table +test.s check Warning InnoDB: Encountered delete marked record in sequence table test/s. test.s check error Corrupt +disconnect prevent_purge; DROP SEQUENCE s; CREATE SEQUENCE s engine=innodb; CHECK TABLE s; @@ -138,6 +142,6 @@ insert into s values (2,1,9223372036854775806,1,1,1000,0,0); FLUSH TABLES; CHECK TABLE s; Table Op Msg_type Msg_text -test.s check Warning More than one row in the table -test.s check status OK +test.s check Warning InnoDB: Should have only one record in sequence table test/s. But it has 2 records. +test.s check error Corrupt DROP SEQUENCE s; diff --git a/mysql-test/suite/sql_sequence/check.test b/mysql-test/suite/sql_sequence/check.test index 04e48a44ddf..f4b65f2f0d7 100644 --- a/mysql-test/suite/sql_sequence/check.test +++ b/mysql-test/suite/sql_sequence/check.test @@ -147,11 +147,16 @@ let $datadir=`select @@datadir`; CREATE SEQUENCE s engine=innodb; copy_file $datadir/test/s.frm $datadir/test/s1.frm; ALTER TABLE s sequence=0; +--connect (prevent_purge,localhost,root) +START TRANSACTION WITH CONSISTENT SNAPSHOT; + +--connection default delete from s; FLUSH TABLES; remove_file $datadir/test/s.frm; move_file $datadir/test/s1.frm $datadir/test/s.frm; CHECK TABLE s; +--disconnect prevent_purge DROP SEQUENCE s; # Just one row, check ok diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 69e8136d3f6..fc5c5cfbb52 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -15197,6 +15197,117 @@ ha_innobase::optimize( return try_alter ? HA_ADMIN_TRY_ALTER : HA_ADMIN_OK; } +/** Does the following validation check for the sequence table +1) Check whether the InnoDB table has no_rollback flags +2) Should have only one primary index +3) Root index page must be leaf page +4) There should be only one record in leaf page +5) There shouldn't be delete marked record in leaf page +6) DB_TRX_ID, DB_ROLL_PTR in the record should be 0 and 1U << 55 +@param thd Thread +@param table InnoDB table +@retval true if validation succeeds or false if validation fails */ +static bool innobase_sequence_table_check(THD *thd, dict_table_t *table) +{ + fil_space_t *space= table->space; + dict_index_t *clust_index= dict_table_get_first_index(table); + mtr_t mtr; + bool corruption= false; + const rec_t *rec= nullptr; + buf_block_t *root_block= nullptr; + + if (UT_LIST_GET_LEN(table->indexes) != 1) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NOT_KEYFILE, + "InnoDB: Sequence table %s does have more than one " + "indexes.", table->name.m_name); + corruption= true; + goto func_exit; + } + + if (!clust_index->is_gen_clust()) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NOT_KEYFILE, + "InnoDB: Sequence table %s does not have generated " + "clustered index.", table->name.m_name); + corruption= true; + goto func_exit; + } + + mtr.start(); + mtr.set_named_space(space); + root_block= buf_page_get_gen(page_id_t(space->id, clust_index->page), + space->zip_size(), RW_S_LATCH, nullptr, BUF_GET, + &mtr); + DBUG_EXECUTE_IF("fail_root_page", root_block= nullptr;); + if (!root_block) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NOT_KEYFILE, + "InnoDB: Sequence table %s is corrupted.", + table->name.m_name); + corruption= true; + goto err_exit; + } + + if (!page_is_leaf(root_block->page.frame)) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NOT_KEYFILE, + "InnoDB: Non leaf page exists for sequence table %s.", + table->name.m_name); + corruption= true; + goto err_exit; + } + + if (page_get_n_recs(root_block->page.frame) != 1) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NOT_KEYFILE, + "InnoDB: Should have only one record in sequence " + "table %s. But it has %u records.", table->name.m_name, + page_get_n_recs(root_block->page.frame)); + corruption= true; + goto err_exit; + } + + rec= page_rec_get_next(page_get_infimum_rec(root_block->page.frame)); + + if (rec_get_deleted_flag(rec, dict_table_is_comp(table))) + { + corruption= true; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NOT_KEYFILE, + "InnoDB: Encountered delete marked record in sequence " + "table %s.", table->name.m_name); + goto err_exit; + } + + if (trx_read_trx_id(rec + clust_index->trx_id_offset) != 0 || + trx_read_roll_ptr(rec + clust_index->trx_id_offset + DATA_TRX_ID_LEN) != + roll_ptr_t{1} << ROLL_PTR_INSERT_FLAG_POS) + { + corruption= true; + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NOT_KEYFILE, + "InnoDB: Record in sequence table %s is corrupted.", + table->name.m_name); + goto err_exit; + } + + if (!table->no_rollback()) + { + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_NOT_KEYFILE, + "InnoDB: Sequence table %s has ROLLBACK enabled.", + table->name.m_name); + corruption= true; + } +err_exit: + mtr.commit(); +func_exit: + if (corruption) + { + dict_set_corrupted(clust_index, "Table corruption"); + return false; + } + return true; +} + /*******************************************************************//** Tries to check that an InnoDB table is not corrupted. If corruption is noticed, prints to stderr information about it. In case of corruption @@ -15265,6 +15376,11 @@ ha_innobase::check( table->s->table_name.str); DBUG_RETURN(HA_ADMIN_CORRUPT); + } else if (table->s->table_type == TABLE_TYPE_SEQUENCE) { + DBUG_RETURN( + innobase_sequence_table_check(thd, m_prebuilt->table) + ? HA_ADMIN_OK + : HA_ADMIN_CORRUPT); } m_prebuilt->trx->op_info = "checking table";