diff --git a/mysql-test/suite/sql_sequence/aria.result b/mysql-test/suite/sql_sequence/aria.result index cfc7d946772..83c9b4f22eb 100644 --- a/mysql-test/suite/sql_sequence/aria.result +++ b/mysql-test/suite/sql_sequence/aria.result @@ -32,14 +32,14 @@ drop sequence t1; create sequence s1; check table s1; Table Op Msg_type Msg_text -test.s1 check note The storage engine for the table doesn't support check +test.s1 check status OK select next value for s1; next value for s1 1 flush tables; check table s1; Table Op Msg_type Msg_text -test.s1 check note The storage engine for the table doesn't support check +test.s1 check status OK select next value for s1; next value for s1 1001 diff --git a/mysql-test/suite/sql_sequence/binlog.result b/mysql-test/suite/sql_sequence/binlog.result index 32788c988fc..8996a470bfa 100644 --- a/mysql-test/suite/sql_sequence/binlog.result +++ b/mysql-test/suite/sql_sequence/binlog.result @@ -23,7 +23,7 @@ Table Op Msg_type Msg_text test.s1 repair status OK check table s1; Table Op Msg_type Msg_text -test.s1 check note The storage engine for the table doesn't support check +test.s1 check status OK rename table s1 to tmp_s; rename table tmp_s to s1; drop sequence s1; diff --git a/mysql-test/suite/sql_sequence/check.result b/mysql-test/suite/sql_sequence/check.result new file mode 100644 index 00000000000..2bf644f022c --- /dev/null +++ b/mysql-test/suite/sql_sequence/check.result @@ -0,0 +1,117 @@ +create sequence s; +call mtr.add_suppression("ha_myisam"); +call mtr.add_suppression("Checking table"); +check table s; +Table Op Msg_type Msg_text +test.s check error Size of datafile is: 4 Should be: 58 +test.s check error Corrupt +drop table s; +create sequence s; +insert into s values (3,1,9223372036854775806,1,1,1000,0,0); +select * from s; +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 s; +Table Op Msg_type Msg_text +test.s check status OK +select nextval(s); +nextval(s) +3 +drop sequence s; +CREATE SEQUENCE s; +ALTER TABLE s sequence=0; +insert into s values (3,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 +DROP SEQUENCE s; +CREATE SEQUENCE s; +ALTER TABLE s sequence=0; +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 error Corrupt +DROP SEQUENCE s; +CREATE SEQUENCE s; +ALTER TABLE s sequence=0; +update s set minimum_value=200, maximum_value=100; +FLUSH TABLES; +CHECK TABLE s; +Table Op Msg_type Msg_text +test.s check Error Sequence 'test.s' has out of range value for options +test.s check error Corrupt +DROP SEQUENCE s; +create sequence s minvalue 13 maxvalue 15 increment by 4; +check table s; +Table Op Msg_type Msg_text +test.s check status OK +select nextval(s); +nextval(s) +13 +check table s; +Table Op Msg_type Msg_text +test.s check Warning Sequence 'test.s' has run out +test.s check status OK +alter sequence s cycle; +check table s; +Table Op Msg_type Msg_text +test.s check status OK +alter sequence s nocycle; +check table s; +Table Op Msg_type Msg_text +test.s check Warning Sequence 'test.s' has run out +test.s check status OK +alter sequence s increment by 1; +check table s; +Table Op Msg_type Msg_text +test.s check Warning Sequence 'test.s' has run out +test.s check status OK +alter sequence s increment by 4; +select nextval(s); +ERROR HY000: Sequence 'test.s' has run out +alter sequence s cycle; +check table s; +Table Op Msg_type Msg_text +test.s check status OK +alter sequence s maxvalue 23 nocycle; +check table s; +Table Op Msg_type Msg_text +test.s check status OK +alter sequence s maxvalue 15; +check table s; +Table Op Msg_type Msg_text +test.s check Warning Sequence 'test.s' has run out +test.s check status OK +drop sequence s; +create sequence s minvalue 13 maxvalue 20 increment by 1; +select nextval(s); +nextval(s) +13 +check table s; +Table Op Msg_type Msg_text +test.s check status OK +select nextval(s); +nextval(s) +14 +drop sequence s; +create sequence s minvalue 13 maxvalue 20 increment by 1; +select nextval(s); +nextval(s) +13 +alter table s sequence=0; +alter table s sequence=1; +check table s; +Table Op Msg_type Msg_text +test.s check Warning Sequence 'test.s' has run out +test.s check status OK +select nextval(s); +ERROR HY000: Sequence 'test.s' has run out +drop sequence s; +create sequence s; +update s set minimum_value=500, maximum_value=200; +ERROR HY000: Storage engine SEQUENCE of the table `test`.`s` doesn't have this option +drop sequence s; diff --git a/mysql-test/suite/sql_sequence/check.test b/mysql-test/suite/sql_sequence/check.test new file mode 100644 index 00000000000..2d88478d663 --- /dev/null +++ b/mysql-test/suite/sql_sequence/check.test @@ -0,0 +1,139 @@ +--source include/have_innodb.inc + +# Check failure of underlying engine +create sequence s; +let $datadir= `select @@datadir`; +remove_file $datadir/test/s.MYD; +write_file $datadir/test/s.MYD; +foo +EOF +call mtr.add_suppression("ha_myisam"); +call mtr.add_suppression("Checking table"); +check table s; +drop table s; + +# Insert a row into a sequence table updates that row +create sequence s; +insert into s values (3,1,9223372036854775806,1,1,1000,0,0); +select * from s; +# ok +check table s; +--disable_ps2_protocol +select nextval(s); +--enable_ps2_protocol +drop sequence s; + +# More than one row +let $datadir=`select @@datadir`; +CREATE SEQUENCE s; +copy_file $datadir/test/s.frm $datadir/test/s1.frm; +ALTER TABLE s sequence=0; +insert into s values (3,1,9223372036854775806,1,1,1000,0,0); +FLUSH TABLES; +remove_file $datadir/test/s.frm; +move_file $datadir/test/s1.frm $datadir/test/s.frm; +CHECK TABLE s; +DROP SEQUENCE s; + +# Fewer than one row +let $datadir=`select @@datadir`; +CREATE SEQUENCE s; +copy_file $datadir/test/s.frm $datadir/test/s1.frm; +ALTER TABLE s sequence=0; +delete from s; +FLUSH TABLES; +remove_file $datadir/test/s.frm; +move_file $datadir/test/s1.frm $datadir/test/s.frm; +CHECK TABLE s; +DROP SEQUENCE s; + +# Wrong metadata (minvalue > maxvalue) +let $datadir=`select @@datadir`; +CREATE SEQUENCE s; +copy_file $datadir/test/s.frm $datadir/test/s1.frm; +ALTER TABLE s sequence=0; +update s set minimum_value=200, maximum_value=100; +FLUSH TABLES; +remove_file $datadir/test/s.frm; +move_file $datadir/test/s1.frm $datadir/test/s.frm; +CHECK TABLE s; +DROP SEQUENCE s; + +# Sequence run out tests. +# +# General principle: CHECK TABLE of a sequence table returns OK with a +# warning of ER_SEQUENCE_RUN_OUT if a SELECT NEXTVAL of the sequence +# in place of the CHECK TABLE statement would report +# ER_SEQUENCE_RUN_OUT. +create sequence s minvalue 13 maxvalue 15 increment by 4; +check table s; +--disable_ps2_protocol +select nextval(s); +--enable_ps2_protocol +check table s; +alter sequence s cycle; +check table s; +alter sequence s nocycle; +check table s; +# Still get run out because next_free_value has not changed. same +# would happen with a SELECT NEXTVAL(s) statement without the +# preceding check table statement. +alter sequence s increment by 1; +check table s; +alter sequence s increment by 4; +# If all_values_used is true, and then we make sequence cycle, check +# table will be ok without warning, as expected. this is because the +# ALTER SEQUENCE statement causes all_values_used to be reset. +--disable_ps2_protocol +--error ER_SEQUENCE_RUN_OUT +select nextval(s); +--enable_ps2_protocol +alter sequence s cycle; +check table s; +alter sequence s maxvalue 23 nocycle; +check table s; +alter sequence s maxvalue 15; +check table s; +drop sequence s; + +# CHECK TABLE calls sequence_definition::check_and_adjust() with +# adjust_next=false, so that there will be no flushing of +# next_free_value in this call, hence no running out +create sequence s minvalue 13 maxvalue 20 increment by 1; +--disable_ps2_protocol +select nextval(s); +--enable_ps2_protocol +check table s; +--disable_ps2_protocol +select nextval(s); +--enable_ps2_protocol +drop sequence s; + +# Without the CHECK TABLE statement below, the ALTER TABLE ... +# SEQUENCE=1 statement would still cause flushing in subsequent SELECT +# NEXTVAL statement (initialized == SQUENCE:SQL_UNITIALIZED => +# read_initial_value() => => sequence_defitinion::adjust_values()), +# resulting in sequence running out. + +# Same effect takes place with ALTER SEQUENCE, though different cause: +# in ALTER SEQUENCE, sequence_defitinion::adjust_values() is called in +# sequence_definition::check_and_adjust() which is called in +# Sql_cmd_alter_sequence::execute() +create sequence s minvalue 13 maxvalue 20 increment by 1; +--disable_ps2_protocol +select nextval(s); +--enable_ps2_protocol +alter table s sequence=0; +alter table s sequence=1; +check table s; +--disable_ps2_protocol +--error ER_SEQUENCE_RUN_OUT +select nextval(s); +--enable_ps2_protocol +drop sequence s; + +# UPDATE is banned by the storage engine - no need to check. +create sequence s; +--error ER_ILLEGAL_HA +update s set minimum_value=500, maximum_value=200; +drop sequence s; diff --git a/mysql-test/suite/sql_sequence/other.result b/mysql-test/suite/sql_sequence/other.result index 0c6908b0b41..4f3c679f2ad 100644 --- a/mysql-test/suite/sql_sequence/other.result +++ b/mysql-test/suite/sql_sequence/other.result @@ -4,14 +4,14 @@ create sequence s1 engine=innodb; check table s1; Table Op Msg_type Msg_text -test.s1 check note The storage engine for the table doesn't support check +test.s1 check status OK select next value for s1; next value for s1 1 flush tables; check table s1; Table Op Msg_type Msg_text -test.s1 check note The storage engine for the table doesn't support check +test.s1 check status OK select next value for s1; next value for s1 1001 @@ -32,7 +32,7 @@ Table Op Msg_type Msg_text test.s1 repair status OK check table s1; Table Op Msg_type Msg_text -test.s1 check note The storage engine for the table doesn't support check +test.s1 check status OK select next value for s1; next value for s1 1001 diff --git a/sql/ha_sequence.cc b/sql/ha_sequence.cc index f89f076da4a..e2cf7a8c3b1 100644 --- a/sql/ha_sequence.cc +++ b/sql/ha_sequence.cc @@ -420,6 +420,50 @@ void ha_sequence::print_error(int error, myf errflag) DBUG_VOID_RETURN; } +int ha_sequence::check(THD* thd, HA_CHECK_OPT* check_opt) +{ + DBUG_ENTER("ha_sequence::check"); + /* Check the underlying engine */ + if (int ret= file->check(thd, check_opt)) + DBUG_RETURN(ret); + /* Check number of rows */ + if ((file->table_flags() & HA_STATS_RECORDS_IS_EXACT)) + { + if (file->stats.records > 1) + push_warning(thd, Sql_condition::WARN_LEVEL_WARN, + ER_SEQUENCE_TABLE_HAS_TOO_MANY_ROWS, + ER_THD(thd, ER_SEQUENCE_TABLE_HAS_TOO_MANY_ROWS)); + else if (file->stats.records == 0) + { + my_error(ER_SEQUENCE_TABLE_HAS_TOO_FEW_ROWS, MYF(0)); + DBUG_RETURN(HA_ADMIN_CORRUPT); + } + } + /* + Initialise the sequence from the table if needed. + */ + if (sequence->initialized == SEQUENCE::SEQ_UNINTIALIZED) + { + if (sequence->read_stored_values(table)) + DBUG_RETURN(HA_ADMIN_FAILED); + else + sequence->initialized= SEQUENCE::SEQ_READY_TO_USE; + } + DBUG_ASSERT(sequence->initialized == SEQUENCE::SEQ_READY_TO_USE); + /* Check and adjust sequence state */ + if (sequence->check_and_adjust(thd, false, /*adjust_next=*/false)) + { + print_error(HA_ERR_SEQUENCE_INVALID_DATA, MYF(0)); + DBUG_RETURN(HA_ADMIN_CORRUPT); + } + /* Check value not exhausted */ + if (sequence->has_run_out()) + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_SEQUENCE_RUN_OUT, ER_THD(thd, ER_SEQUENCE_RUN_OUT), + table->s->db.str, table->s->table_name.str); + DBUG_RETURN(0); +} + /***************************************************************************** Sequence plugin interface *****************************************************************************/ diff --git a/sql/ha_sequence.h b/sql/ha_sequence.h index 8eccbe25bbf..f2a9251fa55 100644 --- a/sql/ha_sequence.h +++ b/sql/ha_sequence.h @@ -159,6 +159,7 @@ public: { return file->is_crashed(); } void column_bitmaps_signal() override { return file->column_bitmaps_signal(); } + int check(THD* thd, HA_CHECK_OPT* check_opt) override; /* New methods */ void register_original_handler(handler *file_arg) diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 4d257985c0c..c7d132c3e3d 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -12308,3 +12308,7 @@ ER_LATITUDE_OUT_OF_RANGE eng "Latitude %f is out of range in function %s. It must be within [-90.000000, 90.000000]." ER_GIS_DIFFERENT_SRIDS_AGGREGATION eng "Arguments to function %s contains geometries with different SRIDs: %d and %d. All geometries must have the same SRID." +ER_SEQUENCE_TABLE_HAS_TOO_FEW_ROWS + eng "Fewer than one row in the table" +ER_SEQUENCE_TABLE_HAS_TOO_MANY_ROWS + eng "More than one row in the table" diff --git a/sql/sql_sequence.cc b/sql/sql_sequence.cc index 976c0dcbb65..ab72205f32f 100644 --- a/sql/sql_sequence.cc +++ b/sql/sql_sequence.cc @@ -161,16 +161,19 @@ longlong sequence_definition::truncate_value(const Longlong_hybrid& original) /** Check whether sequence values are valid. - + Sets default values for fields that are not used, according to Oracle spec. @param in thd The connection @param in set_reserved_until Whether to set reserved_until to start - + @param in adjust_next Whether to call flush + next_free_value. Default to true + @retval false valid true invalid */ -bool sequence_definition::check_and_adjust(THD *thd, bool set_reserved_until) +bool sequence_definition::check_and_adjust(THD *thd, bool set_reserved_until, + bool adjust_next) { DBUG_ENTER("sequence_definition::check_and_adjust"); @@ -241,7 +244,8 @@ bool sequence_definition::check_and_adjust(THD *thd, bool set_reserved_until) if (set_reserved_until) reserved_until= start; - adjust_values(reserved_until); + if (adjust_next) + adjust_values(reserved_until); /* To ensure that cache * real_increment will never overflow */ const longlong max_increment= (real_increment ? @@ -758,7 +762,11 @@ int SEQUENCE::read_stored_values(TABLE *table) /* - Adjust values after reading a the stored state + Adjust next_free_value after reading a the stored state + + Also assign auto_increment_increment to real_increment if increment + is 0, though this assignment may have already happened (e.g. in + check_and_adjust()) */ void sequence_definition::adjust_values(longlong next_value) diff --git a/sql/sql_sequence.h b/sql/sql_sequence.h index 7e916d7776e..3dd1142c609 100644 --- a/sql/sql_sequence.h +++ b/sql/sql_sequence.h @@ -95,7 +95,8 @@ public: longlong value_type_max(); /* min value for the value type, e.g. -32768 for smallint. */ longlong value_type_min(); - bool check_and_adjust(THD *thd, bool set_reserved_until); + bool check_and_adjust(THD *thd, bool set_reserved_until, + bool adjust_next= true); void store_fields(TABLE *table); void read_fields(TABLE *table); int write_initial_sequence(TABLE *table); @@ -153,6 +154,13 @@ public: longlong next_value(TABLE *table, bool second_round, int *error); int set_value(TABLE *table, longlong next_value, ulonglong round_arg, bool is_used); + bool has_run_out() + { + return all_values_used || + (!cycle && + !within_bound(next_free_value, max_value + 1, min_value - 1, + real_increment > 0)); + } bool all_values_used; seq_init initialized;