From 6300b34c169088f15867281e77127044a61aa3b7 Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Mon, 29 Jun 2026 14:00:02 +0300 Subject: [PATCH 1/7] get_next_time() comment --- sql/event_data_objects.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc index 7148ce832594a..130b257b88d20 100644 --- a/sql/event_data_objects.cc +++ b/sql/event_data_objects.cc @@ -687,7 +687,9 @@ add_interval(MYSQL_TIME *ltime, const Time_zone *time_zone, /* - Computes the sum of a timestamp plus interval. + Computes the smallest next > time_now obtained by adding one or more + multiples of the given interval (i_value, i_type) to start, taking + into account time_zone conversions and DST ambiguities. SYNOPSIS get_next_time() From 1d2978a3f6104881faaab58c813c9b02f9503f69 Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Mon, 29 Jun 2026 15:10:58 +0300 Subject: [PATCH 2/7] MDEV-25529 Fix to_string() OOM return semantics for temporal types Temporal_hybrid::to_string() returned the destination String with a stale length when String::alloc() failed, instead of signalling the failure. Return NULL on allocation failure. Apply the same fix to the sibling to_string() methods of Interval_DDhhmmssff, Time, Datetime and Date, which shared the identical flaw, so all five report OOM consistently. The val_str() implementations that return the to_string() result directly did not set null_value, so a NULL result would be taken for a value. They now go through the new Item::check_str(), which sets null_value from the result, so a NULL does not stick to the item for the next execution. The time_to_string_oom debug keyword makes Time::to_string() fail as on out of memory. --- mysql-test/main/func_debug.result | 15 ++++++++++++++ mysql-test/main/func_debug.test | 15 ++++++++++++++ sql/item.h | 33 +++++++++++++++++++++---------- sql/item_func.h | 9 +++++---- sql/item_timefunc.h | 12 +++++------ sql/sql_type.cc | 10 +++++----- sql/sql_type.h | 31 ++++++++++++++++++----------- 7 files changed, 87 insertions(+), 38 deletions(-) diff --git a/mysql-test/main/func_debug.result b/mysql-test/main/func_debug.result index f755e9b2057f5..53c4823487ba0 100644 --- a/mysql-test/main/func_debug.result +++ b/mysql-test/main/func_debug.result @@ -1967,3 +1967,18 @@ Warning 1292 Truncated incorrect DECIMAL value: '0x' SET SESSION debug_dbug="-d,Predicant_to_list_comparator"; SET SESSION debug_dbug="-d,Item_func_in"; SET SESSION debug_dbug="-d,cmp_item"; +# +# MDEV-25529 Fix to_string() OOM return semantics for temporal types +# +# NULL from to_string() does not stick to the next execution +prepare s from "select trim(time'10:00:00') as t"; +set @save_dbug= @@debug_dbug; +set debug_dbug= '+d,time_to_string_oom'; +execute s; +ERROR HY000: Out of memory. +set debug_dbug= @save_dbug; +execute s; +t +10:00:00 +deallocate prepare s; +# End of 13.2 tests diff --git a/mysql-test/main/func_debug.test b/mysql-test/main/func_debug.test index 9337328e4a877..04ef7deac98a0 100644 --- a/mysql-test/main/func_debug.test +++ b/mysql-test/main/func_debug.test @@ -592,3 +592,18 @@ SELECT ('0x',1) IN ((0,1),(1,1)); SET SESSION debug_dbug="-d,Predicant_to_list_comparator"; SET SESSION debug_dbug="-d,Item_func_in"; SET SESSION debug_dbug="-d,cmp_item"; + +--echo # +--echo # MDEV-25529 Fix to_string() OOM return semantics for temporal types +--echo # +--echo # NULL from to_string() does not stick to the next execution +prepare s from "select trim(time'10:00:00') as t"; +set @save_dbug= @@debug_dbug; +set debug_dbug= '+d,time_to_string_oom'; +--error ER_OUT_OF_RESOURCES +execute s; +set debug_dbug= @save_dbug; +execute s; +deallocate prepare s; + +--echo # End of 13.2 tests diff --git a/sql/item.h b/sql/item.h index 4707db64ac297..7ac4a7fca6ffc 100644 --- a/sql/item.h +++ b/sql/item.h @@ -1849,6 +1849,16 @@ class Item :public Value_source, virtual double val_result() { return val_real(); } virtual longlong val_int_result() { return val_int(); } virtual String *str_result(String* tmp) { return val_str(tmp); } + /* + Return a val_str() result and set null_value from it on every call. NULL + is SQL NULL, or an error that is already reported (e.g. out of memory), + which must not stay in null_value for the next execution. + */ + String *check_str(String *res) + { + null_value= !res; + return res; + } virtual my_decimal *val_decimal_result(my_decimal *val) { return val_decimal(val); } virtual bool val_bool_result() { return val_bool(); } @@ -5663,7 +5673,7 @@ class Item_timestamp_literal: public Item_literal } String *val_str(String *to) override { - return m_value.to_datetime(current_thd).to_string(to, decimals); + return check_str(m_value.to_datetime(current_thd).to_string(to, decimals)); } my_decimal *val_decimal(my_decimal *to) override { @@ -5775,7 +5785,7 @@ class Item_date_literal: public Item_temporal_literal } String *val_str(String *to) override { - return update_null() ? 0 : cached_time.to_string(to); + return update_null() ? 0 : check_str(cached_time.to_string(to)); } my_decimal *val_decimal(my_decimal *to) override { @@ -5829,7 +5839,7 @@ class Item_time_literal final: public Item_temporal_literal } double val_real() override { return cached_time.to_double(); } String *val_str(String *to) override - { return cached_time.to_string(to, decimals); } + { return check_str(cached_time.to_string(to, decimals)); } my_decimal *val_decimal(my_decimal *to) override { return cached_time.to_decimal(to); } longlong val_time_packed(THD *thd) override @@ -5909,7 +5919,8 @@ class Item_datetime_literal: public Item_temporal_literal } String *val_str(String *to) override { - return update_null() ? NULL : cached_time.to_string(to, decimals); + return update_null() ? NULL : + check_str(cached_time.to_string(to, decimals)); } my_decimal *val_decimal(my_decimal *to) override { @@ -7406,8 +7417,9 @@ class Item_copy_timestamp: public Item_copy { DBUG_ASSERT(copied_in); DBUG_ASSERT(sane()); - return null_value ? NULL : - m_value.to_datetime(current_thd).to_string(to, decimals); + if (null_value) + return NULL; + return check_str(m_value.to_datetime(current_thd).to_string(to, decimals)); } my_decimal *val_decimal(my_decimal *to) override { @@ -8302,7 +8314,7 @@ class Item_cache_time: public Item_cache_temporal } String *val_str(String *to) override { - return has_value() ? Time(this).to_string(to, decimals) : NULL; + return has_value() ? check_str(Time(this).to_string(to, decimals)) : NULL; } my_decimal *val_decimal(my_decimal *to) override { @@ -8343,7 +8355,8 @@ class Item_cache_datetime: public Item_cache_temporal } String *val_str(String *to) override { - return has_value() ? Datetime(this).to_string(to, decimals) : NULL; + return has_value() ? check_str(Datetime(this).to_string(to, decimals)) : + NULL; } my_decimal *val_decimal(my_decimal *to) override { @@ -8376,7 +8389,7 @@ class Item_cache_date: public Item_cache_temporal { return has_value() ? Date(this).to_double() : 0; } String *val_str(String *to) override { - return has_value() ? Date(this).to_string(to) : NULL; + return has_value() ? check_str(Date(this).to_string(to)) : NULL; } my_decimal *val_decimal(my_decimal *to) override { @@ -8401,7 +8414,7 @@ class Item_cache_timestamp: public Item_cache bool cache_value() override; String* val_str(String *to) override { - return to_datetime(current_thd).to_string(to, decimals); + return check_str(to_datetime(current_thd).to_string(to, decimals)); } my_decimal *val_decimal(my_decimal *to) override { diff --git a/sql/item_func.h b/sql/item_func.h index 3a7c66e899633..8a30d194c60e2 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -659,7 +659,8 @@ class Item_handled_func: public Item_func } String *val_str_ascii(Item_handled_func *item, String *to) const override { - return Temporal_hybrid(item).to_string(to, item->decimals); + return item->check_str(Temporal_hybrid(item).to_string(to, + item->decimals)); } }; @@ -690,7 +691,7 @@ class Item_handled_func: public Item_func } String *val_str_ascii(Item_handled_func *item, String *to) const override { - return Date(item).to_string(to); + return item->check_str(Date(item).to_string(to)); } }; @@ -716,7 +717,7 @@ class Item_handled_func: public Item_func } String *val_str_ascii(Item_handled_func *item, String *to) const override { - return Time(item).to_string(to, item->decimals); + return item->check_str(Time(item).to_string(to, item->decimals)); } bool val_native(THD *thd, Item_handled_func *item, Native *to) const override { @@ -746,7 +747,7 @@ class Item_handled_func: public Item_func } String *val_str_ascii(Item_handled_func *item, String *to) const override { - return Datetime(item).to_string(to, item->decimals); + return item->check_str(Datetime(item).to_string(to, item->decimals)); } }; diff --git a/sql/item_timefunc.h b/sql/item_timefunc.h index 49b082d34216d..72b801d5e74c1 100644 --- a/sql/item_timefunc.h +++ b/sql/item_timefunc.h @@ -692,7 +692,7 @@ class Item_datefunc :public Item_func double val_real() override { return Date(this).to_double(); } String *val_str(String *to) override - { return Date(this).to_string(to); } + { return check_str(Date(this).to_string(to)); } my_decimal *val_decimal(my_decimal *to) override { return Date(this).to_decimal(to); } bool fix_length_and_dec(THD *thd) override @@ -721,7 +721,7 @@ class Item_timefunc :public Item_func double val_real() override { return Time(this).to_double(); } String *val_str(String *to) override - { return Time(this).to_string(to, decimals); } + { return check_str(Time(this).to_string(to, decimals)); } my_decimal *val_decimal(my_decimal *to) override { return Time(this).to_decimal(to); } bool val_native(THD *thd, Native *to) override @@ -746,7 +746,7 @@ class Item_datetimefunc :public Item_func } double val_real() override { return Datetime(this).to_double(); } String *val_str(String *to) override - { return Datetime(this).to_string(to, decimals); } + { return check_str(Datetime(this).to_string(to, decimals)); } my_decimal *val_decimal(my_decimal *to) override { return Datetime(this).to_decimal(to); } }; @@ -790,8 +790,7 @@ class Item_timestampfunc: public Item_func String *val_str(String *to) override { const Datetime dt= to_datetime(current_thd); - null_value= !dt.is_valid_datetime(); - return dt.to_string(to, decimals); + return check_str(dt.to_string(to, decimals)); } }; @@ -1502,8 +1501,7 @@ class Item_interval_DDhhmmssff_typecast :public Item_char_typecast String *val_str(String *to) override { Interval_DDhhmmssff it(current_thd, args[0], m_fsp); - null_value= !it.is_valid_interval_DDhhmmssff(); - return it.to_string(to, m_fsp); + return check_str(it.to_string(to, m_fsp)); } }; diff --git a/sql/sql_type.cc b/sql/sql_type.cc index 538dbd7a5be69..707d7700c8de8 100644 --- a/sql/sql_type.cc +++ b/sql/sql_type.cc @@ -6267,21 +6267,21 @@ String *Type_handler_string_result:: String *Type_handler_time_common:: Item_func_min_max_val_str(Item_func_min_max *func, String *str) const { - return Time(func).to_string(str, func->decimals); + return func->check_str(Time(func).to_string(str, func->decimals)); } String *Type_handler_date_common:: Item_func_min_max_val_str(Item_func_min_max *func, String *str) const { - return Date(func).to_string(str); + return func->check_str(Date(func).to_string(str)); } String *Type_handler_datetime_common:: Item_func_min_max_val_str(Item_func_min_max *func, String *str) const { - return Datetime(func).to_string(str, func->decimals); + return func->check_str(Datetime(func).to_string(str, func->decimals)); } @@ -6289,8 +6289,8 @@ String *Type_handler_timestamp_common:: Item_func_min_max_val_str(Item_func_min_max *func, String *str) const { THD *thd= current_thd; - return Timestamp_or_zero_datetime_native_null(thd, func). - to_datetime(thd).to_string(str, func->decimals); + return func->check_str(Timestamp_or_zero_datetime_native_null(thd, func). + to_datetime(thd).to_string(str, func->decimals)); } diff --git a/sql/sql_type.h b/sql/sql_type.h index 152ee4622b60b..ea411995738c6 100644 --- a/sql/sql_type.h +++ b/sql/sql_type.h @@ -1421,8 +1421,9 @@ class Temporal_hybrid: public Temporal if (!is_valid_temporal()) return NULL; str->set_charset(&my_charset_numeric); - if (!str->alloc(MAX_DATE_STRING_REP_LENGTH)) - str->length(my_TIME_to_str(this, const_cast(str->ptr()), dec)); + if (str->alloc(MAX_DATE_STRING_REP_LENGTH)) + return NULL; + str->length(my_TIME_to_str(this, const_cast(str->ptr()), dec)); return str; } const MYSQL_TIME *get_mysql_time() const @@ -1622,10 +1623,11 @@ class Interval_DDhhmmssff: public Temporal if (!is_valid_interval_DDhhmmssff()) return NULL; str->set_charset(&my_charset_numeric); - if (!str->alloc(MAX_DATE_STRING_REP_LENGTH)) - str->length(my_interval_DDhhmmssff_to_str(this, - const_cast(str->ptr()), - dec)); + if (str->alloc(MAX_DATE_STRING_REP_LENGTH)) + return NULL; + str->length(my_interval_DDhhmmssff_to_str(this, + const_cast(str->ptr()), + dec)); return str; } }; @@ -2060,8 +2062,11 @@ class Time: public Temporal if (!is_valid_time()) return NULL; str->set_charset(&my_charset_numeric); - if (!str->alloc(MAX_DATE_STRING_REP_LENGTH)) - str->length(my_time_to_str(this, const_cast(str->ptr()), dec)); + DBUG_EXECUTE_IF("time_to_string_oom", + my_error(ER_OUT_OF_RESOURCES, MYF(0)); return NULL;); + if (str->alloc(MAX_DATE_STRING_REP_LENGTH)) + return NULL; + str->length(my_time_to_str(this, const_cast(str->ptr()), dec)); return str; } my_decimal *to_decimal(my_decimal *to) const @@ -2422,8 +2427,9 @@ class Date: public Temporal_with_date if (!is_valid_date()) return NULL; str->set_charset(&my_charset_numeric); - if (!str->alloc(MAX_DATE_STRING_REP_LENGTH)) - str->length(my_date_to_str(this, const_cast(str->ptr()))); + if (str->alloc(MAX_DATE_STRING_REP_LENGTH)) + return NULL; + str->length(my_date_to_str(this, const_cast(str->ptr()))); return str; } my_decimal *to_decimal(my_decimal *to) const @@ -2748,8 +2754,9 @@ class Datetime: public Temporal_with_date if (!is_valid_datetime()) return NULL; str->set_charset(&my_charset_numeric); - if (!str->alloc(MAX_DATE_STRING_REP_LENGTH)) - str->length(my_datetime_to_str(this, const_cast(str->ptr()), dec)); + if (str->alloc(MAX_DATE_STRING_REP_LENGTH)) + return NULL; + str->length(my_datetime_to_str(this, const_cast(str->ptr()), dec)); return str; } my_decimal *to_decimal(my_decimal *to) const From a78a6c2b0c8722865262d78a794a6a78773ee3cc Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Mon, 29 Jun 2026 14:00:02 +0300 Subject: [PATCH 3/7] MDEV-25529 Replace COMBINE macro with interval2sec() and interval2usec() Turn the COMBINE macro of date_add_interval() into inline functions with overloads for INTERVAL and MYSQL_TIME, so that the history partition sizing can reuse the seconds part. Move the "Daynumber" comment that sat above the macro to MAX_DAY_NUMBER, which it describes. MAX_DAY_NUMBER moves to sql_time.h as a constexpr (the unused copy in item_timefunc.cc is removed), and the range check of date_add_interval() becomes the inline interval_days_out_of_range(), so that the sizing can check that the seconds do not overflow. --- sql/item_timefunc.cc | 3 --- sql/sql_time.cc | 14 ++-------- sql/sql_time.h | 64 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 15 deletions(-) diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index b777917f3873c..0b9c1f99edaa5 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -51,9 +51,6 @@ #include #include -/** Day number for Dec 31st, 9999. */ -#define MAX_DAY_NUMBER 3652424L - Func_handler_date_add_interval_datetime_arg0_time func_handler_date_add_interval_datetime_arg0_time; diff --git a/sql/sql_time.cc b/sql/sql_time.cc index c38871c3d7896..1f4e668a21dc1 100644 --- a/sql/sql_time.cc +++ b/sql/sql_time.cc @@ -24,8 +24,6 @@ #include -#define MAX_DAY_NUMBER 3652424L - /* Some functions to calculate dates */ /* @@ -604,11 +602,6 @@ void make_truncated_value_warning(THD *thd, } -/* Daynumber from year 0 to 9999-12-31 */ -#define COMBINE(X) \ - (((((X)->day * 24LL + (X)->hour) * 60LL + \ - (X)->minute) * 60LL + (X)->second)*1000000LL + \ - (X)->second_part) #define GET_PART(X, N) X % N ## LL; X/= N ## LL bool date_add_interval(THD *thd, MYSQL_TIME *ltime, interval_type int_type, @@ -639,16 +632,13 @@ bool date_add_interval(THD *thd, MYSQL_TIME *ltime, interval_type int_type, my_bool neg= 0; enum enum_mysql_timestamp_type time_type= ltime->time_type; - if (((ulonglong) interval.day + - (ulonglong) interval.hour / 24 + - (ulonglong) interval.minute / 24 / 60 + - (ulonglong) interval.second / 24 / 60 / 60) > MAX_DAY_NUMBER) + if (interval_days_out_of_range(interval)) goto invalid_date; if (time_type != MYSQL_TIMESTAMP_TIME) ltime->day+= calc_daynr(ltime->year, ltime->month, 1) - 1; - usec= COMBINE(ltime) + sign*COMBINE(&interval); + usec= interval2usec(ltime) + sign * interval2usec(&interval); if (usec < 0) { diff --git a/sql/sql_time.h b/sql/sql_time.h index a2ded1f7019b9..a3724b53f981e 100644 --- a/sql/sql_time.h +++ b/sql/sql_time.h @@ -168,4 +168,68 @@ void unpack_time(longlong packed, MYSQL_TIME *my_time, enum_mysql_timestamp_type ts_type) __attribute__((nonnull)); +/* Day number of 9999-12-31 */ +static constexpr longlong MAX_DAY_NUMBER= 3652424; + +/** + Check if the day-time part of an interval is longer than the date range + (MAX_DAY_NUMBER days). Otherwise the interval converted with interval2sec() + or interval2usec() does not overflow. +*/ +inline bool interval_days_out_of_range(const INTERVAL &interval) +{ + return (ulonglong(interval.day) + + ulonglong(interval.hour) / 24 + + ulonglong(interval.minute) / 24 / 60 + + ulonglong(interval.second) / 24 / 60 / 60) > MAX_DAY_NUMBER; +} + +/** + Convert an interval to a total number of seconds. + + The @c year and @c month members must be 0 (asserted). The @c neg member + is ignored: the caller applies the sign. + + @param x Interval. + + @return Total number of seconds represented by @p x. +*/ +inline longlong interval2sec(const INTERVAL *x) +{ + DBUG_ASSERT(!x->year && !x->month); + return ((x->day * 24LL + x->hour) * 60LL + x->minute) * 60LL + x->second; +} + +/** + Convert a time value to a total number of seconds. + + The @c year, @c month and @c neg members are ignored: date_add_interval() + folds year and month into day and applies the sign itself. + + @param x Time value. + + @return Seconds of the day, hour, minute and second members of @p x. +*/ +inline longlong interval2sec(const MYSQL_TIME *x) +{ + return ((x->day * 24LL + x->hour) * 60LL + x->minute) * 60LL + x->second; +} + +/** + Convert an interval to a total number of microseconds: + interval2sec() * 1000000 + @c second_part. +*/ +inline longlong interval2usec(const INTERVAL *x) +{ + return interval2sec(x) * 1000000LL + x->second_part; +} + +/** + Convert a time value to microseconds: interval2sec() * 1000000 + + @c second_part. +*/ +inline longlong interval2usec(const MYSQL_TIME *x) +{ + return interval2sec(x) * 1000000LL + x->second_part; +} #endif /* SQL_TIME_INCLUDED */ From e1f06d17607f45370c13ca9410e87ca92554b3ee Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Mon, 29 Jun 2026 14:00:02 +0300 Subject: [PATCH 4/7] MDEV-25529 ErrConvTimestamp for printing timestamps Add ErrConvTimestamp, an ErrConv that formats a Timestamp in the session time zone into a fixed buffer for warning and error messages, with the number of fractional digits given explicitly. ErrBuff::set_mysql_time() gets an optional digits argument for it. Use it when printing STARTS and the history range. Rename the error symbol ER_PART_STARTS_BEYOND_INTERVAL to WARN_VERS_STARTS_BEYOND_INTERVAL and extend its message: it now takes the STARTS timestamp and the query timestamp as arguments, so the number of format arguments changes from one to three. The error number (4164) is unchanged, so this is not an ABI break, but libmariadb still exports the old symbol name for that number until its submodule is updated. --- .../suite/versioning/r/partition.result | 6 ++-- sql/partition_info.cc | 13 +++++--- sql/share/errmsg-utf8.txt | 8 ++--- sql/sql_class.h | 30 +++++++++++++++++++ sql/sql_error.h | 5 ++-- 5 files changed, 49 insertions(+), 13 deletions(-) diff --git a/mysql-test/suite/versioning/r/partition.result b/mysql-test/suite/versioning/r/partition.result index 7b9cd01c4363c..bfe5c52040b5f 100644 --- a/mysql-test/suite/versioning/r/partition.result +++ b/mysql-test/suite/versioning/r/partition.result @@ -472,7 +472,7 @@ PARTITIONS 2 create or replace table t1 (i int) with system versioning partition by system_time interval 1 day starts '2000-01-01 00:00:01'; Warnings: -Warning 4164 `t1`: STARTS is later than query time, first history partition may exceed INTERVAL value +Warning 4164 `t1`: STARTS timestamp 2000-01-01 00:00:01 is later than query timestamp 2000-01-01 00:00:00, first history partition may exceed INTERVAL value # Test default STARTS rounding set timestamp= unix_timestamp('1999-12-15 13:33:33'); create or replace table t1 (i int) with system versioning @@ -544,7 +544,7 @@ set time_zone="+03:00"; create or replace table t1 (i int) with system versioning partition by system_time interval 1 day starts '2000-01-01 00:00:00'; Warnings: -Warning 4164 `t1`: STARTS is later than query time, first history partition may exceed INTERVAL value +Warning 4164 `t1`: STARTS timestamp 2000-01-01 00:00:00 is later than query timestamp 1999-12-15 16:33:33, first history partition may exceed INTERVAL value set timestamp= unix_timestamp('2000-01-01 00:00:00'); create or replace table t2 (i int) with system versioning partition by system_time interval 1 day; @@ -604,7 +604,7 @@ create or replace table t1 (i int) with system versioning partition by system_time interval 1 day starts '2000-01-03 00:00:00' partitions 3; Warnings: -Warning 4164 `t1`: STARTS is later than query time, first history partition may exceed INTERVAL value +Warning 4164 `t1`: STARTS timestamp 2000-01-03 00:00:00 is later than query timestamp 2000-01-01 00:00:00, first history partition may exceed INTERVAL value insert into t1 values (0); set timestamp= unix_timestamp('2000-01-01 00:00:01'); update t1 set i= i + 1; diff --git a/sql/partition_info.cc b/sql/partition_info.cc index d1f85e2683441..5508e5b2c8ce1 100644 --- a/sql/partition_info.cc +++ b/sql/partition_info.cc @@ -2870,11 +2870,16 @@ bool partition_info::vers_set_interval(THD* thd, Item* interval, } if (!table) { - if (thd->query_start() < vers_info->interval.start) { + if (thd->query_start() < vers_info->interval.start) + { + ErrConvTimestamp str_interval(thd, + Timestamp(vers_info->interval.start, 0), + 0); + ErrConvTimestamp str_query(thd, Timestamp(thd->query_start(), 0), 0); push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, - ER_PART_STARTS_BEYOND_INTERVAL, - ER_THD(thd, ER_PART_STARTS_BEYOND_INTERVAL), - table_name); + WARN_VERS_STARTS_BEYOND_INTERVAL, + ER_THD(thd, WARN_VERS_STARTS_BEYOND_INTERVAL), + table_name, str_interval.ptr(), str_query.ptr()); } } } diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 3bc85fefad84b..04c504105ff06 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -12142,10 +12142,10 @@ ER_UNKNOWN_OPERATOR sw "Opereta haipo: '%-.128s'" ER_SP_DUP_DECL eng "Duplicate declaration: '%-.64s'" -ER_PART_STARTS_BEYOND_INTERVAL - eng "%sQ: STARTS is later than query time, first history partition may exceed INTERVAL value" - spa "%sQ: STARTS es posterior al momento de consulta (query), la primera partición de historia puede exceder el valor INTERVAL" - sw "%sQ: STARTS ni ya baadaye kuliko muda wa hoja, kizigeu cha kwanza ya historia inaweza kuzidi thamani ya INTERVAL" +WARN_VERS_STARTS_BEYOND_INTERVAL + eng "%sQ: STARTS timestamp %s is later than query timestamp %s, first history partition may exceed INTERVAL value" + spa "%sQ: STARTS %s es posterior al momento de consulta (query) %s, la primera partición de historia puede exceder el valor INTERVAL" + sw "%sQ: STARTS %s ni wa baadaye kuliko muda wa hoja %s, kizigeu cha kwanza cha historia kinaweza kuzidi thamani ya INTERVAL" ER_GALERA_REPLICATION_NOT_SUPPORTED eng "Galera replication not supported" spa "La replicación en Galera no está soportada" diff --git a/sql/sql_class.h b/sql/sql_class.h index d9d7f8b3658e8..a192ab7da12bb 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -8836,6 +8836,36 @@ class Write_log_with_flags }; +/** + Format a Timestamp in the session time zone for an error or warning. + + Holds the value and formats it when the message is built. It formats into + the fixed ErrBuff buffer without allocation, so it cannot fail. +*/ +class ErrConvTimestamp : public ErrConv +{ + THD *thd; + Timestamp ts; + uint dec; +public: + /** + @param thd_arg Current thread (for the session time zone). + @param ts_arg Value to format. + @param dec_arg Number of fractional second digits (0-6). + */ + ErrConvTimestamp(THD *thd_arg, const Timestamp &ts_arg, uint dec_arg) + : ErrConv(), thd(thd_arg), ts(ts_arg), dec(dec_arg) + { + DBUG_ASSERT(dec <= TIME_SECOND_PART_DIGITS); + } + LEX_CSTRING lex_cstring() const override + { + MYSQL_TIME ltime; + ts.to_TIME(thd, <ime, date_mode_t(0)); + return set_mysql_time(<ime, dec); + } +}; + /** Make a new string allocated on THD's mem-root. diff --git a/sql/sql_error.h b/sql/sql_error.h index bec9e794a25fb..5d32506d22662 100644 --- a/sql/sql_error.h +++ b/sql/sql_error.h @@ -897,9 +897,10 @@ class ErrBuff err_buffer[len+2]= 0; return {err_buffer, len+2}; } - LEX_CSTRING set_mysql_time(const MYSQL_TIME *ltime) const + LEX_CSTRING set_mysql_time(const MYSQL_TIME *ltime, + uint dec= AUTO_SEC_PART_DIGITS) const { - int length= my_TIME_to_str(ltime, err_buffer, AUTO_SEC_PART_DIGITS); + int length= my_TIME_to_str(ltime, err_buffer, dec); DBUG_ASSERT(length >= 0); return {err_buffer, (size_t) length}; } From 3374ab18b24d3f06400e5405d5d4d0c07d43312a Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Mon, 29 Jun 2026 14:00:02 +0300 Subject: [PATCH 5/7] MDEV-25529 Extract vers_set_starts() and add starts_clause Extracts the STARTS-alignment logic into vers_set_starts() so it can be reused. If the aligned local time falls into a DST gap (e.g. a midnight that does not exist), the start of the gap is used instead of failing with ER_PART_WRONG_VALUE. If it falls before the TIMESTAMP range (e.g. a local midnight before 1970), the timestamp itself is used. Also adds the Vers_part_info::starts_clause flag in preparation for later commits. The flag records whether STARTS was given explicitly (by the user or in the unpacked frm), which the auto-partitioning code uses to decide whether to re-align STARTS to the existing history. --- sql/partition_info.cc | 73 ++++++++++++++++++++++++++++++++++--------- sql/partition_info.h | 10 ++++++ 2 files changed, 68 insertions(+), 15 deletions(-) diff --git a/sql/partition_info.cc b/sql/partition_info.cc index 5508e5b2c8ce1..1e66b1863e280 100644 --- a/sql/partition_info.cc +++ b/sql/partition_info.cc @@ -2842,6 +2842,7 @@ bool partition_info::vers_set_interval(THD* thd, Item* interval, /* 2. assign STARTS to interval.start */ if (starts) { + vers_info->starts_clause= true; if (starts->fix_fields_if_needed_for_scalar(thd, &starts)) return true; switch (starts->result_type()) @@ -2885,21 +2886,8 @@ bool partition_info::vers_set_interval(THD* thd, Item* interval, } else // calculate default STARTS depending on INTERVAL { - thd->variables.time_zone->gmt_sec_to_TIME(<ime, thd->query_start()); - if (vers_info->interval.step.second) - goto interval_set_starts; - ltime.second= 0; - if (vers_info->interval.step.minute) - goto interval_set_starts; - ltime.minute= 0; - if (vers_info->interval.step.hour) - goto interval_set_starts; - ltime.hour= 0; - -interval_set_starts: - vers_info->interval.start= TIME_to_timestamp(thd, <ime, &err); - if (err) - goto interval_starts_error; + if (vers_set_starts(thd, thd->query_start(), table_name)) + return true; } return false; @@ -2910,6 +2898,61 @@ bool partition_info::vers_set_interval(THD* thd, Item* interval, } +/** + Set STARTS to @p ts with the time-of-day parts below the finest time unit + of the INTERVAL cleared. + + Converts @p ts to broken-down local time in the current session time zone, + then clears time components in order (second, then minute, then hour), + stopping at the finest (smallest) non-zero unit present in + @c vers_info->interval.step. Day, month and year are never cleared, so + this is not a multiple of the interval: INTERVAL 2 HOUR keeps an odd hour + and a month interval keeps the day. If the truncated local time falls into + a DST gap, the start of the gap is used; if it falls before the TIMESTAMP + range, @p ts itself is used. The result is stored in + @c vers_info->interval.start. + + @param thd Current thread. + @param ts Timestamp to align. + @param table_name Table name, for the ER_PART_WRONG_VALUE message. + + @retval false Success. + @retval true Error, reported as ER_PART_WRONG_VALUE. +*/ +bool partition_info::vers_set_starts(THD *thd, my_time_t ts, + const char *table_name) +{ + MYSQL_TIME ltime; + uint err; + thd->variables.time_zone->gmt_sec_to_TIME(<ime, ts); + if (vers_info->interval.step.second) + goto interval_set_starts; + ltime.second= 0; + if (vers_info->interval.step.minute) + goto interval_set_starts; + ltime.minute= 0; + if (vers_info->interval.step.hour) + goto interval_set_starts; + ltime.hour= 0; + +interval_set_starts: + vers_info->interval.start= TIME_to_timestamp(thd, <ime, &err); + /* Truncated below the TIMESTAMP range (e.g. local midnight before 1970) */ + if (err == ER_WARN_DATA_OUT_OF_RANGE) + { + vers_info->interval.start= ts; + err= 0; + } + /* In a DST gap TIME_to_timestamp() returns the start of the gap */ + if (err && err != ER_WARN_INVALID_TIMESTAMP) + { + my_error(ER_PART_WRONG_VALUE, MYF(0), table_name, "STARTS"); + return true; + } + return false; +} + + bool partition_info::vers_set_limit(ulonglong limit, bool auto_hist, const char *table_name) { diff --git a/sql/partition_info.h b/sql/partition_info.h index 32861d36ad279..b0bfd9e0ee249 100644 --- a/sql/partition_info.h +++ b/sql/partition_info.h @@ -37,6 +37,7 @@ struct Vers_part_info : public Sql_alloc Vers_part_info() : limit(0), auto_hist(false), + starts_clause(false), now_part(NULL), hist_part(NULL) { @@ -46,6 +47,7 @@ struct Vers_part_info : public Sql_alloc interval(src.interval), limit(src.limit), auto_hist(src.auto_hist), + starts_clause(src.starts_clause), now_part(NULL), hist_part(NULL) { @@ -55,6 +57,7 @@ struct Vers_part_info : public Sql_alloc interval= src.interval; limit= src.limit; auto_hist= src.auto_hist; + starts_clause= src.starts_clause; now_part= src.now_part; hist_part= src.hist_part; return *this; @@ -87,6 +90,12 @@ struct Vers_part_info : public Sql_alloc } interval; ulonglong limit; bool auto_hist; + /* + STARTS was given explicitly. Always true for INTERVAL partitioning + unpacked from the frm, so only in a parsed statement it tells that the + user gave STARTS. + */ + bool starts_clause; partition_element *now_part; partition_element *hist_part; }; @@ -433,6 +442,7 @@ class partition_info : public DDL_LOG_STATE, public Sql_alloc bool vers_set_interval(THD *thd, Item *interval, interval_type int_type, Item *starts, bool auto_part, const char *table_name); + bool vers_set_starts(THD *thd, my_time_t ts, const char *table_name); bool vers_set_limit(ulonglong limit, bool auto_part, const char *table_name); bool vers_set_hist_part(THD* thd, uint *create_count); bool range_interval_set_count(THD* thd, uint *create_count); From 547a9b671102adf699be2380df57918d1f1937f4 Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Mon, 29 Jun 2026 14:00:03 +0300 Subject: [PATCH 6/7] MDEV-25529 ALTER TABLE FORCE syntax improved Lets an alter_list be supplied alongside a partitioning or remove_partitioning clause in either order. This allows the FORCE clause to be added to an existing command, and lets AUTO and FORCE be specified together, which later commits of this task rely on. --- mysql-test/main/partition.result | 18 ++++++++++++++++++ mysql-test/main/partition.test | 21 +++++++++++++++++++++ sql/sql_yacc.yy | 4 ++++ 3 files changed, 43 insertions(+) diff --git a/mysql-test/main/partition.result b/mysql-test/main/partition.result index 48cc686c6567c..4703071400e0d 100644 --- a/mysql-test/main/partition.result +++ b/mysql-test/main/partition.result @@ -2922,4 +2922,22 @@ DROP TABLE mdev20498; # # End of 10.6 tests # +# +# MDEV-25529 ALTER TABLE FORCE syntax improved +# +create table t1 (id int primary key); +alter table t1 force partition by hash(id) partitions 2; +alter table t1 force remove partitioning; +alter table t1 partition by hash(id) partitions 2 force; +alter table t1 remove partitioning force; +alter table t1 force partition by hash(id) partitions 2 force; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'force' at line 1 +alter table t1 force remove partitioning force; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'force' at line 1 +alter table t1 add column x int partition by hash(id) partitions 2; +alter table t1 remove partitioning add column y int; +drop table t1; +# +# End of 13.2 tests +# ALTER DATABASE test CHARACTER SET utf8mb4 COLLATE utf8mb4_uca1400_ai_ci; diff --git a/mysql-test/main/partition.test b/mysql-test/main/partition.test index 4f6ec51659cff..7e30331b33ccb 100644 --- a/mysql-test/main/partition.test +++ b/mysql-test/main/partition.test @@ -3147,4 +3147,25 @@ DROP TABLE mdev20498; --echo # End of 10.6 tests --echo # +--echo # +--echo # MDEV-25529 ALTER TABLE FORCE syntax improved +--echo # +create table t1 (id int primary key); +alter table t1 force partition by hash(id) partitions 2; +alter table t1 force remove partitioning; +alter table t1 partition by hash(id) partitions 2 force; +alter table t1 remove partitioning force; +--error ER_PARSE_ERROR +alter table t1 force partition by hash(id) partitions 2 force; +--error ER_PARSE_ERROR +alter table t1 force remove partitioning force; +# alter_list is accepted before and after partitioning/remove_partitioning +alter table t1 add column x int partition by hash(id) partitions 2; +alter table t1 remove partitioning add column y int; +drop table t1; + +--echo # +--echo # End of 13.2 tests +--echo # + --source include/test_db_charset_restore.inc diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 4ec4932c950fe..0f5659d037c52 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -7975,8 +7975,12 @@ alter_commands: } | alter_list opt_partitioning + | partitioning + alter_list | alter_list remove_partitioning + | remove_partitioning + alter_list | remove_partitioning | partitioning /* From cc85c4fb7a8eeaddf910fa0be358e7c8860402be Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Mon, 29 Jun 2026 14:00:03 +0300 Subject: [PATCH 7/7] MDEV-25529 Auto-create: Pre-existing historical data is not partitioned as specified by ALTER Adds logic into prep_alter_part_table() for INTERVAL ... AUTO to read the history range (vers_get_history_range()) and size the new layout from it. STARTS is aligned to the oldest history row_end (vers_set_starts()) if it was not specified by the user, or if the user specified it later than that; in the latter case WARN_VERS_WRONG_STARTS is printed. The history partitions cover both the newest history row_end and the query time, so DML after the ALTER does not need to create partitions. A PARTITIONS N clause is kept if it gives more partitions. An explicit subpartition list is replaced by as many default subpartitions. If the layout needs more than MAX_PARTITIONS partitions (counting subpartitions), or the INTERVAL is longer than the date range, the ALTER fails with ER_VERS_HIST_PART_FAILED. Month and year intervals are stepped in UTC; a boundary past the maximum TIMESTAMP ends a partial last history partition. In case of fast ALTER TABLE, f.ex. when partitioning already exists, the above logic is ignored unless FORCE clause is specified. When user specifies partition list explicitly the above logic is ignored even with FORCE clause. With FORCE on a table already partitioned by SYSTEM_TIME the work is done on a clone of the partitioning info. The parsed settings are copied into the clone's vers_info with Vers_part_info::copy_settings(), which keeps the clone's now_part and hist_part (the parsed ones are NULL). Before reading the range, the metadata lock is upgraded to MDL_SHARED_NO_WRITE, and then the copy does not run online, so DML cannot add history that would not fit the computed layout until the ALTER ends (Alter_info::vers_history_read). LOCK=NONE and ALGORITHM=INPLACE/NOCOPY/INSTANT are rejected for such ALTER before the upgrade, so they do not wait for concurrent DML; the ALGORITHM error is reported by the new Alter_info::report_algorithm_unsupported(), which mysql_alter_table() uses as well. The range is read under a table lock taken with mysql_lock_tables(), as some engines (Aria) set up their transaction in THR_LOCK callbacks. The scan can be killed. WARN_VERS_STARTS_BEYOND_INTERVAL for ALTER .. AUTO is checked after the alignment, so it is not printed when STARTS was re-aligned to the history. vers_get_history_range() detects if an index can be used for row_end min/max stats and if so it gets it with ha_index_first() and HA_READ_BEFORE_KEY (as it must ignore current data). Otherwise it does a table scan to read the stats. Current rows with the pre-11.5 row_end marker are not taken for history either. In the index they sort below history after 2038, so the minimum is read after them. There is test_mdev-25529 debug keyword to check the both and compare results. WARN_VERS_SLOW_ROW_END is printed if the scan read any row and there is no usable index (a disabled one does not count as missing). Static key_cmp was renamed to key_eq to resolve compilation after key.h was included as key_cmp was already declared there. --- .../suite/versioning/r/old_timestamp.result | 29 + .../suite/versioning/r/partition2.result | 1306 +++++++++++++++++ .../suite/versioning/t/old_timestamp.test | 21 + mysql-test/suite/versioning/t/partition2.test | 832 +++++++++++ sql/partition_info.cc | 49 +- sql/partition_info.h | 18 +- sql/share/errmsg-utf8.txt | 8 + sql/sql_alter.cc | 17 +- sql/sql_alter.h | 18 +- sql/sql_partition.cc | 238 ++- sql/sql_table.cc | 280 +++- sql/table.h | 3 + 12 files changed, 2784 insertions(+), 35 deletions(-) create mode 100644 mysql-test/suite/versioning/r/partition2.result create mode 100644 mysql-test/suite/versioning/t/partition2.test diff --git a/mysql-test/suite/versioning/r/old_timestamp.result b/mysql-test/suite/versioning/r/old_timestamp.result index 64a2aae612d6a..0621ee1a0bead 100644 --- a/mysql-test/suite/versioning/r/old_timestamp.result +++ b/mysql-test/suite/versioning/r/old_timestamp.result @@ -104,3 +104,32 @@ a row_foo_end 2 2023-12-19 14:23:22.359113 3 2106-02-07 06:28:15.999999 drop table sv_basic,sv_partition,sv_explicit; +# +# MDEV-25529 Auto-create: Pre-existing historical data is not +# partitioned as specified by ALTER +# +# Current rows of a table that is not upgraded keep the old row_end +# marker; they are not history. +select a, row_end from t1 for system_time all order by a; +a row_end +1 2038-01-19 03:14:07.999999 +2 2023-12-19 14:23:22.305292 +3 2038-01-19 03:14:07.999999 +alter table t1 partition by system_time interval 1 hour auto; +Warnings: +Warning 4277 `t1`: no usable ROW END index, using slow scan; add an ordered index starting with the ROW END column (ascending, not DESC) to avoid it +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` int(11) NOT NULL, + PRIMARY KEY (`a`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2023-12-19 14:00:00' AUTO +PARTITIONS 2 +select a from t1 partition (pn) order by a; +a +1 +3 +set timestamp= default; +drop table t1; +# End of 13.2 tests diff --git a/mysql-test/suite/versioning/r/partition2.result b/mysql-test/suite/versioning/r/partition2.result new file mode 100644 index 0000000000000..e72c4feb78f75 --- /dev/null +++ b/mysql-test/suite/versioning/r/partition2.result @@ -0,0 +1,1306 @@ +set @@session.time_zone='+00:00'; +# +# MDEV-25529 Auto-create: Pre-existing historical data is not partitioned as specified by ALTER +# +create or replace table t1 (x int) with system versioning +partition by system_time limit 1; +alter table t1 partition by system_time limit 1 auto; +create or replace table t1 (x int, +row_start timestamp(6) as row start invisible, +row_end timestamp(6) as row end invisible, +period for system_time (row_start, row_end), +index (row_end, row_start), +index (row_start), +index (row_end, x), +index (x, row_end), +index (row_end desc)) with system versioning +partition by system_time limit 1000 partitions 6; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`,`row_start`), + KEY `row_start` (`row_start`), + KEY `row_end_2` (`row_end`,`x`), + KEY `x` (`x`,`row_end`), + KEY `row_end_3` (`row_end` DESC), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME LIMIT 1000 +PARTITIONS 6 +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 00:10:00.22'); +update t1 set x= x + 1; +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= x + 1; +set timestamp= unix_timestamp('2000-01-01 01:30:00'); +update t1 set x= x + 1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +update t1 set x= x + 1; +select *, row_start, row_end from t1 partition (p0); +x row_start row_end +0 2000-01-01 00:00:00.000000 2000-01-01 00:10:00.220000 +1 2000-01-01 00:10:00.220000 2000-01-01 01:00:00.000000 +2 2000-01-01 01:00:00.000000 2000-01-01 01:30:00.000000 +3 2000-01-01 01:30:00.000000 2000-01-01 02:00:00.000000 +select *, row_start, row_end from t1 partition (p1); +x row_start row_end +select *, row_start, row_end from t1 partition (p2); +x row_start row_end +select *, row_start, row_end from t1 partition (p3); +x row_start row_end +select *, row_start, row_end from t1 partition (p4); +x row_start row_end +select *, row_start, row_end from t1 partition (pn); +x row_start row_end +4 2000-01-01 02:00:00.000000 2106-02-07 06:28:15.999999 +set timestamp= default; +ALTER table t1 partition by system_time +interval 1 hour starts '2000-01-01 00:00:00'; +select *, row_start, row_end from t1 partition (p0); +x row_start row_end +0 2000-01-01 00:00:00.000000 2000-01-01 00:10:00.220000 +select *, row_start, row_end from t1 partition (p1); +x row_start row_end +1 2000-01-01 00:10:00.220000 2000-01-01 01:00:00.000000 +2 2000-01-01 01:00:00.000000 2000-01-01 01:30:00.000000 +select *, row_start, row_end from t1 partition (p2); +x row_start row_end +3 2000-01-01 01:30:00.000000 2000-01-01 02:00:00.000000 +select *, row_start, row_end from t1 partition (p3); +x row_start row_end +select *, row_start, row_end from t1 partition (p4); +x row_start row_end +select *, row_start, row_end from t1 partition (pn); +x row_start row_end +4 2000-01-01 02:00:00.000000 2106-02-07 06:28:15.999999 +alter TABLE t1 partition by system_time +interval 1 hour; +select *, row_start, row_end from t1 partition (p0); +x row_start row_end +0 2000-01-01 00:00:00.000000 2000-01-01 00:10:00.220000 +select *, row_start, row_end from t1 partition (p1); +x row_start row_end +1 2000-01-01 00:10:00.220000 2000-01-01 01:00:00.000000 +2 2000-01-01 01:00:00.000000 2000-01-01 01:30:00.000000 +select *, row_start, row_end from t1 partition (p2); +x row_start row_end +3 2000-01-01 01:30:00.000000 2000-01-01 02:00:00.000000 +select *, row_start, row_end from t1 partition (p3); +x row_start row_end +select *, row_start, row_end from t1 partition (p4); +x row_start row_end +select *, row_start, row_end from t1 partition (pn); +x row_start row_end +4 2000-01-01 02:00:00.000000 2106-02-07 06:28:15.999999 +set @@system_versioning_alter_history= keep; +alter table t1 remove partitioning; +select *, row_start, row_end from t1 for system_time all; +x row_start row_end +0 2000-01-01 00:00:00.000000 2000-01-01 00:10:00.220000 +1 2000-01-01 00:10:00.220000 2000-01-01 01:00:00.000000 +2 2000-01-01 01:00:00.000000 2000-01-01 01:30:00.000000 +3 2000-01-01 01:30:00.000000 2000-01-01 02:00:00.000000 +4 2000-01-01 02:00:00.000000 2106-02-07 06:28:15.999999 +set timestamp= unix_timestamp('2000-01-01 02:30:00'); +alter table t1 PARTITION by system_time interval 1 hour auto; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`,`row_start`), + KEY `row_start` (`row_start`), + KEY `row_end_2` (`row_end`,`x`), + KEY `x` (`x`,`row_end`), + KEY `row_end_3` (`row_end` DESC), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2000-01-01 00:00:00' AUTO +PARTITIONS 4 +select *, row_start, row_end from t1 partition (p0); +x row_start row_end +0 2000-01-01 00:00:00.000000 2000-01-01 00:10:00.220000 +select *, row_start, row_end from t1 partition (p1); +x row_start row_end +1 2000-01-01 00:10:00.220000 2000-01-01 01:00:00.000000 +2 2000-01-01 01:00:00.000000 2000-01-01 01:30:00.000000 +select *, row_start, row_end from t1 partition (pn); +x row_start row_end +4 2000-01-01 02:00:00.000000 2106-02-07 06:28:15.999999 +alter table t1 remove partitioning; +alter table t1 partition by SYSTEM_TIME interval 1 hour starts '2000-01-01 00:00:00' auto; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`,`row_start`), + KEY `row_start` (`row_start`), + KEY `row_end_2` (`row_end`,`x`), + KEY `x` (`x`,`row_end`), + KEY `row_end_3` (`row_end` DESC), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2000-01-01 00:00:00' AUTO +PARTITIONS 4 +select *, row_start, row_end from t1 partition (p0); +x row_start row_end +0 2000-01-01 00:00:00.000000 2000-01-01 00:10:00.220000 +select *, row_start, row_end from t1 partition (p1); +x row_start row_end +1 2000-01-01 00:10:00.220000 2000-01-01 01:00:00.000000 +2 2000-01-01 01:00:00.000000 2000-01-01 01:30:00.000000 +select *, row_start, row_end from t1 partition (pn); +x row_start row_end +4 2000-01-01 02:00:00.000000 2106-02-07 06:28:15.999999 +set timestamp= unix_timestamp('2000-01-02 03:01:23.456'); +delete from t1; +alter table t1 remove partitioning; +alter table t1 partition by SYSTEM_TIME interval 1 hour auto; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`,`row_start`), + KEY `row_start` (`row_start`), + KEY `row_end_2` (`row_end`,`x`), + KEY `x` (`x`,`row_end`), + KEY `row_end_3` (`row_end` DESC), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2000-01-01 00:00:00' AUTO +PARTITIONS 29 +select *, row_start, row_end from t1 partition (p0); +x row_start row_end +0 2000-01-01 00:00:00.000000 2000-01-01 00:10:00.220000 +select *, row_start, row_end from t1 partition (p1); +x row_start row_end +1 2000-01-01 00:10:00.220000 2000-01-01 01:00:00.000000 +2 2000-01-01 01:00:00.000000 2000-01-01 01:30:00.000000 +select *, row_start, row_end from t1 partition (p2); +x row_start row_end +3 2000-01-01 01:30:00.000000 2000-01-01 02:00:00.000000 +select *, row_start, row_end from t1 partition (p27); +x row_start row_end +4 2000-01-01 02:00:00.000000 2000-01-02 03:01:23.456000 +select *, row_start, row_end from t1 partition (pn); +x row_start row_end +set timestamp= unix_timestamp('2000-01-02 03:01:23.456'); +delete from t1; +alter table t1 partition by system_time INTERVAL 20 minute auto force; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`,`row_start`), + KEY `row_start` (`row_start`), + KEY `row_end_2` (`row_end`,`x`), + KEY `x` (`x`,`row_end`), + KEY `row_end_3` (`row_end` DESC), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 20 MINUTE STARTS TIMESTAMP'2000-01-01 00:10:00' AUTO +PARTITIONS 82 +select *, row_start, row_end from t1 partition (p0); +x row_start row_end +0 2000-01-01 00:00:00.000000 2000-01-01 00:10:00.220000 +select *, row_start, row_end from t1 partition (p2); +x row_start row_end +1 2000-01-01 00:10:00.220000 2000-01-01 01:00:00.000000 +select *, row_start, row_end from t1 partition (p5); +x row_start row_end +3 2000-01-01 01:30:00.000000 2000-01-01 02:00:00.000000 +select *, row_start, row_end from t1 partition (pn); +x row_start row_end +# STARTS after the oldest history is re-aligned: WARN_VERS_WRONG_STARTS +alter table t1 remove partitioning; +alter table t1 partition by system_time interval 1 hour +starts '2002-01-01 00:00:00' auto; +Warnings: +Warning 4276 `t1`: STARTS timestamp 2002-01-01 00:00:00 is after earliest history timestamp 2000-01-01 00:10:00.220000 and was set to 2000-01-01 00:00:00 +alter table t1 force partition by system_time interval 1 hour +starts '2003-01-01 00:00:00' auto; +Warnings: +Warning 4276 `t1`: STARTS timestamp 2003-01-01 00:00:00 is after earliest history timestamp 2000-01-01 00:10:00.220000 and was set to 2000-01-01 00:00:00 +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`,`row_start`), + KEY `row_start` (`row_start`), + KEY `row_end_2` (`row_end`,`x`), + KEY `x` (`x`,`row_end`), + KEY `row_end_3` (`row_end` DESC), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2000-01-01 00:00:00' AUTO +PARTITIONS 29 +alter table t1 partition by system_time interval 1 hour +starts '2004-01-01 00:00:00' auto force; +Warnings: +Warning 4276 `t1`: STARTS timestamp 2004-01-01 00:00:00 is after earliest history timestamp 2000-01-01 00:10:00.220000 and was set to 2000-01-01 00:00:00 +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`,`row_start`), + KEY `row_start` (`row_start`), + KEY `row_end_2` (`row_end`,`x`), + KEY `x` (`x`,`row_end`), + KEY `row_end_3` (`row_end` DESC), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2000-01-01 00:00:00' AUTO +PARTITIONS 29 +# STARTS is not adjusted to the history range without FORCE +alter table t1 partition by system_time interval 1 hour +starts '2005-01-01 00:00:00' auto; +Warnings: +Warning 4164 `t1`: STARTS timestamp 2005-01-01 00:00:00 is later than query timestamp 2000-01-02 03:01:23, first history partition may exceed INTERVAL value +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`,`row_start`), + KEY `row_start` (`row_start`), + KEY `row_end_2` (`row_end`,`x`), + KEY `x` (`x`,`row_end`), + KEY `row_end_3` (`row_end` DESC), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2005-01-01 00:00:00' AUTO +PARTITIONS 29 +# min_ts == max_ts case, partitions decrease +delete history from t1; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (77); +delete from t1; +alter table t1 partition by system_time interval 7 hour auto force; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`,`row_start`), + KEY `row_start` (`row_start`), + KEY `row_end_2` (`row_end`,`x`), + KEY `x` (`x`,`row_end`), + KEY `row_end_3` (`row_end` DESC), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 7 HOUR STARTS TIMESTAMP'2000-01-01 00:00:00' AUTO +PARTITIONS 2 +# AUTO FORCE re-lays out; with an explicit partition list it does not +set timestamp= unix_timestamp('2000-01-03 00:00:00'); +insert t1 values (88); +delete from t1; +alter table t1 partition by system_time interval 8 hour auto force; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`,`row_start`), + KEY `row_start` (`row_start`), + KEY `row_end_2` (`row_end`,`x`), + KEY `x` (`x`,`row_end`), + KEY `row_end_3` (`row_end` DESC), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 8 HOUR STARTS TIMESTAMP'2000-01-01 00:00:00' AUTO +PARTITIONS 8 +alter table t1 partition by system_time interval 8 hour auto ( +partition p0 history, +partition p1 history, +partition pn current) force; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`,`row_start`), + KEY `row_start` (`row_start`), + KEY `row_end_2` (`row_end`,`x`), + KEY `x` (`x`,`row_end`), + KEY `row_end_3` (`row_end` DESC), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 8 HOUR STARTS TIMESTAMP'2000-01-03 00:00:00' AUTO +(PARTITION `p0` HISTORY ENGINE = MyISAM, + PARTITION `p1` HISTORY ENGINE = MyISAM, + PARTITION `pn` CURRENT ENGINE = MyISAM) +# Switch system_time -> hash +alter table t1 partition by hash(x) partitions 3; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`,`row_start`), + KEY `row_start` (`row_start`), + KEY `row_end_2` (`row_end`,`x`), + KEY `x` (`x`,`row_end`), + KEY `row_end_3` (`row_end` DESC), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY HASH (`x`) +PARTITIONS 3 +# Switch hash -> system_time +alter table t1 partition by system_time interval 9 hour +starts '2001-01-01 00:00:00' auto force; +Warnings: +Warning 4276 `t1`: STARTS timestamp 2001-01-01 00:00:00 is after earliest history timestamp 2000-01-01 00:00:00.000000 and was set to 2000-01-01 00:00:00 +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`,`row_start`), + KEY `row_start` (`row_start`), + KEY `row_end_2` (`row_end`,`x`), + KEY `x` (`x`,`row_end`), + KEY `row_end_3` (`row_end` DESC), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 9 HOUR STARTS TIMESTAMP'2000-01-01 00:00:00' AUTO +PARTITIONS 7 +# Year/month interval +delete history from t1; +alter table t1 remove partitioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (77); +delete from t1; +alter table t1 partition by system_time interval 1 month auto force; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`,`row_start`), + KEY `row_start` (`row_start`), + KEY `row_end_2` (`row_end`,`x`), + KEY `x` (`x`,`row_end`), + KEY `row_end_3` (`row_end` DESC), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 MONTH STARTS TIMESTAMP'2000-01-01 00:00:00' AUTO +PARTITIONS 2 +set timestamp= unix_timestamp('2003-10-01 00:00:00'); +insert t1 values (88); +delete from t1; +alter table t1 partition by system_time interval 11 month auto force; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`,`row_start`), + KEY `row_start` (`row_start`), + KEY `row_end_2` (`row_end`,`x`), + KEY `x` (`x`,`row_end`), + KEY `row_end_3` (`row_end` DESC), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 11 MONTH STARTS TIMESTAMP'2000-01-01 00:00:00' AUTO +PARTITIONS 6 +alter table t1 partition by system_time interval 1 year auto force; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`,`row_start`), + KEY `row_start` (`row_start`), + KEY `row_end_2` (`row_end`,`x`), + KEY `x` (`x`,`row_end`), + KEY `row_end_3` (`row_end` DESC), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 YEAR STARTS TIMESTAMP'2000-01-01 00:00:00' AUTO +PARTITIONS 5 +# No history +delete history from t1; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +alter table t1 partition by system_time interval 1 hour auto force; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`,`row_start`), + KEY `row_start` (`row_start`), + KEY `row_end_2` (`row_end`,`x`), + KEY `x` (`x`,`row_end`), + KEY `row_end_3` (`row_end` DESC), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2000-01-01 00:00:00' AUTO +PARTITIONS 5 +insert t1 values (99); +alter table t1 partition by system_time interval 1 hour auto force; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`,`row_start`), + KEY `row_start` (`row_start`), + KEY `row_end_2` (`row_end`,`x`), + KEY `x` (`x`,`row_end`), + KEY `row_end_3` (`row_end` DESC), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2000-01-01 00:00:00' AUTO +PARTITIONS 5 +# Slow scan warning +create or replace table t1 (x int, +row_start timestamp(6) as row start invisible, +row_end timestamp(6) as row end invisible, +period for system_time (row_start, row_end), +index (x, row_end, row_start), +index (row_start, row_end), +index (x, row_end)) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (77); +delete from t1; +set timestamp= unix_timestamp('2000-01-03 00:00:00'); +insert t1 values (88); +delete from t1; +alter table t1 partition by system_time interval 10 hour auto; +Warnings: +Warning 4277 `t1`: no usable ROW END index, using slow scan; add an ordered index starting with the ROW END column (ascending, not DESC) to avoid it +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `x` (`x`,`row_end`,`row_start`), + KEY `row_start` (`row_start`,`row_end`), + KEY `x_2` (`x`,`row_end`), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 10 HOUR STARTS TIMESTAMP'2000-01-01 00:00:00' AUTO +PARTITIONS 6 +# TRX_ID versioning +create or replace table t1 (x int, +row_start bigint(20) unsigned as row start invisible, +row_end bigint(20) unsigned as row end invisible, +period for system_time (row_start, row_end), +index (row_end)) with system versioning engine innodb; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (77); +delete from t1; +set timestamp= unix_timestamp('2000-01-03 00:00:00'); +insert t1 values (88); +delete from t1; +# Partitioning is not allowed for TRX_ID versioning +alter table t1 partition by system_time interval 11 hour auto; +ERROR HY000: `row_start` must be of type TIMESTAMP(6) for system-versioned table `t1` +drop table t1; +# Max history row_end exactly on an interval boundary. A boundary value +# start + k*i belongs to partition k, so k+1 history partitions are +# needed. Here start=00:00, interval 1 hour, max history row_end=02:00:00 +# belongs to p2 = [02:00, 03:00). +create or replace table t1 (x int, +row_start timestamp(6) as row start invisible, +row_end timestamp(6) as row end invisible, +period for system_time (row_start, row_end), +index (row_end, row_start)) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +update t1 set x= 2; +alter table t1 partition by system_time +interval 1 hour starts '2000-01-01 00:00:00' auto; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`,`row_start`), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2000-01-01 00:00:00' AUTO +PARTITIONS 4 +select x, row_end from t1 partition (p1); +x row_end +0 2000-01-01 01:00:00.000000 +select x, row_end from t1 partition (p2); +x row_end +1 2000-01-01 02:00:00.000000 +select x, row_end from t1 partition (pn); +x row_end +2 2106-02-07 06:28:15.999999 +set timestamp= default; +drop table t1; +# LIMIT ... AUTO on a table that already has history does not size the +# layout from the history: there is no INTERVAL to size it by. +create or replace table t1 (x int) with system versioning; +insert t1 values (0); +update t1 set x= 1; +alter table t1 partition by system_time limit 1 auto; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME LIMIT 1 AUTO +PARTITIONS 2 +drop table t1; +# A disabled row_end index must not be used for the history range. +# With the key disabled the code must fall back to a full scan and still +# lay out history correctly, not fail on the disabled index. +create or replace table t1 (x int, +row_start timestamp(6) as row start invisible, +row_end timestamp(6) as row end invisible, +period for system_time (row_start, row_end), +index k (row_end, row_start)) with system versioning engine=myisam; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +update t1 set x= 2; +set timestamp= unix_timestamp('2000-01-01 02:30:00'); +alter table t1 disable keys; +alter table t1 partition by system_time +interval 1 hour starts '2000-01-01 00:00:00' auto; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `k` (`row_end`,`row_start`), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2000-01-01 00:00:00' AUTO +PARTITIONS 4 +select x, row_end from t1 partition (p2); +x row_end +1 2000-01-01 02:00:00.000000 +# The history-range lookup takes a table lock. Under LOCK TABLES the +# handler is already locked, so it must not lock again. +alter table t1 remove partitioning; +alter table t1 enable keys; +lock tables t1 write; +alter table t1 partition by system_time +interval 1 hour starts '2000-01-01 00:00:00' auto; +unlock tables; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `k` (`row_end`,`row_start`), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2000-01-01 00:00:00' AUTO +PARTITIONS 4 +select x, row_end from t1 partition (p2); +x row_end +1 2000-01-01 02:00:00.000000 +set timestamp= default; +drop table t1; +# History range divided by INTERVAL exceeds MAX_PARTITIONS: the number +# of history partitions needed to cover the range is reported as +# ER_VERS_HIST_PART_FAILED (preceded by ER_TOO_MANY_PARTITIONS_ERROR). +create or replace table t1 (x int) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (1); +set timestamp= unix_timestamp('2000-01-01 00:00:01'); +update t1 set x= 2; +set timestamp= unix_timestamp('2000-01-01 06:00:00'); +update t1 set x= 3; +alter table t1 partition by system_time interval 1 second auto; +ERROR HY000: Versioned table `test`.`t1`: adding HISTORY partition(s) failed +show warnings; +Level Code Message +Warning 4277 `t1`: no usable ROW END index, using slow scan; add an ordered index starting with the ROW END column (ascending, not DESC) to avoid it +Warning 1499 Too many partitions (including subpartitions) were defined +Error 4189 Versioned table `test`.`t1`: adding HISTORY partition(s) failed +set timestamp= default; +drop table t1; +# Month/year history partition boundaries must be stepped in UTC, not in +# the session time zone. Under a DST zone (MET: CET=+1 winter, +# CEST=+2 summer, spring-forward 2024-03-31) the two disagree. +# +# STARTS aligns to local 2024-02-15 00:00 MET = 2024-02-14 23:00 UTC. +# Max history row_end is 2024-04-14 22:30:00 UTC, which falls in the +# one-hour gap between the UTC boundary (Apr 14 23:00) and the +# session-zone boundary (Apr 14 22:00, shifted by the +2 summer offset). +# UTC stepping covers it with two history partitions (PARTITIONS 3); +# stepping in MET would add one more (PARTITIONS 4). The query time +# (22:45 UTC) is inside the same gap. +set time_zone= 'MET'; +create or replace table t1 (x int) with system versioning; +set timestamp= unix_timestamp('2024-02-15 12:00:00'); +insert t1 values (1); +delete from t1; +set timestamp= unix_timestamp('2024-04-15 00:30:00'); +insert t1 values (2); +delete from t1; +set timestamp= unix_timestamp('2024-04-15 00:45:00'); +alter table t1 partition by system_time interval 1 month auto; +Warnings: +Warning 4277 `t1`: no usable ROW END index, using slow scan; add an ordered index starting with the ROW END column (ascending, not DESC) to avoid it +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 MONTH STARTS TIMESTAMP'2024-02-15 00:00:00' AUTO +PARTITIONS 3 +set timestamp= default; +set time_zone= '+00:00'; +drop table t1; +# STARTS aligned to a local midnight that does not exist: Moscow skipped +# 1981-04-01 00:00-01:00 (DST start), so the start of the gap is used +# instead of failing the ALTER. +set time_zone= 'Europe/Moscow'; +create or replace table t1 (x int) with system versioning; +set timestamp= unix_timestamp('1981-04-01 12:00:00'); +insert t1 values (1); +set timestamp= unix_timestamp('1981-04-01 13:00:00'); +delete from t1; +set timestamp= unix_timestamp('1981-04-01 14:00:00'); +alter table t1 partition by system_time interval 1 day auto; +Warnings: +Warning 4277 `t1`: no usable ROW END index, using slow scan; add an ordered index starting with the ROW END column (ascending, not DESC) to avoid it +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 DAY STARTS TIMESTAMP'1981-04-01 01:00:00' AUTO +PARTITIONS 2 +set timestamp= default; +set time_zone= '+00:00'; +drop table t1; +# STARTS later than the query time warns with AUTO too, unless STARTS +# is re-aligned to the history range +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +create or replace table t1 (x int) with system versioning +partition by system_time interval 1 hour starts '2000-01-01 01:00:00'; +Warnings: +Warning 4164 `t1`: STARTS timestamp 2000-01-01 01:00:00 is later than query timestamp 2000-01-01 00:00:00, first history partition may exceed INTERVAL value +# CREATE never re-aligns STARTS +create or replace table t1 (x int) with system versioning +partition by system_time interval 1 hour starts '2000-01-01 01:00:00' auto; +Warnings: +Warning 4164 `t1`: STARTS timestamp 2000-01-01 01:00:00 is later than query timestamp 2000-01-01 00:00:00, first history partition may exceed INTERVAL value +# No history: STARTS is not re-aligned +create or replace table t1 (x int) with system versioning; +alter table t1 partition by system_time interval 1 hour +starts '2000-01-01 01:00:00' auto; +Warnings: +Warning 4164 `t1`: STARTS timestamp 2000-01-01 01:00:00 is later than query timestamp 2000-01-01 00:00:00, first history partition may exceed INTERVAL value +alter table t1 force partition by system_time interval 1 hour +starts '2000-01-01 01:00:00' auto; +Warnings: +Warning 4164 `t1`: STARTS timestamp 2000-01-01 01:00:00 is later than query timestamp 2000-01-01 00:00:00, first history partition may exceed INTERVAL value +# Without AUTO: warns at parse time +alter table t1 partition by system_time interval 1 hour +starts '2000-01-01 01:00:00'; +Warnings: +Warning 4164 `t1`: STARTS timestamp 2000-01-01 01:00:00 is later than query timestamp 2000-01-01 00:00:00, first history partition may exceed INTERVAL value +# Explicit partition list: STARTS is not re-aligned despite history +set timestamp= unix_timestamp('1999-12-31 23:00:00'); +insert t1 values (1); +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +delete from t1; +alter table t1 partition by system_time interval 1 hour +starts '2000-01-01 01:00:00' auto ( +partition p0 history, +partition pn current) force; +Warnings: +Warning 4164 `t1`: STARTS timestamp 2000-01-01 01:00:00 is later than query timestamp 2000-01-01 00:00:00, first history partition may exceed INTERVAL value +drop table t1; +set timestamp= default; +# The history range is read under a table lock (Aria sets up its +# transaction in THR_LOCK callbacks) +create table t1 (x int, +row_start timestamp(6) as row start invisible, +row_end timestamp(6) as row end invisible, +period for system_time (row_start, row_end), +index (row_end, row_start)) with system versioning engine=aria; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +update t1 set x= 2; +set timestamp= unix_timestamp('2000-01-01 03:00:00'); +alter table t1 partition by system_time interval 1 hour auto; +select x, row_end from t1 partition (p0); +x row_end +0 2000-01-01 01:00:00.000000 +select x, row_end from t1 partition (p1); +x row_end +1 2000-01-01 02:00:00.000000 +select x from t1 partition (pn); +x +2 +drop table t1; +set timestamp= default; +# The layout covers the query time as well, so DML after the ALTER +# does not need to create partitions. STARTS aligns to 01:00, the query +# time 05:30 needs history partitions p0..p4. +create or replace table t1 (x int) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +update t1 set x= 2; +set timestamp= unix_timestamp('2000-01-01 05:30:00'); +alter table t1 partition by system_time interval 1 hour auto; +Warnings: +Warning 4277 `t1`: no usable ROW END index, using slow scan; add an ordered index starting with the ROW END column (ascending, not DESC) to avoid it +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2000-01-01 01:00:00' AUTO +PARTITIONS 6 +update t1 set x= 3; +select x, row_end from t1 partition (p4); +x row_end +2 2000-01-01 05:30:00.000000 +# Covering the query time needs too many partitions +alter table t1 remove partitioning; +set timestamp= default; +alter table t1 partition by system_time interval 1 hour auto; +ERROR HY000: Versioned table `test`.`t1`: adding HISTORY partition(s) failed +# DML keeps working +update t1 set x= 4; +drop table t1; +# DML waits from the history-range read until the ALTER is done, also +# during the copy (the copy does not run online). The ALTER at 03:00 +# lays out p0..p2, the UPDATE at 03:30 then puts its history row into +# p2 = [03:00, 04:00). Without the early metadata lock upgrade the +# UPDATE runs while the ALTER is copying. +create or replace table t1 (x int, +row_start timestamp(6) as row start invisible, +row_end timestamp(6) as row end invisible, +period for system_time (row_start, row_end), +index (row_end, row_start)) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +update t1 set x= 2; +set timestamp= default; +connect con1,localhost,root; +connect con2,localhost,root; +disconnect con1; +disconnect con2; +connection default; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`,`row_start`), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2000-01-01 01:00:00' AUTO +PARTITIONS 4 +select x, row_end from t1 partition (p2); +x row_end +2 2000-01-01 03:30:00.000000 +# The ALTER fails if concurrent DML cannot be blocked. It fails at the MDL +# upgrade, before the history range is read: otherwise covering the query +# time would fail with ER_VERS_HIST_PART_FAILED. +alter table t1 remove partitioning; +connect con1,localhost,root; +begin; +insert t1 values (5); +connection default; +set lock_wait_timeout= 1; +alter table t1 partition by system_time interval 1 hour auto; +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +set lock_wait_timeout= default; +connection con1; +commit; +disconnect con1; +connection default; +# Table lock failure in the history-range read (debug builds only) +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`,`row_start`), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING +drop table t1; +# KILL QUERY during the history-range scan (debug_sync builds only). +# With the index, test_mdev-25529 reads it before the scan. +create or replace table t1 (x int, +row_start timestamp(6) as row start invisible, +row_end timestamp(6) as row end invisible, +period for system_time (row_start, row_end), +index (row_end)) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +set timestamp= default; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING +drop table t1; +# The copy does not run online, so LOCK=NONE is rejected, and it is a +# copy, so ALGORITHM=INPLACE is rejected too. Both are rejected before the +# MDL upgrade, so they do not wait for concurrent DML. +create or replace table t1 (x int) with system versioning; +connect con1,localhost,root; +begin; +insert t1 values (1); +connection default; +set lock_wait_timeout= 1; +alter table t1 lock=none partition by system_time interval 1 hour auto; +ERROR 0A000: LOCK=NONE is not supported. Reason: PARTITION BY SYSTEM_TIME INTERVAL ... AUTO. Try LOCK=SHARED +alter table t1 algorithm=inplace partition by system_time interval 1 hour auto; +ERROR 0A000: ALGORITHM=INPLACE is not supported for this operation. Try ALGORITHM=COPY +set lock_wait_timeout= default; +connection con1; +commit; +disconnect con1; +connection default; +drop table t1; +# A failed ALTER ... AUTO FORCE on a table already partitioned by +# INTERVAL leaves its layout intact +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +create or replace table t1 (x int) with system versioning +partition by system_time interval 1 hour auto; +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2000-01-01 00:00:00' AUTO +PARTITIONS 3 +set timestamp= default; +alter table t1 partition by system_time interval 1 minute auto force; +ERROR HY000: Versioned table `test`.`t1`: adding HISTORY partition(s) failed +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2000-01-01 00:00:00' AUTO +PARTITIONS 3 +set timestamp= unix_timestamp('2000-01-01 01:30:00'); +update t1 set x= 2; +select x, row_end from t1 partition (p1); +x row_end +0 2000-01-01 01:00:00.000000 +1 2000-01-01 01:30:00.000000 +set timestamp= default; +drop table t1; +# Month interval near TIMESTAMP_MAX_VALUE: the boundary after the query +# time is past it, so the last history partition is partial +create or replace table t1 (x int) with system versioning; +set timestamp= unix_timestamp('2106-01-20 00:00:00'); +insert t1 values (1); +set timestamp= unix_timestamp('2106-01-25 00:00:00'); +delete from t1; +set timestamp= unix_timestamp('2106-01-26 00:00:00'); +alter table t1 partition by system_time interval 1 month auto; +Warnings: +Warning 4277 `t1`: no usable ROW END index, using slow scan; add an ordered index starting with the ROW END column (ascending, not DESC) to avoid it +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 MONTH STARTS TIMESTAMP'2106-01-25 00:00:00' AUTO +PARTITIONS 2 +set timestamp= default; +drop table t1; +# InnoDB: history range from the row_end index +create or replace table t1 (x int, +row_start timestamp(6) as row start invisible, +row_end timestamp(6) as row end invisible, +period for system_time (row_start, row_end), +index (row_end)) with system versioning engine=innodb; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +update t1 set x= 2; +set timestamp= unix_timestamp('2000-01-01 03:00:00'); +alter table t1 partition by system_time interval 1 hour auto; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2000-01-01 01:00:00' AUTO +PARTITIONS 4 +select x, row_end from t1 partition (p0); +x row_end +0 2000-01-01 01:00:00.000000 +select x, row_end from t1 partition (p1); +x row_end +1 2000-01-01 02:00:00.000000 +select x from t1 partition (pn); +x +2 +set timestamp= default; +drop table t1; +# MEMORY: history range from a scan +create or replace table t1 (x int) with system versioning engine=memory; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +update t1 set x= 2; +set timestamp= unix_timestamp('2000-01-01 03:00:00'); +alter table t1 partition by system_time interval 1 hour auto; +Warnings: +Warning 4277 `t1`: no usable ROW END index, using slow scan; add an ordered index starting with the ROW END column (ascending, not DESC) to avoid it +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL +) ENGINE=MEMORY DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2000-01-01 01:00:00' AUTO +PARTITIONS 4 +select x from t1 partition (p0); +x +0 +select x from t1 partition (p1); +x +1 +select x from t1 partition (pn); +x +2 +set timestamp= default; +drop table t1; +# History row_end in the last second of the TIMESTAMP range +set @@system_versioning_insert_history= 1; +create or replace table t1 (x int, +row_start timestamp(6) as row start invisible, +row_end timestamp(6) as row end invisible, +period for system_time (row_start, row_end)) with system versioning; +insert t1 (x, row_start, row_end) +values (1, '2106-02-07 06:00:00', '2106-02-07 06:28:15.5'); +set @@system_versioning_insert_history= 0; +set timestamp= unix_timestamp('2106-02-07 06:28:15'); +alter table t1 partition by system_time interval 1 hour auto; +Warnings: +Warning 4277 `t1`: no usable ROW END index, using slow scan; add an ordered index starting with the ROW END column (ascending, not DESC) to avoid it +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2106-02-07 06:00:00' AUTO +PARTITIONS 2 +set timestamp= default; +drop table t1; +# PARTITIONS N is kept if it is more than the history needs +create or replace table t1 (x int) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +update t1 set x= 2; +set timestamp= unix_timestamp('2000-01-01 02:30:00'); +alter table t1 partition by system_time interval 1 hour auto partitions 10; +Warnings: +Warning 4277 `t1`: no usable ROW END index, using slow scan; add an ordered index starting with the ROW END column (ascending, not DESC) to avoid it +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2000-01-01 01:00:00' AUTO +PARTITIONS 10 +alter table t1 remove partitioning; +alter table t1 partition by system_time interval 1 hour auto partitions 2; +Warnings: +Warning 4277 `t1`: no usable ROW END index, using slow scan; add an ordered index starting with the ROW END column (ascending, not DESC) to avoid it +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2000-01-01 01:00:00' AUTO +PARTITIONS 3 +set timestamp= default; +drop table t1; +# LOCK=NONE with an explicit partition list: no history is read +create or replace table t1 (x int) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (1); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 2; +alter table t1 lock=none partition by system_time interval 1 hour auto ( +partition p0 history, +partition pn current); +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2000-01-01 01:00:00' AUTO +(PARTITION `p0` HISTORY ENGINE = MyISAM, + PARTITION `pn` CURRENT ENGINE = MyISAM) +set timestamp= default; +drop table t1; +# Current rows with the pre-11.5 row_end marker are not history +set @@system_versioning_insert_history= 1; +create or replace table t1 (x int, +row_start timestamp(6) as row start invisible, +row_end timestamp(6) as row end invisible, +period for system_time (row_start, row_end), +index (row_end)) with system versioning; +insert t1 (x, row_start, row_end) values +(1, '2000-01-01 00:00:00', '2000-01-01 01:00:00'), +(2, '2000-01-01 01:00:00', '2038-01-19 03:14:07.999999'), +(3, '2000-01-01 01:00:00', '2038-01-19 03:14:07.999999'); +set @@system_versioning_insert_history= 0; +set timestamp= unix_timestamp('2000-01-01 01:30:00'); +alter table t1 partition by system_time interval 1 hour auto; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2000-01-01 01:00:00' AUTO +PARTITIONS 2 +select x, row_end from t1 partition (p0); +x row_end +1 2000-01-01 01:00:00.000000 +select x from t1 partition (pn); +x +2 +3 +# The same without the index (scan) +alter table t1 remove partitioning; +alter table t1 drop index row_end; +alter table t1 partition by system_time interval 1 hour auto; +Warnings: +Warning 4277 `t1`: no usable ROW END index, using slow scan; add an ordered index starting with the ROW END column (ascending, not DESC) to avoid it +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2000-01-01 01:00:00' AUTO +PARTITIONS 2 +set timestamp= default; +drop table t1; +# History after the pre-11.5 marker sorts above it in the index +set @@system_versioning_insert_history= 1; +create or replace table t1 (x int, +row_start timestamp(6) as row start invisible, +row_end timestamp(6) as row end invisible, +period for system_time (row_start, row_end), +index (row_end)) with system versioning; +insert t1 (x, row_start, row_end) values +(1, '2040-01-01 00:00:00', '2040-01-01 01:00:00'), +(2, '2000-01-01 01:00:00', '2038-01-19 03:14:07.999999'); +set @@system_versioning_insert_history= 0; +set timestamp= unix_timestamp('2040-01-01 01:30:00'); +alter table t1 partition by system_time interval 1 hour auto; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL, + `row_start` timestamp(6) GENERATED ALWAYS AS ROW START INVISIBLE, + `row_end` timestamp(6) GENERATED ALWAYS AS ROW END INVISIBLE, + KEY `row_end` (`row_end`), + PERIOD FOR SYSTEM_TIME (`row_start`, `row_end`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 HOUR STARTS TIMESTAMP'2040-01-01 01:00:00' AUTO +PARTITIONS 2 +select x, row_end from t1 partition (p0); +x row_end +1 2040-01-01 01:00:00.000000 +select x from t1 partition (pn); +x +2 +set timestamp= default; +drop table t1; +# STARTS aligned to a local midnight before the TIMESTAMP range: the +# oldest history row_end is used as STARTS +set time_zone= '+03:00'; +create or replace table t1 (x int) with system versioning; +set timestamp= 3600; +insert t1 values (1); +set timestamp= 7200; +delete from t1; +set timestamp= 7300; +alter table t1 partition by system_time interval 1 day auto; +Warnings: +Warning 4277 `t1`: no usable ROW END index, using slow scan; add an ordered index starting with the ROW END column (ascending, not DESC) to avoid it +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) DEFAULT NULL +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci WITH SYSTEM VERSIONING + PARTITION BY SYSTEM_TIME INTERVAL 1 DAY STARTS TIMESTAMP'1970-01-01 05:00:00' AUTO +PARTITIONS 2 +set timestamp= default; +set time_zone= '+00:00'; +drop table t1; +# Subpartitions count against MAX_PARTITIONS +create or replace table t1 (x int) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (1); +set timestamp= unix_timestamp('2000-01-01 00:00:01'); +update t1 set x= 2; +set timestamp= unix_timestamp('2000-01-01 01:30:00'); +alter table t1 partition by system_time interval 1 second auto +subpartition by key (x) subpartitions 2; +ERROR HY000: Versioned table `test`.`t1`: adding HISTORY partition(s) failed +show warnings; +Level Code Message +Warning 4277 `t1`: no usable ROW END index, using slow scan; add an ordered index starting with the ROW END column (ascending, not DESC) to avoid it +Warning 1499 Too many partitions (including subpartitions) were defined +Error 4189 Versioned table `test`.`t1`: adding HISTORY partition(s) failed +set timestamp= default; +drop table t1; +# An explicit subpartition list is replaced by default subpartitions, +# as many as there were +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +create or replace table t1 (x int) with system versioning +partition by system_time interval 1 hour starts '2000-01-01 00:00:00' +subpartition by key (x) ( +partition p0 history (subpartition s0, subpartition s1), +partition p1 history (subpartition s2, subpartition s3), +partition pn current (subpartition s4, subpartition s5)); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +set timestamp= unix_timestamp('2000-01-01 02:30:00'); +alter table t1 partition by system_time interval 1 hour +starts '2000-01-01 00:00:00' auto force; +Warnings: +Warning 4277 `t1`: no usable ROW END index, using slow scan; add an ordered index starting with the ROW END column (ascending, not DESC) to avoid it +select partition_name, subpartition_name from information_schema.partitions +where table_schema= 'test' and table_name= 't1' +order by partition_ordinal_position, subpartition_ordinal_position; +partition_name subpartition_name +p0 p0sp0 +p0 p0sp1 +p1 p1sp0 +p1 p1sp1 +p2 p2sp0 +p2 p2sp1 +pn pnsp0 +pn pnsp1 +select x, row_end from t1 partition (p1); +x row_end +0 2000-01-01 01:00:00.000000 +select x from t1 partition (pn); +x +1 +set timestamp= default; +drop table t1; +# INTERVAL overflow while stepping the boundaries +create or replace table t1 (x int) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (1); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +delete from t1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +alter table t1 partition by system_time interval 9999 year auto; +ERROR HY000: Versioned table `test`.`t1`: adding HISTORY partition(s) failed +show warnings; +Level Code Message +Warning 4277 `t1`: no usable ROW END index, using slow scan; add an ordered index starting with the ROW END column (ascending, not DESC) to avoid it +Warning 1690 TIMESTAMP value is out of range in 'INTERVAL' +Error 4189 Versioned table `test`.`t1`: adding HISTORY partition(s) failed +set timestamp= default; +drop table t1; +# INTERVAL longer than the date range: its seconds would overflow +create or replace table t1 (x int) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (1); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +delete from t1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +alter table t1 partition by system_time interval 4611686018427387904 minute auto; +ERROR HY000: Versioned table `test`.`t1`: adding HISTORY partition(s) failed +show warnings; +Level Code Message +Warning 4277 `t1`: no usable ROW END index, using slow scan; add an ordered index starting with the ROW END column (ascending, not DESC) to avoid it +Warning 1690 TIMESTAMP value is out of range in 'INTERVAL' +Error 4189 Versioned table `test`.`t1`: adding HISTORY partition(s) failed +alter table t1 partition by system_time interval 4611686018427387903 minute auto; +ERROR HY000: Versioned table `test`.`t1`: adding HISTORY partition(s) failed +show warnings; +Level Code Message +Warning 4277 `t1`: no usable ROW END index, using slow scan; add an ordered index starting with the ROW END column (ascending, not DESC) to avoid it +Warning 1690 TIMESTAMP value is out of range in 'INTERVAL' +Error 4189 Versioned table `test`.`t1`: adding HISTORY partition(s) failed +set timestamp= default; +drop table t1; +# End of 13.2 tests diff --git a/mysql-test/suite/versioning/t/old_timestamp.test b/mysql-test/suite/versioning/t/old_timestamp.test index 41e41fc2a9050..5cd3e14e081bc 100644 --- a/mysql-test/suite/versioning/t/old_timestamp.test +++ b/mysql-test/suite/versioning/t/old_timestamp.test @@ -57,3 +57,24 @@ select a,row_foo_end from sv_explicit for system_time all; drop table sv_basic,sv_partition,sv_explicit; +--echo # +--echo # MDEV-25529 Auto-create: Pre-existing historical data is not +--echo # partitioned as specified by ALTER +--echo # +--echo # Current rows of a table that is not upgraded keep the old row_end +--echo # marker; they are not history. +--copy_file std_data/sv_basic.MYD $MARIADB_DATADIR/test/t1.MYD +--copy_file std_data/sv_basic.MYI $MARIADB_DATADIR/test/t1.MYI +--copy_file std_data/sv_basic.frm $MARIADB_DATADIR/test/t1.frm +select a, row_end from t1 for system_time all order by a; +let $hist_end= `select max(row_end) from t1 for system_time all where row_end < '2038-01-01'`; +--disable_query_log +eval set timestamp= unix_timestamp('$hist_end') + 60; +--enable_query_log +alter table t1 partition by system_time interval 1 hour auto; +show create table t1; +select a from t1 partition (pn) order by a; +set timestamp= default; +drop table t1; + +--echo # End of 13.2 tests diff --git a/mysql-test/suite/versioning/t/partition2.test b/mysql-test/suite/versioning/t/partition2.test new file mode 100644 index 0000000000000..38d2b4baa16a7 --- /dev/null +++ b/mysql-test/suite/versioning/t/partition2.test @@ -0,0 +1,832 @@ +--source include/have_partition.inc +--source include/have_innodb.inc +--source include/have_aria.inc +--source include/have_64bit_timestamp.inc +--source include/maybe_debug.inc +let $have_debug_sync= `select count(*) from information_schema.session_variables where variable_name = 'debug_sync' and variable_value like 'on %'`; + +--enable_prepare_warnings +set @@session.time_zone='+00:00'; + +--echo # +--echo # MDEV-25529 Auto-create: Pre-existing historical data is not partitioned as specified by ALTER +--echo # + +# Test both index and scan, compare the results between them +if ($have_debug) +{ + --disable_query_log + set @old_dbug= @@debug_dbug; + set debug_dbug= '+d,test_mdev-25529'; + --enable_query_log +} + +create or replace table t1 (x int) with system versioning +partition by system_time limit 1; +alter table t1 partition by system_time limit 1 auto; + +create or replace table t1 (x int, + row_start timestamp(6) as row start invisible, + row_end timestamp(6) as row end invisible, + period for system_time (row_start, row_end), + index (row_end, row_start), + index (row_start), + index (row_end, x), + index (x, row_end), + index (row_end desc)) with system versioning +partition by system_time limit 1000 partitions 6; + +show create table t1; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 00:10:00.22'); +update t1 set x= x + 1; +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= x + 1; +set timestamp= unix_timestamp('2000-01-01 01:30:00'); +update t1 set x= x + 1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +update t1 set x= x + 1; + +select *, row_start, row_end from t1 partition (p0); +select *, row_start, row_end from t1 partition (p1); +select *, row_start, row_end from t1 partition (p2); +select *, row_start, row_end from t1 partition (p3); +select *, row_start, row_end from t1 partition (p4); +select *, row_start, row_end from t1 partition (pn); +set timestamp= default; + +ALTER table t1 partition by system_time +interval 1 hour starts '2000-01-01 00:00:00'; +select *, row_start, row_end from t1 partition (p0); +select *, row_start, row_end from t1 partition (p1); +select *, row_start, row_end from t1 partition (p2); +select *, row_start, row_end from t1 partition (p3); +select *, row_start, row_end from t1 partition (p4); +select *, row_start, row_end from t1 partition (pn); + +alter TABLE t1 partition by system_time +interval 1 hour; +select *, row_start, row_end from t1 partition (p0); +select *, row_start, row_end from t1 partition (p1); +select *, row_start, row_end from t1 partition (p2); +select *, row_start, row_end from t1 partition (p3); +select *, row_start, row_end from t1 partition (p4); +select *, row_start, row_end from t1 partition (pn); + +set @@system_versioning_alter_history= keep; +alter table t1 remove partitioning; +# First history: 2000-01-01 00:10:00.220000 +# Last history: 2000-01-01 02:00:00.000000 +select *, row_start, row_end from t1 for system_time all; + +set timestamp= unix_timestamp('2000-01-01 02:30:00'); +alter table t1 PARTITION by system_time interval 1 hour auto; + +show create table t1; +select *, row_start, row_end from t1 partition (p0); +select *, row_start, row_end from t1 partition (p1); +select *, row_start, row_end from t1 partition (pn); + +alter table t1 remove partitioning; +alter table t1 partition by SYSTEM_TIME interval 1 hour starts '2000-01-01 00:00:00' auto; + +show create table t1; +select *, row_start, row_end from t1 partition (p0); +select *, row_start, row_end from t1 partition (p1); +select *, row_start, row_end from t1 partition (pn); + +set timestamp= unix_timestamp('2000-01-02 03:01:23.456'); +delete from t1; +alter table t1 remove partitioning; +alter table t1 partition by SYSTEM_TIME interval 1 hour auto; + +show create table t1; +select *, row_start, row_end from t1 partition (p0); +select *, row_start, row_end from t1 partition (p1); +select *, row_start, row_end from t1 partition (p2); +select *, row_start, row_end from t1 partition (p27); +select *, row_start, row_end from t1 partition (pn); + +set timestamp= unix_timestamp('2000-01-02 03:01:23.456'); +delete from t1; +alter table t1 partition by system_time INTERVAL 20 minute auto force; +show create table t1; +select *, row_start, row_end from t1 partition (p0); +select *, row_start, row_end from t1 partition (p2); +select *, row_start, row_end from t1 partition (p5); +select *, row_start, row_end from t1 partition (pn); + +--echo # STARTS after the oldest history is re-aligned: WARN_VERS_WRONG_STARTS +alter table t1 remove partitioning; +alter table t1 partition by system_time interval 1 hour +starts '2002-01-01 00:00:00' auto; + +alter table t1 force partition by system_time interval 1 hour +starts '2003-01-01 00:00:00' auto; +show create table t1; + +alter table t1 partition by system_time interval 1 hour +starts '2004-01-01 00:00:00' auto force; +show create table t1; + +--echo # STARTS is not adjusted to the history range without FORCE +alter table t1 partition by system_time interval 1 hour +starts '2005-01-01 00:00:00' auto; +show create table t1; + +--echo # min_ts == max_ts case, partitions decrease +delete history from t1; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (77); +delete from t1; +alter table t1 partition by system_time interval 7 hour auto force; +show create table t1; + +--echo # AUTO FORCE re-lays out; with an explicit partition list it does not +set timestamp= unix_timestamp('2000-01-03 00:00:00'); +insert t1 values (88); +delete from t1; +alter table t1 partition by system_time interval 8 hour auto force; +show create table t1; + +alter table t1 partition by system_time interval 8 hour auto ( + partition p0 history, + partition p1 history, + partition pn current) force; +show create table t1; + +--echo # Switch system_time -> hash +alter table t1 partition by hash(x) partitions 3; +show create table t1; +--echo # Switch hash -> system_time +alter table t1 partition by system_time interval 9 hour +starts '2001-01-01 00:00:00' auto force; +show create table t1; + +--echo # Year/month interval +delete history from t1; +alter table t1 remove partitioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (77); +delete from t1; +alter table t1 partition by system_time interval 1 month auto force; +show create table t1; +set timestamp= unix_timestamp('2003-10-01 00:00:00'); +insert t1 values (88); +delete from t1; +alter table t1 partition by system_time interval 11 month auto force; +show create table t1; +alter table t1 partition by system_time interval 1 year auto force; +show create table t1; + +--echo # No history +delete history from t1; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +alter table t1 partition by system_time interval 1 hour auto force; +show create table t1; +insert t1 values (99); +alter table t1 partition by system_time interval 1 hour auto force; +show create table t1; + +--echo # Slow scan warning +create or replace table t1 (x int, + row_start timestamp(6) as row start invisible, + row_end timestamp(6) as row end invisible, + period for system_time (row_start, row_end), + index (x, row_end, row_start), + index (row_start, row_end), + index (x, row_end)) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (77); +delete from t1; +set timestamp= unix_timestamp('2000-01-03 00:00:00'); +insert t1 values (88); +delete from t1; +alter table t1 partition by system_time interval 10 hour auto; +show create table t1; + +--echo # TRX_ID versioning +create or replace table t1 (x int, + row_start bigint(20) unsigned as row start invisible, + row_end bigint(20) unsigned as row end invisible, + period for system_time (row_start, row_end), + index (row_end)) with system versioning engine innodb; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (77); +delete from t1; +set timestamp= unix_timestamp('2000-01-03 00:00:00'); +insert t1 values (88); +delete from t1; +--echo # Partitioning is not allowed for TRX_ID versioning +--error ER_VERS_FIELD_WRONG_TYPE +alter table t1 partition by system_time interval 11 hour auto; + +drop table t1; + +--echo # Max history row_end exactly on an interval boundary. A boundary value +--echo # start + k*i belongs to partition k, so k+1 history partitions are +--echo # needed. Here start=00:00, interval 1 hour, max history row_end=02:00:00 +--echo # belongs to p2 = [02:00, 03:00). +create or replace table t1 (x int, + row_start timestamp(6) as row start invisible, + row_end timestamp(6) as row end invisible, + period for system_time (row_start, row_end), + index (row_end, row_start)) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +update t1 set x= 2; +alter table t1 partition by system_time +interval 1 hour starts '2000-01-01 00:00:00' auto; +show create table t1; +select x, row_end from t1 partition (p1); +select x, row_end from t1 partition (p2); +select x, row_end from t1 partition (pn); +set timestamp= default; +drop table t1; + +--echo # LIMIT ... AUTO on a table that already has history does not size the +--echo # layout from the history: there is no INTERVAL to size it by. +create or replace table t1 (x int) with system versioning; +insert t1 values (0); +update t1 set x= 1; +alter table t1 partition by system_time limit 1 auto; +show create table t1; +drop table t1; + +--echo # A disabled row_end index must not be used for the history range. +--echo # With the key disabled the code must fall back to a full scan and still +--echo # lay out history correctly, not fail on the disabled index. +create or replace table t1 (x int, + row_start timestamp(6) as row start invisible, + row_end timestamp(6) as row end invisible, + period for system_time (row_start, row_end), + index k (row_end, row_start)) with system versioning engine=myisam; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +update t1 set x= 2; +set timestamp= unix_timestamp('2000-01-01 02:30:00'); +alter table t1 disable keys; +alter table t1 partition by system_time +interval 1 hour starts '2000-01-01 00:00:00' auto; +show create table t1; +select x, row_end from t1 partition (p2); + +--echo # The history-range lookup takes a table lock. Under LOCK TABLES the +--echo # handler is already locked, so it must not lock again. +alter table t1 remove partitioning; +alter table t1 enable keys; +lock tables t1 write; +alter table t1 partition by system_time +interval 1 hour starts '2000-01-01 00:00:00' auto; +unlock tables; +show create table t1; +select x, row_end from t1 partition (p2); +set timestamp= default; +drop table t1; + +--echo # History range divided by INTERVAL exceeds MAX_PARTITIONS: the number +--echo # of history partitions needed to cover the range is reported as +--echo # ER_VERS_HIST_PART_FAILED (preceded by ER_TOO_MANY_PARTITIONS_ERROR). +create or replace table t1 (x int) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (1); +set timestamp= unix_timestamp('2000-01-01 00:00:01'); +update t1 set x= 2; +set timestamp= unix_timestamp('2000-01-01 06:00:00'); +update t1 set x= 3; +--error ER_VERS_HIST_PART_FAILED +alter table t1 partition by system_time interval 1 second auto; +show warnings; +set timestamp= default; +drop table t1; + +--echo # Month/year history partition boundaries must be stepped in UTC, not in +--echo # the session time zone. Under a DST zone (MET: CET=+1 winter, +--echo # CEST=+2 summer, spring-forward 2024-03-31) the two disagree. +--echo # +--echo # STARTS aligns to local 2024-02-15 00:00 MET = 2024-02-14 23:00 UTC. +--echo # Max history row_end is 2024-04-14 22:30:00 UTC, which falls in the +--echo # one-hour gap between the UTC boundary (Apr 14 23:00) and the +--echo # session-zone boundary (Apr 14 22:00, shifted by the +2 summer offset). +--echo # UTC stepping covers it with two history partitions (PARTITIONS 3); +--echo # stepping in MET would add one more (PARTITIONS 4). The query time +--echo # (22:45 UTC) is inside the same gap. +set time_zone= 'MET'; +create or replace table t1 (x int) with system versioning; +set timestamp= unix_timestamp('2024-02-15 12:00:00'); +insert t1 values (1); +delete from t1; +set timestamp= unix_timestamp('2024-04-15 00:30:00'); +insert t1 values (2); +delete from t1; +set timestamp= unix_timestamp('2024-04-15 00:45:00'); +alter table t1 partition by system_time interval 1 month auto; +show create table t1; +set timestamp= default; +set time_zone= '+00:00'; +drop table t1; + +--echo # STARTS aligned to a local midnight that does not exist: Moscow skipped +--echo # 1981-04-01 00:00-01:00 (DST start), so the start of the gap is used +--echo # instead of failing the ALTER. +set time_zone= 'Europe/Moscow'; +create or replace table t1 (x int) with system versioning; +set timestamp= unix_timestamp('1981-04-01 12:00:00'); +insert t1 values (1); +set timestamp= unix_timestamp('1981-04-01 13:00:00'); +delete from t1; +set timestamp= unix_timestamp('1981-04-01 14:00:00'); +alter table t1 partition by system_time interval 1 day auto; +show create table t1; +set timestamp= default; +set time_zone= '+00:00'; +drop table t1; + +--echo # STARTS later than the query time warns with AUTO too, unless STARTS +--echo # is re-aligned to the history range +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +create or replace table t1 (x int) with system versioning +partition by system_time interval 1 hour starts '2000-01-01 01:00:00'; +--echo # CREATE never re-aligns STARTS +create or replace table t1 (x int) with system versioning +partition by system_time interval 1 hour starts '2000-01-01 01:00:00' auto; +--echo # No history: STARTS is not re-aligned +create or replace table t1 (x int) with system versioning; +alter table t1 partition by system_time interval 1 hour + starts '2000-01-01 01:00:00' auto; +alter table t1 force partition by system_time interval 1 hour + starts '2000-01-01 01:00:00' auto; +--echo # Without AUTO: warns at parse time +alter table t1 partition by system_time interval 1 hour + starts '2000-01-01 01:00:00'; +--echo # Explicit partition list: STARTS is not re-aligned despite history +set timestamp= unix_timestamp('1999-12-31 23:00:00'); +insert t1 values (1); +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +delete from t1; +alter table t1 partition by system_time interval 1 hour + starts '2000-01-01 01:00:00' auto ( + partition p0 history, + partition pn current) force; +drop table t1; +set timestamp= default; + +--echo # The history range is read under a table lock (Aria sets up its +--echo # transaction in THR_LOCK callbacks) +create table t1 (x int, + row_start timestamp(6) as row start invisible, + row_end timestamp(6) as row end invisible, + period for system_time (row_start, row_end), + index (row_end, row_start)) with system versioning engine=aria; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +update t1 set x= 2; +set timestamp= unix_timestamp('2000-01-01 03:00:00'); +alter table t1 partition by system_time interval 1 hour auto; +select x, row_end from t1 partition (p0); +select x, row_end from t1 partition (p1); +select x from t1 partition (pn); +drop table t1; +set timestamp= default; + +--echo # The layout covers the query time as well, so DML after the ALTER +--echo # does not need to create partitions. STARTS aligns to 01:00, the query +--echo # time 05:30 needs history partitions p0..p4. +create or replace table t1 (x int) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +update t1 set x= 2; +set timestamp= unix_timestamp('2000-01-01 05:30:00'); +alter table t1 partition by system_time interval 1 hour auto; +show create table t1; +update t1 set x= 3; +select x, row_end from t1 partition (p4); +--echo # Covering the query time needs too many partitions +alter table t1 remove partitioning; +set timestamp= default; +--error ER_VERS_HIST_PART_FAILED +alter table t1 partition by system_time interval 1 hour auto; +--echo # DML keeps working +update t1 set x= 4; +drop table t1; + +--echo # DML waits from the history-range read until the ALTER is done, also +--echo # during the copy (the copy does not run online). The ALTER at 03:00 +--echo # lays out p0..p2, the UPDATE at 03:30 then puts its history row into +--echo # p2 = [03:00, 04:00). Without the early metadata lock upgrade the +--echo # UPDATE runs while the ALTER is copying. +create or replace table t1 (x int, + row_start timestamp(6) as row start invisible, + row_end timestamp(6) as row end invisible, + period for system_time (row_start, row_end), + index (row_end, row_start)) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +update t1 set x= 2; +set timestamp= default; +--connect con1,localhost,root +--connect con2,localhost,root +--disable_query_log +--connection con1 +set time_zone= '+00:00'; +set timestamp= unix_timestamp('2000-01-01 03:00:00'); +--connection con2 +set time_zone= '+00:00'; +set timestamp= unix_timestamp('2000-01-01 03:30:00'); +if ($have_debug_sync) +{ + --connection con1 + set debug_sync= 'copy_data_between_tables_before SIGNAL ready WAIT_FOR go'; + send alter table t1 partition by system_time interval 1 hour auto; + --connection default + set debug_sync= 'now WAIT_FOR ready'; + --connection con2 + send update t1 set x= 3; + --connection default + let $wait_condition= select count(*) = 1 from information_schema.processlist + where state = 'Waiting for table metadata lock' + and info = 'update t1 set x= 3'; + --source include/wait_condition.inc + --disable_query_log + set debug_sync= 'now SIGNAL go'; + --connection con1 + reap; + --connection con2 + reap; + --connection default + set debug_sync= 'reset'; +} +if (!$have_debug_sync) +{ + --connection con1 + alter table t1 partition by system_time interval 1 hour auto; + --connection con2 + update t1 set x= 3; +} +--enable_query_log +--disconnect con1 +--disconnect con2 +--connection default +show create table t1; +select x, row_end from t1 partition (p2); + +--echo # The ALTER fails if concurrent DML cannot be blocked. It fails at the MDL +--echo # upgrade, before the history range is read: otherwise covering the query +--echo # time would fail with ER_VERS_HIST_PART_FAILED. +alter table t1 remove partitioning; +--connect con1,localhost,root +begin; +insert t1 values (5); +--connection default +set lock_wait_timeout= 1; +--error ER_LOCK_WAIT_TIMEOUT +alter table t1 partition by system_time interval 1 hour auto; +set lock_wait_timeout= default; +--connection con1 +commit; +--disconnect con1 +--connection default + +--echo # Table lock failure in the history-range read (debug builds only) +--disable_query_log +--disable_result_log +if ($have_debug) +{ + set @save_dbug= @@debug_dbug; + set debug_dbug= '+d,vers_history_range_lock_fail'; + --error ER_LOCK_WAIT_TIMEOUT + alter table t1 partition by system_time interval 1 hour auto; + set debug_dbug= @save_dbug; +} +--enable_result_log +--enable_query_log +show create table t1; +drop table t1; + +--echo # KILL QUERY during the history-range scan (debug_sync builds only). +--echo # With the index, test_mdev-25529 reads it before the scan. +create or replace table t1 (x int, + row_start timestamp(6) as row start invisible, + row_end timestamp(6) as row end invisible, + period for system_time (row_start, row_end), + index (row_end)) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +set timestamp= default; +--disable_query_log +--disable_result_log +if ($have_debug_sync) +{ + --connect con1,localhost,root + set time_zone= '+00:00'; + set timestamp= unix_timestamp('2000-01-01 03:00:00'); + set debug_dbug= '+d,test_mdev-25529'; + let $con1_id= `select connection_id()`; + set debug_sync= 'vers_get_history_range_scan SIGNAL scanning WAIT_FOR go'; + send alter table t1 partition by system_time interval 1 hour auto; + --connection default + set debug_sync= 'now WAIT_FOR scanning'; + eval kill query $con1_id; + set debug_sync= 'now SIGNAL go'; + --connection con1 + --error ER_QUERY_INTERRUPTED + reap; + --disconnect con1 + --connection default + set debug_sync= 'reset'; +} +--enable_result_log +--enable_query_log +show create table t1; +drop table t1; + +--echo # The copy does not run online, so LOCK=NONE is rejected, and it is a +--echo # copy, so ALGORITHM=INPLACE is rejected too. Both are rejected before the +--echo # MDL upgrade, so they do not wait for concurrent DML. +create or replace table t1 (x int) with system versioning; +--connect con1,localhost,root +begin; +insert t1 values (1); +--connection default +set lock_wait_timeout= 1; +--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON +alter table t1 lock=none partition by system_time interval 1 hour auto; +--error ER_ALTER_OPERATION_NOT_SUPPORTED +alter table t1 algorithm=inplace partition by system_time interval 1 hour auto; +set lock_wait_timeout= default; +--connection con1 +commit; +--disconnect con1 +--connection default +drop table t1; + +--echo # A failed ALTER ... AUTO FORCE on a table already partitioned by +--echo # INTERVAL leaves its layout intact +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +create or replace table t1 (x int) with system versioning +partition by system_time interval 1 hour auto; +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +show create table t1; +set timestamp= default; +--error ER_VERS_HIST_PART_FAILED +alter table t1 partition by system_time interval 1 minute auto force; +show create table t1; +set timestamp= unix_timestamp('2000-01-01 01:30:00'); +update t1 set x= 2; +select x, row_end from t1 partition (p1); +set timestamp= default; +drop table t1; + +--echo # Month interval near TIMESTAMP_MAX_VALUE: the boundary after the query +--echo # time is past it, so the last history partition is partial +create or replace table t1 (x int) with system versioning; +set timestamp= unix_timestamp('2106-01-20 00:00:00'); +insert t1 values (1); +set timestamp= unix_timestamp('2106-01-25 00:00:00'); +delete from t1; +set timestamp= unix_timestamp('2106-01-26 00:00:00'); +alter table t1 partition by system_time interval 1 month auto; +show create table t1; +set timestamp= default; +drop table t1; + +--echo # InnoDB: history range from the row_end index +create or replace table t1 (x int, + row_start timestamp(6) as row start invisible, + row_end timestamp(6) as row end invisible, + period for system_time (row_start, row_end), + index (row_end)) with system versioning engine=innodb; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +update t1 set x= 2; +set timestamp= unix_timestamp('2000-01-01 03:00:00'); +alter table t1 partition by system_time interval 1 hour auto; +show create table t1; +select x, row_end from t1 partition (p0); +select x, row_end from t1 partition (p1); +select x from t1 partition (pn); +set timestamp= default; +drop table t1; + +--echo # MEMORY: history range from a scan +create or replace table t1 (x int) with system versioning engine=memory; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +update t1 set x= 2; +set timestamp= unix_timestamp('2000-01-01 03:00:00'); +alter table t1 partition by system_time interval 1 hour auto; +show create table t1; +select x from t1 partition (p0); +select x from t1 partition (p1); +select x from t1 partition (pn); +set timestamp= default; +drop table t1; + +--echo # History row_end in the last second of the TIMESTAMP range +set @@system_versioning_insert_history= 1; +create or replace table t1 (x int, + row_start timestamp(6) as row start invisible, + row_end timestamp(6) as row end invisible, + period for system_time (row_start, row_end)) with system versioning; +insert t1 (x, row_start, row_end) + values (1, '2106-02-07 06:00:00', '2106-02-07 06:28:15.5'); +set @@system_versioning_insert_history= 0; +set timestamp= unix_timestamp('2106-02-07 06:28:15'); +alter table t1 partition by system_time interval 1 hour auto; +show create table t1; +set timestamp= default; +drop table t1; + +--echo # PARTITIONS N is kept if it is more than the history needs +create or replace table t1 (x int) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +update t1 set x= 2; +set timestamp= unix_timestamp('2000-01-01 02:30:00'); +alter table t1 partition by system_time interval 1 hour auto partitions 10; +show create table t1; +alter table t1 remove partitioning; +alter table t1 partition by system_time interval 1 hour auto partitions 2; +show create table t1; +set timestamp= default; +drop table t1; + +--echo # LOCK=NONE with an explicit partition list: no history is read +create or replace table t1 (x int) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (1); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 2; +alter table t1 lock=none partition by system_time interval 1 hour auto ( + partition p0 history, + partition pn current); +show create table t1; +set timestamp= default; +drop table t1; + +--echo # Current rows with the pre-11.5 row_end marker are not history +set @@system_versioning_insert_history= 1; +create or replace table t1 (x int, + row_start timestamp(6) as row start invisible, + row_end timestamp(6) as row end invisible, + period for system_time (row_start, row_end), + index (row_end)) with system versioning; +insert t1 (x, row_start, row_end) values + (1, '2000-01-01 00:00:00', '2000-01-01 01:00:00'), + (2, '2000-01-01 01:00:00', '2038-01-19 03:14:07.999999'), + (3, '2000-01-01 01:00:00', '2038-01-19 03:14:07.999999'); +set @@system_versioning_insert_history= 0; +set timestamp= unix_timestamp('2000-01-01 01:30:00'); +alter table t1 partition by system_time interval 1 hour auto; +show create table t1; +select x, row_end from t1 partition (p0); +select x from t1 partition (pn); +--echo # The same without the index (scan) +alter table t1 remove partitioning; +alter table t1 drop index row_end; +alter table t1 partition by system_time interval 1 hour auto; +show create table t1; +set timestamp= default; +drop table t1; + +--echo # History after the pre-11.5 marker sorts above it in the index +set @@system_versioning_insert_history= 1; +create or replace table t1 (x int, + row_start timestamp(6) as row start invisible, + row_end timestamp(6) as row end invisible, + period for system_time (row_start, row_end), + index (row_end)) with system versioning; +insert t1 (x, row_start, row_end) values + (1, '2040-01-01 00:00:00', '2040-01-01 01:00:00'), + (2, '2000-01-01 01:00:00', '2038-01-19 03:14:07.999999'); +set @@system_versioning_insert_history= 0; +set timestamp= unix_timestamp('2040-01-01 01:30:00'); +alter table t1 partition by system_time interval 1 hour auto; +show create table t1; +select x, row_end from t1 partition (p0); +select x from t1 partition (pn); +set timestamp= default; +drop table t1; + +--echo # STARTS aligned to a local midnight before the TIMESTAMP range: the +--echo # oldest history row_end is used as STARTS +set time_zone= '+03:00'; +create or replace table t1 (x int) with system versioning; +# 1970-01-01 01:00:00 UTC +set timestamp= 3600; +insert t1 values (1); +# 1970-01-01 02:00:00 UTC +set timestamp= 7200; +delete from t1; +set timestamp= 7300; +alter table t1 partition by system_time interval 1 day auto; +show create table t1; +set timestamp= default; +set time_zone= '+00:00'; +drop table t1; + +--echo # Subpartitions count against MAX_PARTITIONS +create or replace table t1 (x int) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (1); +set timestamp= unix_timestamp('2000-01-01 00:00:01'); +update t1 set x= 2; +set timestamp= unix_timestamp('2000-01-01 01:30:00'); +--error ER_VERS_HIST_PART_FAILED +alter table t1 partition by system_time interval 1 second auto +subpartition by key (x) subpartitions 2; +show warnings; +set timestamp= default; +drop table t1; + +--echo # An explicit subpartition list is replaced by default subpartitions, +--echo # as many as there were +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +create or replace table t1 (x int) with system versioning +partition by system_time interval 1 hour starts '2000-01-01 00:00:00' +subpartition by key (x) ( + partition p0 history (subpartition s0, subpartition s1), + partition p1 history (subpartition s2, subpartition s3), + partition pn current (subpartition s4, subpartition s5)); +insert t1 values (0); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +update t1 set x= 1; +set timestamp= unix_timestamp('2000-01-01 02:30:00'); +alter table t1 partition by system_time interval 1 hour +starts '2000-01-01 00:00:00' auto force; +select partition_name, subpartition_name from information_schema.partitions +where table_schema= 'test' and table_name= 't1' +order by partition_ordinal_position, subpartition_ordinal_position; +select x, row_end from t1 partition (p1); +select x from t1 partition (pn); +set timestamp= default; +drop table t1; + +--echo # INTERVAL overflow while stepping the boundaries +create or replace table t1 (x int) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (1); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +delete from t1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +--error ER_VERS_HIST_PART_FAILED +alter table t1 partition by system_time interval 9999 year auto; +show warnings; +set timestamp= default; +drop table t1; + +--echo # INTERVAL longer than the date range: its seconds would overflow +create or replace table t1 (x int) with system versioning; +set timestamp= unix_timestamp('2000-01-01 00:00:00'); +insert t1 values (1); +set timestamp= unix_timestamp('2000-01-01 01:00:00'); +delete from t1; +set timestamp= unix_timestamp('2000-01-01 02:00:00'); +--error ER_VERS_HIST_PART_FAILED +alter table t1 partition by system_time interval 4611686018427387904 minute auto; +show warnings; +--error ER_VERS_HIST_PART_FAILED +alter table t1 partition by system_time interval 4611686018427387903 minute auto; +show warnings; +set timestamp= default; +drop table t1; + +if ($have_debug) +{ + --disable_query_log + set debug_dbug= @old_dbug; + --enable_query_log +} + +--disable_prepare_warnings + +--echo # End of 13.2 tests diff --git a/sql/partition_info.cc b/sql/partition_info.cc index 1e66b1863e280..52cc015967a19 100644 --- a/sql/partition_info.cc +++ b/sql/partition_info.cc @@ -448,7 +448,13 @@ bool partition_info::set_up_default_partitions(THD *thd, handler *file, } } else + { + /* + new and push_back() allocate on the active arena (thd->mem_root), + which has already reported the failure. + */ goto end; + } } while (++i < num_parts); result= FALSE; end: @@ -2869,20 +2875,12 @@ bool partition_info::vers_set_interval(THD* thd, Item* interval, default: goto interval_starts_error; } - if (!table) - { - if (thd->query_start() < vers_info->interval.start) - { - ErrConvTimestamp str_interval(thd, - Timestamp(vers_info->interval.start, 0), - 0); - ErrConvTimestamp str_query(thd, Timestamp(thd->query_start(), 0), 0); - push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, - WARN_VERS_STARTS_BEYOND_INTERVAL, - ER_THD(thd, WARN_VERS_STARTS_BEYOND_INTERVAL), - table_name, str_interval.ptr(), str_query.ptr()); - } - } + /* + ALTER .. AUTO may re-align STARTS to the history range in + prep_alter_part_table(), which does the check after that. + */ + if (!table && !(auto_hist && thd->lex->sql_command == SQLCOM_ALTER_TABLE)) + vers_warn_starts_beyond_query(thd, table_name); } else // calculate default STARTS depending on INTERVAL { @@ -2898,6 +2896,29 @@ bool partition_info::vers_set_interval(THD* thd, Item* interval, } +/** + Warn if STARTS is later than the query time. + + @param thd Current thread. + @param table_name Table name, for the warning message. +*/ +void partition_info::vers_warn_starts_beyond_query(THD *thd, + const char *table_name) + const +{ + if (thd->query_start() < vers_info->interval.start) + { + ErrConvTimestamp str_interval(thd, Timestamp(vers_info->interval.start, 0), + 0); + ErrConvTimestamp str_query(thd, Timestamp(thd->query_start(), 0), 0); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + WARN_VERS_STARTS_BEYOND_INTERVAL, + ER_THD(thd, WARN_VERS_STARTS_BEYOND_INTERVAL), + table_name, str_interval.ptr(), str_query.ptr()); + } +} + + /** Set STARTS to @p ts with the time-of-day parts below the finest time unit of the INTERVAL cleared. diff --git a/sql/partition_info.h b/sql/partition_info.h index b0bfd9e0ee249..b961594b16e73 100644 --- a/sql/partition_info.h +++ b/sql/partition_info.h @@ -44,23 +44,25 @@ struct Vers_part_info : public Sql_alloc interval.type= INTERVAL_LAST; } Vers_part_info(const Vers_part_info &src) : - interval(src.interval), - limit(src.limit), - auto_hist(src.auto_hist), - starts_clause(src.starts_clause), now_part(NULL), hist_part(NULL) { + copy_settings(src); } Vers_part_info& operator= (const Vers_part_info &src) + { + copy_settings(src); + now_part= src.now_part; + hist_part= src.hist_part; + return *this; + } + /* Copy the settings, keep now_part and hist_part */ + void copy_settings(const Vers_part_info &src) { interval= src.interval; limit= src.limit; auto_hist= src.auto_hist; starts_clause= src.starts_clause; - now_part= src.now_part; - hist_part= src.hist_part; - return *this; } bool initialized() { @@ -443,6 +445,8 @@ class partition_info : public DDL_LOG_STATE, public Sql_alloc interval_type int_type, Item *starts, bool auto_part, const char *table_name); bool vers_set_starts(THD *thd, my_time_t ts, const char *table_name); + void vers_warn_starts_beyond_query(THD *thd, + const char *table_name) const; bool vers_set_limit(ulonglong limit, bool auto_part, const char *table_name); bool vers_set_hist_part(THD* thd, uint *create_count); bool range_interval_set_count(THD* thd, uint *create_count); diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 04c504105ff06..f9263480125fa 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -12427,3 +12427,11 @@ ER_INVALID_HOST_NETMASK eng "Invalid masked host '%s' for user '%s'" ER_ACL_ENTRY_IGNORED_BAD_HOST_MASK eng "'%s' entry '%s' ignored, the host is not a valid ip/netmask" +WARN_VERS_WRONG_STARTS + chi "%sQ: STARTS 时间戳 %s 晚于最早历史日期 %s,已被设置为 %s" + eng "%sQ: STARTS timestamp %s is after earliest history timestamp %s and was set to %s" + spa "%sQ: la marca de tiempo STARTS %s es posterior a la fecha histórica más temprana %s y se estableció en %s" +WARN_VERS_SLOW_ROW_END + chi "%sQ: 没有可用的 ROW END 索引,正在使用慢速扫描;请添加以 ROW END 列开头的有序索引(升序,非 DESC)以避免" + eng "%sQ: no usable ROW END index, using slow scan; add an ordered index starting with the ROW END column (ascending, not DESC) to avoid it" + spa "%sQ: no hay un índice ROW END utilizable, usando escaneo lento; agregue un índice ordenado que comience por la columna ROW END (ascendente, no DESC) para evitarlo" diff --git a/sql/sql_alter.cc b/sql/sql_alter.cc index a4b9bb06cc70a..f62aedf3f64bd 100644 --- a/sql/sql_alter.cc +++ b/sql/sql_alter.cc @@ -40,7 +40,8 @@ Alter_info::Alter_info(const Alter_info &rhs, MEM_ROOT *mem_root) partition_names(rhs.partition_names, mem_root), num_parts(rhs.num_parts), requested_algorithm(rhs.requested_algorithm), - requested_lock(rhs.requested_lock) + requested_lock(rhs.requested_lock), + vers_history_read(rhs.vers_history_read) { /* Make deep copies of used objects. @@ -83,6 +84,20 @@ void Alter_info::set_requested_algorithm(enum_alter_table_algorithm algo_val) requested_algorithm= algo_val; } +void Alter_info::report_lock_none_unsupported(const char *reason) +{ + my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0), + "LOCK=NONE", reason, "LOCK=SHARED"); +} + + +void Alter_info::report_algorithm_unsupported(THD *thd) const +{ + my_error(ER_ALTER_OPERATION_NOT_SUPPORTED, MYF(0), + algorithm_clause(thd), "ALGORITHM=COPY"); +} + + bool Alter_info::set_requested_lock(const LEX_CSTRING *str) { // To avoid adding new keywords to the grammar, we match strings here. diff --git a/sql/sql_alter.h b/sql/sql_alter.h index 6ae37da774a3e..c1b87ef704148 100644 --- a/sql/sql_alter.h +++ b/sql/sql_alter.h @@ -197,6 +197,15 @@ class Alter_info // Type of ALTER TABLE lock. enum_alter_table_lock requested_lock; + /* + AUTO reads the history range (prep_alter_part_table()) under + MDL_SHARED_NO_WRITE: the copy must not run online. + */ + bool vers_history_read; + /* Why AUTO that reads the history range cannot run online */ + static constexpr const char *vers_history_read_reason= + "PARTITION BY SYSTEM_TIME INTERVAL ... AUTO"; + Alter_info() : select_field_count(0), @@ -205,7 +214,8 @@ class Alter_info original_table(0), num_parts(0), requested_algorithm(ALTER_TABLE_ALGORITHM_NONE), - requested_lock(ALTER_TABLE_LOCK_DEFAULT) + requested_lock(ALTER_TABLE_LOCK_DEFAULT), + vers_history_read(false) {} void reset() @@ -229,6 +239,7 @@ class Alter_info partition_names.empty(); requested_algorithm= ALTER_TABLE_ALGORITHM_NONE; requested_lock= ALTER_TABLE_LOCK_DEFAULT; + vers_history_read= false; } @@ -274,6 +285,11 @@ class Alter_info bool set_requested_lock(const LEX_CSTRING *str); + /* ER_ALTER_OPERATION_NOT_SUPPORTED_REASON for LOCK=NONE, try LOCK=SHARED */ + static void report_lock_none_unsupported(const char *reason); + /* ER_ALTER_OPERATION_NOT_SUPPORTED for the ALGORITHM, try ALGORITHM=COPY */ + void report_algorithm_unsupported(THD *thd) const; + /** Set the requested algorithm to the given algorithm value @param algo_value algorithm to be set diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index 61decbd231ef6..93de9b7c77c0a 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -100,6 +100,10 @@ static int get_partition_id_linear_key_sub(partition_info *part_info, uint32 *pa static uint32 get_next_partition_via_walking(PARTITION_ITERATOR*); static void set_up_range_analysis_info(partition_info *part_info); static uint32 get_next_subpartition_via_walking(PARTITION_ITERATOR*); +static bool vers_calc_hist_parts(THD *thd, my_time_t end, + const Vers_part_info::interval_t &interval, + uint num_subparts, uint *hist_parts, + TABLE_SHARE *s); #endif uint32 get_next_partition_id_range(PARTITION_ITERATOR* part_iter); @@ -6153,20 +6157,28 @@ the generated partition syntax in a correct manner. tab_part_info != part_info && part_info->part_type == VERSIONING_PARTITION && part_info->num_parts == 0) { - if (part_info->vers_info->interval.is_set() && ( + /* AUTO with FORCE re-lays out history by copying (see below) */ + const bool vers_relayout= part_info->vers_info->auto_hist && + (alter_info->flags & ALTER_RECREATE); + if (part_info->vers_info->interval.is_set() && (vers_relayout || !tab_part_info->vers_info->interval.is_set() || part_info->vers_info->interval == tab_part_info->vers_info->interval)) { /* If interval is changed we can not do fast alter */ - tab_part_info= tab_part_info->get_clone(thd); + partition_info *clone= tab_part_info->get_clone(thd); + if (!clone) + goto err; + /* The parsed now_part and hist_part are NULL: keep the clone's */ + clone->vers_info->copy_settings(*part_info->vers_info); + tab_part_info= clone; } else { /* NOTE: fast_alter_partition_table() works on existing TABLE data. */ *fast_alter_table= true; table->mark_table_for_reopen(); + *tab_part_info->vers_info= *part_info->vers_info; } - *tab_part_info->vers_info= *part_info->vers_info; thd->work_part_info= part_info= tab_part_info; *partition_changed= true; } @@ -6198,6 +6210,123 @@ the generated partition syntax in a correct manner. } } + /* + Handle system-versioned auto-partitioning: size the new layout. + Align STARTS to the oldest historical row_end (a user STARTS at or + before it is kept) and create enough history partitions to cover the + newest one and the query time, so pre-existing history is spread + across partitions instead of crammed into a single one. + + Only for INTERVAL ... AUTO (interval.is_set() && auto_hist) on + TIMESTAMP-based versioning, when the parsed clause (alt_part_info, + not part_info, which may already be the table's) has no explicit + partition list (use_default_partitions). A fast ALTER skips it unless + FORCE is given (ALTER_RECREATE), to keep the frm-only path cheap; with + FORCE the re-layout makes it a copy. + */ + const bool vers_auto_alter= + (alter_info->partition_flags & ALTER_PARTITION_INFO) && + part_info->part_type == VERSIONING_PARTITION && + part_info->vers_info->auto_hist; + if (vers_auto_alter && table->versioned(VERS_TIMESTAMP) && + part_info->vers_info->interval.is_set() && + alt_part_info->use_default_partitions && + (!*fast_alter_table || (alter_info->flags & ALTER_RECREATE))) + { + *fast_alter_table= false; + Timestamp min_ts(0, 0), max_ts(0, 0); + Vers_part_info *vers_info= part_info->vers_info; + auto &interval= vers_info->interval; + /* + Block DML from here on: history written after the range is read + would not fit the computed layout. online_alter_check_supported() + keeps the copy offline for this, so the lock is held until the end. + LOCK=NONE and ALGORITHM=INPLACE/NOCOPY/INSTANT are rejected before + waiting for the lock. + */ + if (alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_NONE) + { + Alter_info::report_lock_none_unsupported( + Alter_info::vers_history_read_reason); + goto err; + } + if (alter_info->algorithm_is_nocopy(thd)) + { + alter_info->report_algorithm_unsupported(thd); + goto err; + } + DBUG_ASSERT(!table->s->tmp_table); /* no versioned temporary tables */ + if (thd->mdl_context.upgrade_shared_lock( + table->mdl_ticket, MDL_SHARED_NO_WRITE, + thd->variables.lock_wait_timeout)) + goto err; + alter_info->vers_history_read= true; + if (table->vers_get_history_range(thd, min_ts, max_ts)) + goto err; + if (max_ts.tv_sec > MY_TIME_T_MIN) /* there is history in the table */ + { + DBUG_ASSERT(max_ts.tv_sec <= TIMESTAMP_MAX_VALUE); + DBUG_ASSERT(min_ts.tv_sec <= max_ts.tv_sec); + if (!vers_info->starts_clause || interval.start > min_ts.tv_sec) + { + /* str_interval captures the old STARTS before vers_set_starts(). */ + ErrConvTimestamp str_interval(thd, Timestamp(interval.start, 0), 0); + if (part_info->vers_set_starts(thd, min_ts.tv_sec, + table->s->table_name.str)) + goto err; + if (vers_info->starts_clause) + { + ErrConvTimestamp str_min_ts(thd, min_ts, 6); + ErrConvTimestamp str_interval2(thd, Timestamp(interval.start, 0), + 0); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + WARN_VERS_WRONG_STARTS, + ER_THD(thd, WARN_VERS_WRONG_STARTS), + table->s->table_name.str, + str_interval.ptr(), str_min_ts.ptr(), + str_interval2.ptr()); + } + } + + /* PARTITIONS N given by the user is kept if it is more */ + const uint user_parts= alt_part_info->use_default_num_partitions ? + 0 : alt_part_info->num_parts; + part_info->use_default_num_partitions= false; + part_info->use_default_partitions= true; + if (!part_info->use_default_subpartitions) + { + /* Replace an explicit subpartition list, keeping its size */ + part_info->use_default_subpartitions= true; + part_info->use_default_num_subpartitions= false; + } + part_info->default_partitions_setup= false; + part_info->partitions.empty(); + part_info->vers_info->now_part= NULL; + part_info->vers_info->hist_part= NULL; + + /* + Cover the query time too, so DML right after the ALTER finds an + existing partition for its history; if the layout (history and + current partitions, times subpartitions) exceeds MAX_PARTITIONS, + the ALTER fails. + */ + const my_time_t end= MY_MAX(max_ts.tv_sec, thd->query_start()); + uint hist_parts= 0; + if (vers_calc_hist_parts(thd, end, interval, + MY_MAX(part_info->num_subparts, 1), + &hist_parts, table->s)) + goto err; + part_info->num_parts= MY_MAX(hist_parts + 1, user_parts); + } /* if (max_ts.tv_sec > MY_TIME_T_MIN) */ + else /* no history */ + DBUG_ASSERT(max_ts.cmp(Timestamp(MY_TIME_T_MIN, 0)) == 0); + } + + /* Deferred from vers_set_interval(): STARTS may have been re-aligned */ + if (vers_auto_alter && part_info->vers_info->starts_clause) + part_info->vers_warn_starts_beyond_query(thd, + table->s->table_name.str); + /* Set up partition default_engine_type either from the create_info or from the previus table @@ -6223,7 +6352,7 @@ the generated partition syntax in a correct manner. DBUG_ASSERT(create_info->db_type); create_info->db_type= partition_hton; } - } + } /* if (thd->work_part_info) */ } DBUG_RETURN(FALSE); err: @@ -6234,6 +6363,107 @@ the generated partition syntax in a correct manner. } +/** + Calculate the number of history partitions required for a time range. + + Uses @c interval.start as the left boundary and @p end as the right + boundary. For intervals without a year or month component the count is + computed arithmetically using interval2sec(). For month- or year-based + intervals, boundaries are advanced step by step using date_add_interval() + in UTC. + + @param thd Current thread. + @param end Right boundary (inclusive) as a UNIX timestamp: the + newest history row_end or the query time, whichever + is later. + @param interval Partition interval definition. + @param num_subparts Number of subpartitions per partition (at least 1). + @param hist_parts Out parameter receiving the number of history partitions + needed. + @param s Table share, used to name the table in error messages. + + @retval false Success. + @retval true Error: interval overflow, or the history partitions with the + current partition (times @p num_subparts) would exceed + @c MAX_PARTITIONS. The error is reported. +*/ +static bool vers_calc_hist_parts(THD *thd, my_time_t end, + const Vers_part_info::interval_t &interval, + uint num_subparts, uint *hist_parts, + TABLE_SHARE *s) +{ + uint error= 0; + my_time_t start= interval.start; + DBUG_ASSERT(hist_parts != NULL); + DBUG_ASSERT(num_subparts > 0); + DBUG_ASSERT(!interval.step.neg); /* rejected by vers_set_interval() */ + /* STARTS <= oldest history <= end */ + DBUG_ASSERT(start <= end); + *hist_parts= 0; + + if (!(interval.step.year || interval.step.month)) + { + my_time_t range= end - start; + /* As in date_add_interval(), so that interval2sec() does not overflow */ + if (interval_days_out_of_range(interval.step)) + goto out_of_range; + const longlong i_sec= interval2sec(&interval.step); + DBUG_ASSERT(i_sec > 0); + + /* + A history partition k covers [start + k*i, start + (k+1)*i), so a row + whose row_end lands exactly on a boundary start + k*i belongs to + partition k and needs k+1 history partitions. Hence floor(range/i) + 1, + not ceil(range/i). + */ + const longlong parts= range / i_sec + 1; + if ((parts + 1) * num_subparts > MAX_PARTITIONS) + goto too_many; + *hist_parts= static_cast(parts); + return false; + } + + /* + Step the boundaries in UTC, as check_vers_constants() and + vers_set_hist_part() lay them out: the count must match those + boundaries. The session zone could cross a DST gap and miscount. + */ + MYSQL_TIME boundary; + my_tz_OFFSET0->gmt_sec_to_TIME(&boundary, start); + + while (start <= end) + { + if (date_add_interval(thd, &boundary, interval.type, interval.step, + false)) + goto out_of_range; + + start= my_tz_OFFSET0->TIME_to_gmt_sec(&boundary, &error); + if (error && error != ER_WARN_DATA_OUT_OF_RANGE) + goto out_of_range; + + if ((++(*hist_parts) + 1) * num_subparts > MAX_PARTITIONS) + goto too_many; + /* + Boundary past TIMESTAMP_MAX_VALUE: this is the partial last history + partition, as in check_vers_constants(). + */ + if (error) + break; + } + + return false; + +out_of_range: + my_error(ER_DATA_OUT_OF_RANGE, MYF(ME_WARNING), "TIMESTAMP", "INTERVAL"); + goto fail; +too_many: + my_error(ER_TOO_MANY_PARTITIONS_ERROR, MYF(ME_WARNING)); +fail: + my_error(ER_VERS_HIST_PART_FAILED, MYF(0), s->db.str, s->table_name.str); + return true; +} + + /* Change partitions, used to implement ALTER TABLE ADD/REORGANIZE/COALESCE partitions. This method is used to implement both single-phase and multi- diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 2779003fd6c3a..05377dc430231 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -66,6 +66,7 @@ #include "vector_mhnsw.h" #include "sql_truncate.h" // fk_truncate_illegal_if_parent() #include "sql_rename.h" // do_rename() +#include "key.h" #ifdef WITH_WSREP #include "wsrep_mysqld.h" @@ -2632,7 +2633,7 @@ void promote_first_timestamp_column(List *column_definitions) } } -static bool key_cmp(const Key_part_spec &a, const Key_part_spec &b) +static bool key_eq(const Key_part_spec &a, const Key_part_spec &b) { return a.length == b.length && a.asc == b.asc && a.field_name.streq(b.field_name); @@ -2676,7 +2677,7 @@ static void check_duplicate_key(THD *thd, const Key *key, const KEY *key_info, } if (std::equal(key->columns.begin(), key->columns.end(), k.columns.begin(), - key_cmp)) + key_eq)) { push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, ER_DUP_INDEX, ER_THD(thd, ER_DUP_INDEX), key_info->name.str); @@ -11061,6 +11062,14 @@ const char *online_alter_check_supported(THD *thd, if (!*online) return "DROP SYSTEM VERSIONING"; + /* + The history partitions are sized from the history read before the copy + (prep_alter_part_table()): DML must stay blocked until the end. + */ + *online= !alter_info->vers_history_read; + if (!*online) + return Alter_info::vers_history_read_reason; + *online= !thd->lex->ignore; if (!*online) return "ALTER IGNORE TABLE"; @@ -11919,8 +11928,7 @@ do_continue:; if (alter_info->algorithm_is_nocopy(thd) && !(require_copy_algorithm && alter_info->algorithm_not_specified())) { - my_error(ER_ALTER_OPERATION_NOT_SUPPORTED, MYF(0), - alter_info->algorithm_clause(thd), "ALGORITHM=COPY"); + alter_info->report_algorithm_unsupported(thd); DBUG_RETURN(true); } @@ -12073,8 +12081,7 @@ do_continue:; { if (alter_info->algorithm_is_nocopy(thd)) { - my_error(ER_ALTER_OPERATION_NOT_SUPPORTED, MYF(0), - alter_info->algorithm_clause(thd), "ALGORITHM=COPY"); + alter_info->report_algorithm_unsupported(thd); goto err_new_table_cleanup; } goto alter_copy; @@ -12318,8 +12325,7 @@ do_continue:; alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_NONE) { DBUG_ASSERT(!online); - my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0), - "LOCK=NONE", reason, "LOCK=SHARED"); + Alter_info::report_lock_none_unsupported(reason); goto err_new_table_cleanup; } } @@ -14388,3 +14394,261 @@ bool HA_CREATE_INFO:: } return false; } + + +#ifdef WITH_PARTITION_STORAGE_ENGINE +/** + Find the shortest index usable for scanning the row-end field in order. + + Selects an index whose first key part is the row-end field and that supports + an ordered forward read of that leading part, so its first entry and its last + entry before the current-row (max) key yield the range of historical row-end + values. To qualify, an index must: + + - start with the row-end field; + - support ordered index reads (@c HA_READ_ORDER); + - allow a leading-prefix read, i.e. not require the whole key + (@c HA_ONLY_WHOLE_INDEX), unless it consists of a single key part; + - not sort the row-end part in reverse (@c HA_REVERSE_SORT). + + Among the candidates the one with the shortest key length is chosen. + + @note keys_in_use_for_query is intentionally not consulted, as ALTER TABLE + does not call setup_tables(). + + @param[out] best_idx Index number of the chosen key, or @c MAX_KEY if none. + + @return Chosen key, or @c NULL if none qualifies. +*/ +KEY *TABLE::vers_end_best_idx(uint &best_idx) const +{ + KEY *key= key_info, *best_key= NULL; + best_idx= MAX_KEY; + Field *end_field= vers_end_field(); + const field_index_t end_field_idx= end_field->field_index; + for (uint idx= 0; idx < s->keys; idx++, key++) + { + DBUG_ASSERT(key->key_part->fieldnr > 0); + ulonglong index_flags; + if (key->key_part->fieldnr - 1 == end_field_idx && + ((index_flags= file->index_flags(idx, 0, false)) & HA_READ_ORDER) && + (!(index_flags & HA_ONLY_WHOLE_INDEX) || + key->user_defined_key_parts == 1) && + !(key->key_part->key_part_flag & HA_REVERSE_SORT) && + (!best_key || best_key->key_length > key->key_length)) + { + best_key= key; + best_idx= idx; + } + } + return best_key; +} + + +/** + Find the minimum and maximum historical row-end timestamps in a versioned + table. + + Selects the shortest forward index that starts with the row-end field and + reads its first entry (the minimum) and the last entry before the current-row + (max) key (the maximum historical value). If no usable index exists, or the + table has disabled indexes (ALTER ... DISABLE KEYS), falls back to a full + table scan. The scan warns with advice to add the index only if it read any + row and no usable index exists (a disabled one does not count as missing). + Current rows (row_end at the maximum Timestamp value, or at the pre-11.5 + marker 2038-01-19 03:14:07.999999) are not taken for history on either path. + + Takes and releases the table lock if the handler is not locked yet. + Overwrites record[0]. + + In debug builds, both the index and scan paths are executed and their + results are asserted to be equal when the DBUG_IF "test_mdev-25529" flag + is set. + + @param thd Current thread. + @param min_ts Out parameter: minimum historical row-end timestamp. + Set to TIMESTAMP_MAX_VALUE.999999 when the table has no + history. + @param max_ts Out parameter: maximum historical row-end timestamp. + Set to MY_TIME_T_MIN when the table has no history. + + @retval false Success. + @retval true Error (table lock, handler error or kill), reported. +*/ +bool TABLE::vers_get_history_range(THD *thd, Timestamp &min_ts, + Timestamp &max_ts) +{ + DBUG_ASSERT(versioned()); + Field *end_field= vers_end_field(); + uint best_idx; + KEY *best_key= vers_end_best_idx(best_idx); + /* + Advise adding an index only if there is none: with disabled keys + (ALTER ... DISABLE KEYS) the advice is wrong. + */ + const bool warn_slow= !best_key; + if (file->indexes_are_disabled()) + best_key= NULL; + + int error= 0; + MYSQL_LOCK *lock= NULL; + const Timestamp ts_max(TIMESTAMP_MAX_VALUE, TIME_MAX_SECOND_PART); + const Timestamp ts_min(MY_TIME_T_MIN, 0); + min_ts= ts_max; + max_ts= ts_min; +#ifndef DBUG_OFF + Timestamp min_ts2(0, 0), max_ts2(0, 0); +#endif /* DBUG_OFF */ + MY_BITMAP *save_read_set= read_set; + MY_BITMAP *save_write_set= write_set; + DBUG_ASSERT(save_read_set != &tmp_set); + bitmap_clear_all(&tmp_set); + column_bitmaps_set(&tmp_set, &tmp_set); + bitmap_set_bit(read_set, end_field->field_index); + file->column_bitmaps_signal(false); + DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, s->db.str, + s->table_name.str, + MDL_SHARED_READ)); + DBUG_ASSERT(!s->tmp_table); /* no versioned temporary tables */ + if (file->get_lock_type() == F_UNLCK) + { + /* + Take the table lock, not only the external lock: some engines (Aria) + set up their transaction in THR_LOCK callbacks. + */ + TABLE *tbl= this; + DBUG_EXECUTE_IF("vers_history_range_lock_fail", + my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); + error= 1; + goto end;); + if (!(lock= mysql_lock_tables(thd, &tbl, 1, 0))) + { + error= 1; /* already reported */ + goto end; + } + } + + if (best_key) + { + uchar search_key[MAX_KEY_LENGTH]; + KEY_PART_INFO *key_part= best_key->key_part; + const uint key_prefix_len= key_part[0].store_length; + /* Read relative to the row_end value in record[0] */ + auto read_key= [&](enum ha_rkey_function find_flag) + { + key_copy(search_key, record[0], best_key, key_prefix_len); + return file->ha_index_read_map(record[0], search_key, key_part_map(1), + find_flag); + }; + + /* Get range from index */ + if ((error= file->ha_index_init(best_idx, true))) + goto end_unlock; + + error= file->ha_index_first(record[0]); + if (!error && end_field->is_max()) + { + /* + is_max() also takes the pre-11.5 current-row marker (row_end at + INT_MAX32), which sorts below history after 2038: read after it. + */ + error= read_key(HA_READ_AFTER_KEY); + } + if (!error && !end_field->is_max()) + { + min_ts.tv_sec= end_field->get_timestamp(&min_ts.tv_usec); + end_field->set_max(); + error= read_key(HA_READ_BEFORE_KEY); + if (!error && end_field->is_max()) + { + /* Current rows with the old marker: read before them */ + end_field->store_timestamp(INT_MAX32, TIME_MAX_SECOND_PART); + error= read_key(HA_READ_BEFORE_KEY); + } + /* The first entry is history, so there is a last one */ + DBUG_ASSERT(error != HA_ERR_END_OF_FILE && + error != HA_ERR_KEY_NOT_FOUND); + if (!error) + max_ts.tv_sec= end_field->get_timestamp(&max_ts.tv_usec); + } + else if (error == HA_ERR_END_OF_FILE || error == HA_ERR_KEY_NOT_FOUND) + error= 0; /* no history */ + + file->ha_index_end(); + + if (error) + goto end_unlock; + +#ifndef DBUG_OFF + /* Test both index and scan, compare the results between them */ + min_ts2= min_ts; + max_ts2= max_ts; + if (DBUG_IF("test_mdev-25529")) + { + min_ts= ts_max; + max_ts= ts_min; + goto jump_scan; + } +#endif /* DBUG_OFF */ + } + else + { +#ifndef DBUG_OFF +jump_scan: +#endif /* DBUG_OFF */ + /* Get range by scan */ + ha_rows rows= 0; + Timestamp ts(0, 0); + if ((error= file->ha_rnd_init(1))) + goto end_unlock; + + while (!(error= file->ha_rnd_next(record[0]))) + { + DEBUG_SYNC(thd, "vers_get_history_range_scan"); + if (thd->killed) + { + error= HA_ERR_ABORTED_BY_USER; /* print_error() reports the kill */ + break; + } + rows++; + if (end_field->is_max()) /* current row, also the pre-11.5 marker */ + continue; + ts.tv_sec= end_field->get_timestamp(&ts.tv_usec); + if (ts.cmp(min_ts) < 0) + min_ts= ts; + if (ts.cmp(max_ts) > 0) + max_ts= ts; + } + + file->ha_rnd_end(); + if (warn_slow && rows && error == HA_ERR_END_OF_FILE) + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + WARN_VERS_SLOW_ROW_END, + ER_THD(thd, WARN_VERS_SLOW_ROW_END), + s->table_name.str); +#ifndef DBUG_OFF + if (best_key && error == HA_ERR_END_OF_FILE) + { + DBUG_ASSERT(DBUG_IF("test_mdev-25529")); + DBUG_ASSERT(min_ts.cmp(min_ts2) == 0); + DBUG_ASSERT(max_ts.cmp(max_ts2) == 0); + } +#endif /* DBUG_OFF */ + } + + if (error == HA_ERR_END_OF_FILE) + error= 0; + +end_unlock: + if (lock) + mysql_unlock_tables(thd, lock); + + if (error) + file->print_error(error, MYF(0)); + +end: + column_bitmaps_set(save_read_set, save_write_set); + file->column_bitmaps_signal(false); + return error != 0; +} +#endif /* WITH_PARTITION_STORAGE_ENGINE */ diff --git a/sql/table.h b/sql/table.h index 453cadf999b28..c52caf789f13f 100644 --- a/sql/table.h +++ b/sql/table.h @@ -2078,6 +2078,9 @@ struct TABLE Open_table_context *ot_ctx); bool range_interval_check_partition(THD *thd, TABLE_LIST *table_list, Open_table_context *ot_ctx); + KEY *vers_end_best_idx(uint &best_idx) const; + bool vers_get_history_range(THD *thd, Timestamp &min_ts, + Timestamp &max_ts); #endif bool vers_implicit() const;