From 6294516a56b3ae3d4b72326a61cfe9fe014b56c7 Mon Sep 17 00:00:00 2001 From: Nikita Malyavin Date: Mon, 4 Feb 2019 09:37:39 +1000 Subject: [PATCH] MDEV-16975 Application-time periods: ALTER TABLE * implicit period constraint is hidden and cannot be dropped independently * create...like and create...select support --- mysql-test/suite/period/r/alter.result | 174 ++++++++++++++++++++++++ mysql-test/suite/period/r/create.result | 11 +- mysql-test/suite/period/t/alter.test | 131 ++++++++++++++++++ mysql-test/suite/period/t/create.test | 4 + sql/field.h | 2 +- sql/handler.cc | 30 ++-- sql/handler.h | 15 +- sql/share/errmsg-utf8.txt | 3 + sql/sql_class.h | 3 +- sql/sql_insert.cc | 5 +- sql/sql_lex.h | 13 +- sql/sql_show.cc | 6 +- sql/sql_table.cc | 120 ++++++++++++++-- sql/sql_yacc.yy | 24 +++- sql/table.cc | 19 ++- sql/table.h | 3 + sql/unireg.cc | 23 +++- 17 files changed, 533 insertions(+), 53 deletions(-) create mode 100644 mysql-test/suite/period/r/alter.result create mode 100644 mysql-test/suite/period/t/alter.test diff --git a/mysql-test/suite/period/r/alter.result b/mysql-test/suite/period/r/alter.result new file mode 100644 index 00000000000..c487b4a0d7f --- /dev/null +++ b/mysql-test/suite/period/r/alter.result @@ -0,0 +1,174 @@ +set @s= '1992-01-01'; +set @e= '1999-12-31'; +create or replace table t (s date, e date); +# period start/end columns are implicit NOT NULL +alter table t add period for a(s, e); +show create table t; +Table Create Table +t CREATE TABLE `t` ( + `s` date NOT NULL, + `e` date NOT NULL, + PERIOD FOR `a` (`s`, `e`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +create or replace table t (s date, e date); +alter table t change s s date, add period for a(s, e); +show create table t; +Table Create Table +t CREATE TABLE `t` ( + `s` date NOT NULL, + `e` date NOT NULL, + PERIOD FOR `a` (`s`, `e`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +alter table t add id int; +show create table t; +Table Create Table +t CREATE TABLE `t` ( + `s` date NOT NULL, + `e` date NOT NULL, + `id` int(11) DEFAULT NULL, + PERIOD FOR `a` (`s`, `e`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +alter table t drop id; +show create table t; +Table Create Table +t CREATE TABLE `t` ( + `s` date NOT NULL, + `e` date NOT NULL, + PERIOD FOR `a` (`s`, `e`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +insert t values(@e, @s); +ERROR 23000: CONSTRAINT `a` failed for `test`.`t` +alter table t drop constraint a; +ERROR HY000: Can't DROP CONSTRAINT `a`. Use DROP PERIOD `a` for this +# no-op +alter table t drop period if exists for b; +Warnings: +Note 1091 Can't DROP PERIOD `b`; check that it exists +# no-op +alter table t add period if not exists for a(e, s); +Warnings: +Note 1060 Duplicate column name 'a' +alter table t drop period if exists for a; +# no-op +alter table t drop period if exists for a; +Warnings: +Note 1091 Can't DROP PERIOD `a`; check that it exists +alter table t add period for a(s, e), add period if not exists for a(e, s); +show create table t; +Table Create Table +t CREATE TABLE `t` ( + `s` date NOT NULL, + `e` date NOT NULL, + PERIOD FOR `a` (`s`, `e`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +alter table t drop period for a; +# Constraint is dropped +insert t values(@e, @s); +alter table t drop period for a; +ERROR 42000: Can't DROP PERIOD `a`; check that it exists +alter table t add period for a(s, e), drop period for a; +ERROR 42000: Can't DROP PERIOD `a`; check that it exists +truncate t; +alter table t add period for a(s, e); +insert t values(@e, @s); +ERROR 23000: CONSTRAINT `a` failed for `test`.`t` +alter table t add period for a(s, e), drop period for a; +insert t values(@e, @s); +ERROR 23000: CONSTRAINT `a` failed for `test`.`t` +alter table t add s1 date not null, add period for b(s1, e), drop period for a; +show create table t; +Table Create Table +t CREATE TABLE `t` ( + `s` date NOT NULL, + `e` date NOT NULL, + `s1` date NOT NULL, + PERIOD FOR `b` (`s1`, `e`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +insert t(s, s1, e) values(@e, @s, @e); +insert t(s, s1, e) values(@e, @e, @s); +ERROR 23000: CONSTRAINT `b` failed for `test`.`t` +create table t1 like t; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `s` date NOT NULL, + `e` date NOT NULL, + `s1` date NOT NULL, + PERIOD FOR `b` (`s1`, `e`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +create table t2 (period for b(s,e)) select * from t; +ERROR 23000: CONSTRAINT `b` failed for `test`.`t2` +create table t2 (period for b(s1,e)) select * from t; +# SQL16 11.27 , Syntax Rules, 5)g) +# The declared type of BC1 shall be either DATE or a timestamp type +# and shall be equivalent to the declared type of BC2. +create or replace table t (s timestamp not null, e timestamp(6) not null); +alter table t add period for a(s, e); +ERROR HY000: Fields of PERIOD FOR `a` have different types +# SQL16 11.27 , Syntax Rules, 5)c) +# No column of T shall have a column name that is equivalent to ATPN. +create or replace table t (a int, s date, e date); +alter table t add period for a(s, e); +ERROR 42S21: Duplicate column name 'a' +# SQL16 11.27 , Syntax Rules, 5)i) +# Neither BC1 nor BC2 shall be an identity column, a generated column, +# a system-time period start column, or a system-time period end column. +create or replace table t (id int primary key, +s date, +e date generated always as (s+1)); +alter table t add period for a(s, e); +ERROR HY000: Period field `e` cannot be GENERATED ALWAYS AS +create or replace table t (id int primary key, +s date, +e date as (s+1) VIRTUAL); +alter table t add period for a(s, e); +ERROR HY000: Period field `e` cannot be GENERATED ALWAYS AS +create or replace table t (id int primary key, s timestamp(6), e timestamp(6), +st timestamp(6) as row start, +en timestamp(6) as row end, +period for system_time (st, en)) with system versioning; +alter table t add period for a(s, en); +ERROR HY000: Period field `en` cannot be GENERATED ALWAYS AS +# SQL16 11.27 , Syntax Rules, 5)b) +# The table descriptor of T shall not include a period descriptor other +# than a system-time period descriptor. +alter table t add period for a(s, e); +alter table t add period for b(s, e); +ERROR HY000: Cannot specify more than one application-time period +# SQL16, Part 2, 11.3 , Syntax Rules, 2)e)v)2)B) +# Let S be the schema identified by the explicit or implicit +# of TN. Let IDCN be an implementation-dependent +# that is not equivalent to the of +# any table constraint descriptor included in S. The following +#
is implicit: +# CONSTRAINT IDCN CHECK ( CN1 < CN2 ) +# +# Due to the above standard limitation, the constraint name can't always +# match the period name. So it matches when possible; and when not, it +# is unique not taken name prefixed with period name. +create or replace table t (x int, s date, e date, +period for mytime(s, e)); +show create table t; +Table Create Table +t CREATE TABLE `t` ( + `x` int(11) DEFAULT NULL, + `s` date NOT NULL, + `e` date NOT NULL, + PERIOD FOR `mytime` (`s`, `e`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +alter table t add constraint mytime check (x > 1); +show create table t; +Table Create Table +t CREATE TABLE `t` ( + `x` int(11) DEFAULT NULL, + `s` date NOT NULL, + `e` date NOT NULL, + PERIOD FOR `mytime` (`s`, `e`), + CONSTRAINT `mytime` CHECK (`x` > 1) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 +insert t values (2, @e, @s); +ERROR 23000: CONSTRAINT `mytime_1` failed for `test`.`t` +alter table t add constraint mytime_1 check (x > 2); +insert t values (3, @e, @s); +ERROR 23000: CONSTRAINT `mytime_2` failed for `test`.`t` +create or replace database test; diff --git a/mysql-test/suite/period/r/create.result b/mysql-test/suite/period/r/create.result index e498e745240..df77669f727 100644 --- a/mysql-test/suite/period/r/create.result +++ b/mysql-test/suite/period/r/create.result @@ -1,5 +1,7 @@ create or replace table t (id int primary key, s date, e date, period for mytime(s,e)); +# CONSTRAINT CHECK (s < e) is added implicitly, and shouldn't be shown +# this is important for correct command-based replication show create table t; Table Create Table t CREATE TABLE `t` ( @@ -7,8 +9,7 @@ t CREATE TABLE `t` ( `s` date NOT NULL, `e` date NOT NULL, PRIMARY KEY (`id`), - PERIOD FOR `mytime` (`s`, `e`), - CONSTRAINT `CONSTRAINT_1` CHECK (`s` < `e`) + PERIOD FOR `mytime` (`s`, `e`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 create or replace table t (id int primary key, s timestamp(6), e timestamp(6), period for mytime(s,e)); @@ -19,8 +20,7 @@ t CREATE TABLE `t` ( `s` timestamp(6) NOT NULL DEFAULT '0000-00-00 00:00:00.000000', `e` timestamp(6) NOT NULL DEFAULT '0000-00-00 00:00:00.000000', PRIMARY KEY (`id`), - PERIOD FOR `mytime` (`s`, `e`), - CONSTRAINT `CONSTRAINT_1` CHECK (`s` < `e`) + PERIOD FOR `mytime` (`s`, `e`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 # SQL16, Part 2, 11.3
, Syntax Rules, 2)a) # 2) If a
TPD is specified, then: @@ -86,7 +86,8 @@ t CREATE TABLE `t` ( `s` date NOT NULL, `e` date NOT NULL, PERIOD FOR `mytime` (`s`, `e`), - CONSTRAINT `CONSTRAINT_1` CHECK (`s` < `e`), CONSTRAINT `mytime` CHECK (`x` > 1) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 +insert t values (2, '2001-01-01', '2001-01-01'); +ERROR 23000: CONSTRAINT `mytime_1` failed for `test`.`t` create or replace database test; diff --git a/mysql-test/suite/period/t/alter.test b/mysql-test/suite/period/t/alter.test new file mode 100644 index 00000000000..4d504369859 --- /dev/null +++ b/mysql-test/suite/period/t/alter.test @@ -0,0 +1,131 @@ +set @s= '1992-01-01'; +set @e= '1999-12-31'; + +create or replace table t (s date, e date); + +--echo # period start/end columns are implicit NOT NULL +alter table t add period for a(s, e); +show create table t; + +create or replace table t (s date, e date); +alter table t change s s date, add period for a(s, e); +show create table t; + +alter table t add id int; +show create table t; +alter table t drop id; +show create table t; + +--error ER_CONSTRAINT_FAILED +insert t values(@e, @s); + +--error ER_PERIOD_CONSTRAINT_DROP +alter table t drop constraint a; + +--echo # no-op +alter table t drop period if exists for b; +--echo # no-op +alter table t add period if not exists for a(e, s); + +alter table t drop period if exists for a; +--echo # no-op +alter table t drop period if exists for a; + +alter table t add period for a(s, e), add period if not exists for a(e, s); +show create table t; + +alter table t drop period for a; +--echo # Constraint is dropped +insert t values(@e, @s); + +--error ER_CANT_DROP_FIELD_OR_KEY +alter table t drop period for a; +--error ER_CANT_DROP_FIELD_OR_KEY +alter table t add period for a(s, e), drop period for a; + +truncate t; +alter table t add period for a(s, e); +--error ER_CONSTRAINT_FAILED +insert t values(@e, @s); +alter table t add period for a(s, e), drop period for a; +--error ER_CONSTRAINT_FAILED +insert t values(@e, @s); +alter table t add s1 date not null, add period for b(s1, e), drop period for a; +show create table t; +insert t(s, s1, e) values(@e, @s, @e); +--error ER_CONSTRAINT_FAILED +insert t(s, s1, e) values(@e, @e, @s); + +create table t1 like t; +show create table t1; + +--error ER_CONSTRAINT_FAILED +create table t2 (period for b(s,e)) select * from t; + +create table t2 (period for b(s1,e)) select * from t; + +--echo # SQL16 11.27 , Syntax Rules, 5)g) +--echo # The declared type of BC1 shall be either DATE or a timestamp type +--echo # and shall be equivalent to the declared type of BC2. +create or replace table t (s timestamp not null, e timestamp(6) not null); +--error ER_PERIOD_TYPES_MISMATCH +alter table t add period for a(s, e); + +--echo # SQL16 11.27 , Syntax Rules, 5)c) +--echo # No column of T shall have a column name that is equivalent to ATPN. +create or replace table t (a int, s date, e date); +--error ER_DUP_FIELDNAME +alter table t add period for a(s, e); + +--echo # SQL16 11.27 , Syntax Rules, 5)i) +--echo # Neither BC1 nor BC2 shall be an identity column, a generated column, +--echo # a system-time period start column, or a system-time period end column. +create or replace table t (id int primary key, + s date, + e date generated always as (s+1)); +--error ER_PERIOD_FIELD_WRONG_ATTRIBUTES +alter table t add period for a(s, e); + +create or replace table t (id int primary key, + s date, + e date as (s+1) VIRTUAL); +--error ER_PERIOD_FIELD_WRONG_ATTRIBUTES +alter table t add period for a(s, e); + +create or replace table t (id int primary key, s timestamp(6), e timestamp(6), + st timestamp(6) as row start, + en timestamp(6) as row end, + period for system_time (st, en)) with system versioning; +--error ER_PERIOD_FIELD_WRONG_ATTRIBUTES +alter table t add period for a(s, en); + +--echo # SQL16 11.27 , Syntax Rules, 5)b) +--echo # The table descriptor of T shall not include a period descriptor other +--echo # than a system-time period descriptor. +alter table t add period for a(s, e); +--error ER_MORE_THAN_ONE_PERIOD +alter table t add period for b(s, e); + +--echo # SQL16, Part 2, 11.3
, Syntax Rules, 2)e)v)2)B) +--echo # Let S be the schema identified by the explicit or implicit +--echo # of TN. Let IDCN be an implementation-dependent +--echo # that is not equivalent to the of +--echo # any table constraint descriptor included in S. The following +--echo #
is implicit: +--echo # CONSTRAINT IDCN CHECK ( CN1 < CN2 ) +--echo # +--echo # Due to the above standard limitation, the constraint name can't always +--echo # match the period name. So it matches when possible; and when not, it +--echo # is unique not taken name prefixed with period name. +create or replace table t (x int, s date, e date, + period for mytime(s, e)); +show create table t; +alter table t add constraint mytime check (x > 1); +show create table t; +--error ER_CONSTRAINT_FAILED +insert t values (2, @e, @s); +alter table t add constraint mytime_1 check (x > 2); +--error ER_CONSTRAINT_FAILED +insert t values (3, @e, @s); + +create or replace database test; diff --git a/mysql-test/suite/period/t/create.test b/mysql-test/suite/period/t/create.test index 0960d1b5420..9648fa02fbc 100644 --- a/mysql-test/suite/period/t/create.test +++ b/mysql-test/suite/period/t/create.test @@ -1,5 +1,7 @@ create or replace table t (id int primary key, s date, e date, period for mytime(s,e)); +--echo # CONSTRAINT CHECK (s < e) is added implicitly, and shouldn't be shown +--echo # this is important for correct command-based replication show create table t; create or replace table t (id int primary key, s timestamp(6), e timestamp(6), period for mytime(s,e)); @@ -69,5 +71,7 @@ create or replace table t (x int, s date, e date, period for mytime(s, e), constraint mytime check (x > 1)); show create table t; +--error ER_CONSTRAINT_FAILED +insert t values (2, '2001-01-01', '2001-01-01'); create or replace database test; diff --git a/sql/field.h b/sql/field.h index a348e9ec480..023d29880e4 100644 --- a/sql/field.h +++ b/sql/field.h @@ -540,7 +540,7 @@ public: bool stored_in_db; bool utf8; /* Already in utf8 */ Item *expr; - LEX_CSTRING name; /* Name of constraint */ + Lex_ident name; /* Name of constraint */ /* see VCOL_* (VCOL_FIELD_REF, ...) */ uint flags; diff --git a/sql/handler.cc b/sql/handler.cc index 9150b055313..f11361f7779 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -7584,18 +7584,18 @@ bool Vers_parse_info::check_sys_fields(const Lex_table_name &table_name, return true; } -static bool check_period_field(const Create_field* f, const char* name, - const char* period_name) +bool Table_period_info::check_field(const Create_field* f, + const Lex_ident& f_name) const { bool res= false; if (!f) { - my_error(ER_BAD_FIELD_ERROR, MYF(0), name, period_name); + my_error(ER_BAD_FIELD_ERROR, MYF(0), f_name.str, name.str); res= true; } else if (f->type_handler()->mysql_timestamp_type() == MYSQL_TIMESTAMP_ERROR) { - my_error(ER_WRONG_FIELD_SPEC, MYF(0), name); + my_error(ER_WRONG_FIELD_SPEC, MYF(0), f->field_name.str); res= true; } else if (f->vcol_info || f->flags & VERS_SYSTEM_FIELD) @@ -7610,10 +7610,13 @@ static bool check_period_field(const Create_field* f, const char* name, bool Table_scope_and_contents_source_st::check_fields( THD *thd, Alter_info *alter_info, TABLE_LIST &create_table) { - bool res= vers_check_system_fields(thd, alter_info, create_table); - if (res) - return true; + return vers_check_system_fields(thd, alter_info, create_table) + || check_period_fields(thd, alter_info); +} +bool Table_scope_and_contents_source_st::check_period_fields( + THD *thd, Alter_info *alter_info) +{ if (!period_info.name) return false; @@ -7639,8 +7642,8 @@ bool Table_scope_and_contents_source_st::check_fields( } } - res= check_period_field(row_start, period.start.str, period_info.name.str); - res= res || check_period_field(row_end, period.end.str, period_info.name.str); + bool res= period_info.check_field(row_start, period.start.str) + || period_info.check_field(row_end, period.end.str); if (res) return true; @@ -7660,9 +7663,14 @@ Table_scope_and_contents_source_st::fix_create_fields(THD *thd, const TABLE_LIST &create_table, bool create_select) { - if (vers_fix_system_fields(thd, alter_info, create_table, create_select)) - return true; + return vers_fix_system_fields(thd, alter_info, create_table, create_select) + || fix_period_fields(thd, alter_info); +} +bool +Table_scope_and_contents_source_st::fix_period_fields(THD *thd, + Alter_info *alter_info) +{ if (!period_info.name) return false; diff --git a/sql/handler.h b/sql/handler.h index 2348575651a..8bee07c1287 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -1968,11 +1968,15 @@ enum vers_sys_type_t VERS_TRX_ID }; -struct Table_period_info +struct Table_period_info: Sql_alloc { - Table_period_info() {} + Table_period_info() : + create_if_not_exists(false), + constr(NULL) {} Table_period_info(const char *name_arg, size_t size) : - name(name_arg, size) {} + name(name_arg, size), + create_if_not_exists(false), + constr(NULL) {} Lex_ident name; @@ -1986,6 +1990,8 @@ struct Table_period_info Lex_ident end; }; start_end_t period; + bool create_if_not_exists; + Virtual_column_info *constr; bool is_set() const { @@ -1998,6 +2004,7 @@ struct Table_period_info period.start= start; period.end= end; } + bool check_field(const Create_field* f, const Lex_ident& f_name) const; }; struct Vers_parse_info: public Table_period_info @@ -2168,7 +2175,9 @@ struct Table_scope_and_contents_source_st: bool fix_create_fields(THD *thd, Alter_info *alter_info, const TABLE_LIST &create_table, bool create_select= false); + bool fix_period_fields(THD *thd, Alter_info *alter_info); bool check_fields(THD *thd, Alter_info *alter_info, TABLE_LIST &create_table); + bool check_period_fields(THD *thd, Alter_info *alter_info); bool vers_fix_system_fields(THD *thd, Alter_info *alter_info, const TABLE_LIST &create_table, diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index d46594d1d37..d79f3aa12c2 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -7948,3 +7948,6 @@ ER_PERIOD_NOT_FOUND eng "Period %`s is not found in table" ER_PERIOD_COLUMNS_UPDATED eng "Column %`s used in period %`s specified in update SET list" + +ER_PERIOD_CONSTRAINT_DROP + eng "Can't DROP CONSTRAINT `%s`. Use DROP PERIOD `%s` for this" diff --git a/sql/sql_class.h b/sql/sql_class.h index 361340ee235..6df1e58c95f 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -288,7 +288,7 @@ public: class Alter_drop :public Sql_alloc { public: - enum drop_type {KEY, COLUMN, FOREIGN_KEY, CHECK_CONSTRAINT }; + enum drop_type { KEY, COLUMN, FOREIGN_KEY, CHECK_CONSTRAINT, PERIOD }; const char *name; enum drop_type type; bool drop_if_exists; @@ -307,6 +307,7 @@ public: { return type == COLUMN ? "COLUMN" : type == CHECK_CONSTRAINT ? "CONSTRAINT" : + type == PERIOD ? "PERIOD" : type == KEY ? "INDEX" : "FOREIGN KEY"; } }; diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index b56fa44d8fd..ba01fa57670 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -4170,8 +4170,7 @@ TABLE *select_create::create_table_from_items(THD *thd, List *items, if (!opt_explicit_defaults_for_timestamp) promote_first_timestamp_column(&alter_info->create_list); - if (create_info->vers_fix_system_fields(thd, alter_info, *create_table, - true)) + if (create_info->fix_create_fields(thd, alter_info, *create_table, true)) DBUG_RETURN(NULL); while ((item=it++)) @@ -4210,7 +4209,7 @@ TABLE *select_create::create_table_from_items(THD *thd, List *items, alter_info->create_list.push_back(cr_field, thd->mem_root); } - if (create_info->vers_check_system_fields(thd, alter_info, *create_table)) + if (create_info->check_fields(thd, alter_info, *create_table)) DBUG_RETURN(NULL); DEBUG_SYNC(thd,"create_table_select_before_create"); diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 0bc65382b27..93a6ee0ef6a 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -4277,6 +4277,10 @@ public: int add_period(Lex_ident name, Lex_ident_sys_st start, Lex_ident_sys_st end) { Table_period_info &info= create_info.period_info; + + if (check_exists && info.name.streq(name)) + return 0; + if (info.is_set()) { my_error(ER_MORE_THAN_ONE_PERIOD, MYF(0)); @@ -4285,10 +4289,11 @@ public: info.set_period(start, end); info.name= name; - Virtual_column_info *constr= new Virtual_column_info(); - constr->expr= lt_creator.create(thd, create_item_ident_nosp(thd, &start), - create_item_ident_nosp(thd, &end)); - add_constraint(&null_clex_str, constr, false); + info.constr= new Virtual_column_info(); + info.constr->expr= lt_creator.create(thd, + create_item_ident_nosp(thd, &start), + create_item_ident_nosp(thd, &end)); + add_constraint(&null_clex_str, info.constr, false); return 0; } diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 291df4305cd..5e768ff31f7 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2417,8 +2417,12 @@ int show_create_table(THD *thd, TABLE_LIST *table_list, String *packet, for (uint i= share->field_check_constraints; i < share->table_check_constraints ; i++) { - StringBuffer str(&my_charset_utf8mb4_general_ci); Virtual_column_info *check= table->check_constraints[i]; + // period constraint is implicit + if (share->period.constr_name.streq(check->name)) + continue; + + StringBuffer str(&my_charset_utf8mb4_general_ci); check->print(&str); packet->append(STRING_WITH_LEN(",\n ")); diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 13674392bb0..b7bf618a1ec 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -66,6 +66,7 @@ static int check_if_keyname_exists(const char *name,KEY *start, KEY *end); static char *make_unique_key_name(THD *thd, const char *field_name, KEY *start, KEY *end); static void make_unique_constraint_name(THD *thd, LEX_CSTRING *name, + const char *own_name_base, List *vcol, uint *nr); static const @@ -4203,9 +4204,14 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, while ((check= c_it++)) { if (!check->name.length) - make_unique_constraint_name(thd, &check->name, + { + const char *own_name_base= create_info->period_info.constr == check + ? create_info->period_info.name.str : NULL; + + make_unique_constraint_name(thd, &check->name, own_name_base, &alter_info->check_constraint_list, &nr); + } { /* Check that there's no repeating constraint names. */ List_iterator_fast @@ -5274,17 +5280,22 @@ make_unique_key_name(THD *thd, const char *field_name,KEY *start,KEY *end) */ static void make_unique_constraint_name(THD *thd, LEX_CSTRING *name, + const char *own_name_base, List *vcol, uint *nr) { char buff[MAX_FIELD_NAME], *end; List_iterator_fast it(*vcol); - - end=strmov(buff, "CONSTRAINT_"); - for (;;) + end=strmov(buff, own_name_base ? own_name_base : "CONSTRAINT_"); + for (int round= 0;; round++) { Virtual_column_info *check; - char *real_end= int10_to_str((*nr)++, end, 10); + char *real_end= end; + if (round == 1 && own_name_base) + *end++= '_'; + // if own_base_name provided, try it first + if (round != 0 || !own_name_base) + real_end= int10_to_str((*nr)++, end, 10); it.rewind(); while ((check= it++)) { @@ -5914,6 +5925,7 @@ static bool is_candidate_key(KEY *key) thd Thread object. table The altered table. alter_info List of columns and indexes to create + period_info Application-time period info DESCRIPTION Looks for the IF [NOT] EXISTS options, checks the states and remove items @@ -5924,7 +5936,8 @@ static bool is_candidate_key(KEY *key) */ static void -handle_if_exists_options(THD *thd, TABLE *table, Alter_info *alter_info) +handle_if_exists_options(THD *thd, TABLE *table, Alter_info *alter_info, + Table_period_info *period_info) { Field **f_ptr; DBUG_ENTER("handle_if_exists_option"); @@ -6110,6 +6123,11 @@ drop_create_field: } } } + else if (drop->type == Alter_drop::PERIOD) + { + if (table->s->period.name.streq(drop->name)) + remove_drop= FALSE; + } else /* Alter_drop::KEY and Alter_drop::FOREIGN_KEY */ { uint n_key; @@ -6390,6 +6408,26 @@ remove_key: } } + /* ADD PERIOD */ + + if (period_info->create_if_not_exists && table->s->period.name + && table->s->period.name.streq(period_info->name)) + { + DBUG_ASSERT(period_info->is_set()); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_DUP_FIELDNAME, ER_THD(thd, ER_DUP_FIELDNAME), + period_info->name.str, table->s->table_name.str); + + List_iterator vit(alter_info->check_constraint_list); + while (vit++ != period_info->constr) + { + // do nothing + } + vit.remove(); + + *period_info= {}; + } + DBUG_VOID_RETURN; } @@ -7792,6 +7830,7 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, Create_field *def; Field **f_ptr,*field; MY_BITMAP *dropped_fields= NULL; // if it's NULL - no dropped fields + bool drop_period= false; DBUG_ENTER("mysql_prepare_alter_table"); /* @@ -8356,6 +8395,35 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, } } + if (table->s->period.name) + { + drop_it.rewind(); + Alter_drop *drop; + for (bool found= false; !found && (drop= drop_it++); ) + { + found= drop->type == Alter_drop::PERIOD && + table->s->period.name.streq(drop->name); + } + + if (drop) + { + drop_period= true; + drop_it.remove(); + } + else if (create_info->period_info.is_set() && table->s->period.name) + { + my_error(ER_MORE_THAN_ONE_PERIOD, MYF(0)); + goto err; + } + else + { + Field *s= table->s->period.start_field(table->s); + Field *e= table->s->period.end_field(table->s); + create_info->period_info.set_period(s->field_name, e->field_name); + create_info->period_info.name= table->s->period.name; + } + } + /* Add all table level constraints which are not in the drop list */ if (table->s->table_check_constraints) { @@ -8366,6 +8434,7 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, { Virtual_column_info *check= table->check_constraints[i]; Alter_drop *drop; + bool keep= true; drop_it.rewind(); while ((drop=drop_it++)) { @@ -8373,17 +8442,39 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, !my_strcasecmp(system_charset_info, check->name.str, drop->name)) { drop_it.remove(); + keep= false; break; } } + + if (share->period.constr_name.streq(check->name.str)) + { + if (!drop_period && !keep) + { + my_error(ER_PERIOD_CONSTRAINT_DROP, MYF(0), check->name.str, + share->period.name.str); + goto err; + } + keep= keep && !drop_period; + + DBUG_ASSERT(create_info->period_info.constr == NULL || drop_period); + + if (keep) + { + Item *expr_copy= check->expr->get_copy(thd); + check= new Virtual_column_info(); + check->expr= expr_copy; + create_info->period_info.constr= check; + } + } /* see if the constraint depends on *only* on dropped fields */ - if (!drop && dropped_fields) + if (keep && dropped_fields) { table->default_column_bitmaps(); bitmap_clear_all(table->read_set); check->expr->walk(&Item::register_field_in_read_map, 1, 0); if (bitmap_is_subset(table->read_set, dropped_fields)) - drop= (Alter_drop*)1; + keep= false; else if (bitmap_is_overlapping(dropped_fields, table->read_set)) { bitmap_intersect(table->read_set, dropped_fields); @@ -8393,7 +8484,7 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, goto err; } } - if (!drop) + if (keep) { if (alter_info->flags & ALTER_RENAME_COLUMN) { @@ -8417,8 +8508,9 @@ mysql_prepare_alter_table(THD *thd, TABLE *table, case Alter_drop::KEY: case Alter_drop::COLUMN: case Alter_drop::CHECK_CONSTRAINT: - my_error(ER_CANT_DROP_FIELD_OR_KEY, MYF(0), drop->type_name(), - alter_info->drop_list.head()->name); + case Alter_drop::PERIOD: + my_error(ER_CANT_DROP_FIELD_OR_KEY, MYF(0), drop->type_name(), + alter_info->drop_list.head()->name); goto err; case Alter_drop::FOREIGN_KEY: // Leave the DROP FOREIGN KEY names in the alter_info->drop_list. @@ -9341,7 +9433,7 @@ do_continue:; } } - handle_if_exists_options(thd, table, alter_info); + handle_if_exists_options(thd, table, alter_info, &create_info->period_info); /* Look if we have to do anything at all. @@ -9423,6 +9515,10 @@ do_continue:; set_table_default_charset(thd, create_info, &alter_ctx.db); + if (create_info->check_period_fields(thd, alter_info) + || create_info->fix_period_fields(thd, alter_info)) + DBUG_RETURN(true); + if (!opt_explicit_defaults_for_timestamp) promote_first_timestamp_column(&alter_info->create_list); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 60fbd0e2722..23e074eae6c 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -2066,6 +2066,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); key_using_alg part_column_list period_for_system_time + period_for_application_time server_def server_options_list server_option definer_opt no_definer definer get_diagnostics parse_vcol_expr vcol_opt_specifier vcol_opt_attribute @@ -6538,6 +6539,7 @@ field_list_item: | key_def | constraint_def | period_for_system_time + | PERIOD_SYM period_for_application_time { } ; column_def: @@ -6645,9 +6647,12 @@ period_for_system_time: Vers_parse_info &info= Lex->vers_get_info(); info.set_period($4, $6); } - | PERIOD_SYM FOR_SYM ident '(' ident ',' ident ')' + ; + +period_for_application_time: + FOR_SYM ident '(' ident ',' ident ')' { - if (Lex->add_period($3, $5, $7)) + if (Lex->add_period($2, $4, $6)) MYSQL_YYABORT; } ; @@ -8324,6 +8329,13 @@ alter_list_item: { Lex->alter_info.flags|= ALTER_ADD_PERIOD; } + | ADD + PERIOD_SYM opt_if_not_exists_table_element period_for_application_time + { + Table_period_info &period= Lex->create_info.period_info; + period.create_if_not_exists= Lex->check_exists; + Lex->alter_info.flags|= ALTER_ADD_CHECK_CONSTRAINT; + } | add_column '(' create_field_list ')' { LEX *lex=Lex; @@ -8493,6 +8505,14 @@ alter_list_item: { Lex->alter_info.flags|= ALTER_DROP_PERIOD; } + | DROP PERIOD_SYM opt_if_exists_table_element FOR_SYM ident + { + Alter_drop *ad= new Alter_drop(Alter_drop::PERIOD, $5.str, $3); + if (unlikely(ad == NULL)) + MYSQL_YYABORT; + Lex->alter_info.drop_list.push_back(ad, thd->mem_root); + Lex->alter_info.flags|= ALTER_DROP_CHECK_CONSTRAINT; + } ; opt_index_lock_algorithm: diff --git a/sql/table.cc b/sql/table.cc index 981ca4a1deb..58d9f16c0a1 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -2028,13 +2028,26 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write, if (extra2.application_period.str) { - period.name.length= extra2.application_period.length - 2 * frm_fieldno_size; + const uchar *name_pos= extra2.application_period.str + frm_ident_len_size; + period.name.length= uint2korr(extra2.application_period.str); period.name.str= strmake_root(&mem_root, - (char*)extra2.application_period.str, + (char*)name_pos, period.name.length); - const uchar *field_pos= extra2.application_period.str + period.name.length; + + const uchar *constr_pos= name_pos + period.name.length + frm_ident_len_size; + period.constr_name.length= uint2korr(name_pos + period.name.length); + period.constr_name.str= strmake_root(&mem_root, + (char*)constr_pos, + period.constr_name.length); + + const uchar *field_pos= constr_pos + period.constr_name.length; if (init_period_from_extra2(period, field_pos)) goto err; + + if (period.name.length + period.constr_name.length + + 2 * frm_ident_len_size + 2 * frm_fieldno_size + != extra2.application_period.length) + goto err; } for (i=0 ; i < share->fields; i++, strpos+=field_pack_length, field_ptr++) diff --git a/sql/table.h b/sql/table.h index b28e865f0b1..da2247c4f6a 100644 --- a/sql/table.h +++ b/sql/table.h @@ -774,6 +774,7 @@ struct TABLE_SHARE uint16 start_fieldno; uint16 end_fieldno; Lex_ident name; + Lex_ident constr_name; Field *start_field(TABLE_SHARE *s) const { return s->field[start_fieldno]; @@ -1782,6 +1783,8 @@ class IS_table_read_plan; /** number of bytes used by field positional indexes in frm */ constexpr uint frm_fieldno_size= 2; +/** number of bytes used by identifier length in frm */ +constexpr uint frm_ident_len_size= 2; class select_unit; class TMP_TABLE_PARAM; diff --git a/sql/unireg.cc b/sql/unireg.cc index 9d4b7b93f0a..ab3ac0dabca 100644 --- a/sql/unireg.cc +++ b/sql/unireg.cc @@ -143,6 +143,14 @@ bool has_extra2_field_flags(List &create_fields) return false; } +static inline +uchar* store_str(uchar *buf, const Lex_ident &str) +{ + int2store(buf, str.length); + memcpy(buf + frm_ident_len_size, str.str, str.length); + return buf + str.length + frm_ident_len_size; +} + /** Create a frm (table definition) file @@ -170,8 +178,11 @@ LEX_CUSTRING build_frm_image(THD *thd, const LEX_CSTRING *table, ulong data_offset; uint options_len; uint gis_extra2_len= 0; - uint period_info_len= create_info->period_info.name.length - + 2 * frm_fieldno_size; + size_t period_info_len= create_info->period_info.name + ? create_info->period_info.name.length + + create_info->period_info.constr->name.length + + 2 * frm_ident_len_size + 2 * frm_fieldno_size + : 0; uchar fileinfo[FRM_HEADER_SIZE],forminfo[FRM_FORMINFO_SIZE]; const partition_info *part_info= IF_PARTITIONING(thd->work_part_info, 0); bool error; @@ -351,14 +362,12 @@ LEX_CUSTRING build_frm_image(THD *thd, const LEX_CSTRING *table, #endif /*HAVE_SPATIAL*/ // PERIOD - if (create_info->period_info.name) + if (create_info->period_info.is_set()) { *pos++= EXTRA2_APPLICATION_TIME_PERIOD; - - Lex_ident &period_name= create_info->period_info.name; pos= extra2_write_len(pos, period_info_len); - memcpy(pos, period_name.str, period_name.length); - pos+= period_name.length; + pos= store_str(pos, create_info->period_info.name); + pos= store_str(pos, create_info->period_info.constr->name); int2store(pos, get_fieldno_by_name(create_info, create_fields, create_info->period_info.period.start));