From 5478199dc15fa68597f180a2db2c17dd763a46bf Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Mon, 29 Jun 2026 14:00:02 +0300 Subject: [PATCH 1/8] MDEV-25529 Comments * get_next_time() comment * THD::used comment --- sql/event_data_objects.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sql/event_data_objects.cc b/sql/event_data_objects.cc index 72bc2d38d0779..3a1b01156d8e6 100644 --- a/sql/event_data_objects.cc +++ b/sql/event_data_objects.cc @@ -689,6 +689,10 @@ 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() time_zone event time zone From e1b552d2a2133f00817000dc49ec983361f89f91 Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Mon, 29 Jun 2026 15:10:58 +0300 Subject: [PATCH 2/8] MDEV-25529 Fix Temporal_hybrid::to_string() broken return semantics It returned non-NULL on alloc error. --- sql/sql_type.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sql/sql_type.h b/sql/sql_type.h index 7b6e705a86379..695535379e4d6 100644 --- a/sql/sql_type.h +++ b/sql/sql_type.h @@ -1420,8 +1420,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 From 4a934c68a25640fcb2630bb5a0c93c5564f665ae Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Mon, 29 Jun 2026 14:00:02 +0300 Subject: [PATCH 3/8] MDEV-25529 converted COMBINE macro to interval2usec inline function --- sql/sql_time.cc | 8 ++------ sql/sql_time.h | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/sql/sql_time.cc b/sql/sql_time.cc index c38871c3d7896..1a8028ecfe80b 100644 --- a/sql/sql_time.cc +++ b/sql/sql_time.cc @@ -24,6 +24,7 @@ #include +/* Daynumber from year 0 to 9999-12-31 */ #define MAX_DAY_NUMBER 3652424L /* Some functions to calculate dates */ @@ -604,11 +605,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, @@ -648,7 +644,7 @@ bool date_add_interval(THD *thd, MYSQL_TIME *ltime, interval_type int_type, 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 255ebfeb663e3..3cb07e82f3ef7 100644 --- a/sql/sql_time.h +++ b/sql/sql_time.h @@ -165,4 +165,36 @@ longlong pack_time(const MYSQL_TIME *my_time); void unpack_time(longlong packed, MYSQL_TIME *my_time, enum_mysql_timestamp_type ts_type); +/** + Convert an interval value to a total number of seconds. + + The template parameter @c T must provide @c day, @c hour, @c minute, and + @c second members. + + @param x Pointer to an interval-like value. + + @return Total number of seconds represented by @p x. +*/ +template +inline longlong interval2sec(T x) +{ + return ((x->day * 24LL + x->hour) * 60LL + x->minute) * 60LL + x->second; +} + +/** + Convert an interval value to a total number of microseconds. + + Delegates to interval2sec() and adds @c x->second_part. The template + parameter @c T must provide all members required by interval2sec() plus + a @c second_part member. + + @param x Pointer to an interval-like value. + + @return Total number of microseconds represented by @p x. +*/ +template +inline longlong interval2usec(T x) +{ + return interval2sec(x) * 1000000LL + x->second_part; +} #endif /* SQL_TIME_INCLUDED */ From 5f8310eed311068c3fff57be3d938c7d43188bfe Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Mon, 29 Jun 2026 14:00:02 +0300 Subject: [PATCH 4/8] MDEV-25529 TimestampString for printing timestamps --- include/my_time.h | 41 +++++++++++++++++ .../suite/versioning/r/partition.result | 6 +-- sql/partition_info.cc | 8 ++-- sql/share/errmsg-utf8.txt | 8 ++-- sql/sql_class.cc | 22 ++++++++++ sql/sql_class.h | 44 +++++++++++++++++++ sql/sql_type.cc | 17 +++++++ sql/sql_type.h | 1 + 8 files changed, 137 insertions(+), 10 deletions(-) diff --git a/include/my_time.h b/include/my_time.h index 08fef1b5c6d9a..87e42318e4966 100644 --- a/include/my_time.h +++ b/include/my_time.h @@ -301,6 +301,47 @@ enum interval_type INTERVAL_MINUTE_MICROSECOND, INTERVAL_SECOND_MICROSECOND, INTERVAL_LAST }; +/** + High-precision timestamp representation. + + Stores a UNIX timestamp as a seconds-since-epoch value and a sub-second + part in microseconds. +*/ +typedef struct my_timespec +{ + my_time_t sec; + ulong usec; +} my_timespec_t; + +static inline +int cmp(my_timespec_t a, my_timespec_t b) +{ + return ((a.sec > b.sec || (a.sec == b.sec && a.usec > b.usec)) ? 1 : + ((a.sec < b.sec || (a.sec == b.sec && a.usec < b.usec)) ? -1 : 0)); +} + C_MODE_END +#ifdef __cplusplus +constexpr my_timespec_t MY_TIMESPEC_MIN= {MY_TIME_T_MIN, 0}; +constexpr my_timespec_t MY_TIMESPEC_MAX= {TIMESTAMP_MAX_VALUE, TIME_MAX_SECOND_PART}; + +inline +bool operator< (my_timespec_t a, my_timespec_t b) +{ + return cmp(a, b) < 0; +} + +inline +bool operator> (my_timespec_t a, my_timespec_t b) +{ + return cmp(a, b) > 0; +} + +inline +bool operator== (my_timespec_t a, my_timespec_t b) +{ + return (a.sec == b.sec) && (a.usec == b.usec); +} +#endif #endif /* _my_time_h_ */ diff --git a/mysql-test/suite/versioning/r/partition.result b/mysql-test/suite/versioning/r/partition.result index c2a09cf7794a8..70677bde7e14e 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 a55a0840bf220..7d9f4a5ce8e12 100644 --- a/sql/partition_info.cc +++ b/sql/partition_info.cc @@ -2853,10 +2853,12 @@ bool partition_info::vers_set_interval(THD* thd, Item* interval, if (!table) { if (thd->query_start() < vers_info->interval.start) { + TimestampString str_interval(thd, vers_info->interval.start); + TimestampString str_query(thd, thd->query_start()); 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.cstr(), str_query.cstr()); } } } diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 611ca88b2d130..242241af37454 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -12144,10 +12144,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.cc b/sql/sql_class.cc index 70c571f14e6ec..7f92bb246140c 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -9126,6 +9126,28 @@ bool THD::timestamp_to_TIME(MYSQL_TIME *ltime, my_time_t ts, } +/** + Format a timestamp as a string using the session time zone. + + Converts @p ts to a Temporal_hybrid value in the current session time zone + and stores its string representation with @p dec fractional second digits + in @p str. Sets the @c TIME_ZONE_USED flag on the current statement. + + @param str Destination string. + @param dec Number of fractional second digits (0-6). + @param ts Timestamp value to format. + + @retval false Success. + @retval true Conversion or formatting error. +*/ +bool THD::timestamp_to_string(String *str, uint dec, my_timespec_t ts) +{ + Temporal_hybrid th(this, ts); + used|= TIME_ZONE_USED; + return th.to_string(str, dec) == nullptr; +} + + void THD::my_ok_with_recreate_info(const Recreate_info &info, ulong warn_count) { diff --git a/sql/sql_class.h b/sql/sql_class.h index aad3defa963be..3392fe68a6405 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -4780,6 +4780,11 @@ class THD: public THD_count, /* this must be first */ const Type_handler *type_handler_for_datetime() const; bool timestamp_to_TIME(MYSQL_TIME *ltime, my_time_t ts, ulong sec_part, date_mode_t fuzzydate); + bool timestamp_to_string(String *str, uint dec, my_timespec_t ts); + bool timestamp_to_string(String *str, uint dec, my_time_t ts) + { + return timestamp_to_string(str, dec, {ts, 0}); + } inline my_time_t query_start() { return start_time; } inline ulong query_start_sec_part() { used|= QUERY_START_SEC_PART_USED; return start_time_sec_part; } @@ -8721,6 +8726,45 @@ class Write_log_with_flags }; +class TimestampString : public String +{ + THD *thd; + my_timespec_t ts; + +public: + /** + Construct a lazily formatted timestamp from a high-precision value. + + @param thd Current thread. + @param ts Timestamp value (seconds + microseconds) to format on demand. + */ + TimestampString(THD *thd, my_timespec_t ts) : thd{thd}, ts{ts} {} + /** + Construct a lazily formatted timestamp from a whole-seconds value. + + @param thd Current thread. + @param sec Timestamp in seconds since epoch. + */ + TimestampString(THD *thd, my_time_t sec) : thd{thd}, ts{sec, 0} {} + + /** + Return the timestamp formatted as a null-terminated C string. + + Formats the stored timestamp into this String object on demand. + + @param dec Number of fractional second digits to include (default 0). + + @return Pointer to the formatted string, or @c "ERROR" on failure. + */ + const char * cstr(uint dec= 0) + { + if (thd->timestamp_to_string(this, dec, ts)) + return "ERROR"; + else + return c_ptr_safe(); + } +}; + /** Make a new string allocated on THD's mem-root. diff --git a/sql/sql_type.cc b/sql/sql_type.cc index 454612e34e4fb..e45b3e518797d 100644 --- a/sql/sql_type.cc +++ b/sql/sql_type.cc @@ -396,6 +396,23 @@ Temporal_hybrid::Temporal_hybrid(THD *thd, Item *item, date_mode_t fuzzydate) } +/** + Construct a temporal value from a high-precision timestamp. + + Converts @p time.sec to broken-down local time in the current session + time zone and stores @p time.usec as the sub-second part. + + @param thd Current thread. + @param time Timestamp value (seconds + microseconds) to convert. +*/ +Temporal_hybrid::Temporal_hybrid(THD *thd, my_timespec_t time) +{ + thd->variables.time_zone->gmt_sec_to_TIME(this, time.sec); + DBUG_ASSERT(time.usec < 1000000); + second_part= time.usec; +} + + uint Timestamp::binary_length_to_precision(uint length) { switch (length) { diff --git a/sql/sql_type.h b/sql/sql_type.h index 695535379e4d6..77bfc39517935 100644 --- a/sql/sql_type.h +++ b/sql/sql_type.h @@ -1391,6 +1391,7 @@ class Temporal_hybrid: public Temporal else make_from_decimal(thd, warn, nr, mode); } + Temporal_hybrid(THD *thd, my_timespec_t time); // End of constructors bool copy_valid_value_to_mysql_time(MYSQL_TIME *ltime) const From 8d25e36965a58c002928f16e8860a4b5274d31cb Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Mon, 29 Jun 2026 14:00:02 +0300 Subject: [PATCH 5/8] MDEV-25529 cleanup for vers_set_starts() and starts_clause --- sql/partition_info.cc | 51 +++++++++++++++++++++++++++++++------------ sql/partition_info.h | 5 +++++ 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/sql/partition_info.cc b/sql/partition_info.cc index 7d9f4a5ce8e12..79a7bd92e973a 100644 --- a/sql/partition_info.cc +++ b/sql/partition_info.cc @@ -2824,6 +2824,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()) @@ -2864,20 +2865,7 @@ 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) + if (vers_set_starts(thd, thd->query_start())) goto interval_starts_error; } @@ -2889,6 +2877,41 @@ bool partition_info::vers_set_interval(THD* thd, Item* interval, } +/** + Round down the STARTS value to the partition interval boundary. + + Converts @p ts to broken-down local time in the current session time zone, + then clears time components (second, minute, hour) until the value aligns + with the coarsest unit present in @c vers_info->interval.step. The + result is stored back in @c vers_info->interval.start. + + @param thd Current thread. + @param ts Timestamp to align. + + @retval false Success. + @retval true Error while converting the rounded value back to TIMESTAMP. +*/ +bool partition_info::vers_set_starts(THD *thd, my_time_t ts) +{ + 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); + return (bool) err; +} + + 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 e25e27860c966..6debf10189f2d 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,7 @@ struct Vers_part_info : public Sql_alloc } interval; ulonglong limit; bool auto_hist; + bool starts_clause; partition_element *now_part; partition_element *hist_part; }; @@ -420,6 +424,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); bool vers_set_limit(ulonglong limit, bool auto_part, const char *table_name); bool vers_set_hist_part(THD* thd, uint *create_count); bool vers_require_hist_part(THD *thd) const From 08acd39189c9cd9cf553479d73434eef579d9aa1 Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Mon, 29 Jun 2026 14:00:03 +0300 Subject: [PATCH 6/8] MDEV-25529 ALTER TABLE FORCE syntax improved Improves ALTER TABLE syntax when alter_list can be supplied alongside a partitioning expression, so that they can appear in any order. This is particularly useful for the FORCE clause when adding it to an existing command. Also improves handling of AUTO with FORCE, so that AUTO FORCE specified together provides more consistent syntax, which is used by this task in further commits. --- mysql-test/main/partition.result | 15 +++++++++++++++ mysql-test/main/partition.test | 17 +++++++++++++++++ sql/sql_yacc.yy | 4 ++++ 3 files changed, 36 insertions(+) diff --git a/mysql-test/main/partition.result b/mysql-test/main/partition.result index 48cc686c6567c..e7fc0e6855a55 100644 --- a/mysql-test/main/partition.result +++ b/mysql-test/main/partition.result @@ -2922,4 +2922,19 @@ 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; 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..6703d5bdb4313 100644 --- a/mysql-test/main/partition.test +++ b/mysql-test/main/partition.test @@ -3147,4 +3147,21 @@ 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; +# Strange syntax, but it exists since 2005 (cd483c55) +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; + --source include/test_db_charset_restore.inc diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 6ab9dff16ad4f..f42136775d0f1 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -7855,8 +7855,12 @@ alter_commands: } | alter_list opt_partitioning + | partitioning + alter_list | alter_list remove_partitioning + | remove_partitioning + alter_list | remove_partitioning | partitioning /* From 3615202f5a9078635d5b285be6be5060a3e1feb3 Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Mon, 29 Jun 2026 14:00:03 +0300 Subject: [PATCH 7/8] MDEV-25529 set_up_default_partitions() ER_OUT_OF_RESOURCES error --- sql/partition_info.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sql/partition_info.cc b/sql/partition_info.cc index 79a7bd92e973a..8b7abfbb3e4c3 100644 --- a/sql/partition_info.cc +++ b/sql/partition_info.cc @@ -429,6 +429,7 @@ bool partition_info::set_up_default_partitions(THD *thd, handler *file, i= 0; do { + // TODO (newbie): how is it freed? Explain in comment or remake via table->mem_root. partition_element *part_elem= new partition_element(); if (likely(part_elem != 0 && !partitions.push_back(part_elem))) { @@ -448,7 +449,10 @@ bool partition_info::set_up_default_partitions(THD *thd, handler *file, } } else + { + my_error(ER_OUT_OF_RESOURCES, MYF(0)); goto end; + } } while (++i < num_parts); result= FALSE; end: From 74ccf259b5211415d3ae7c80fd6b9c195c502520 Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Mon, 29 Jun 2026 14:00:03 +0300 Subject: [PATCH 8/8] MDEV-25529 Auto-create: Pre-existing historical data is not partitioned as specified by ALTER Adds logic into prep_alter_part_table() for AUTO to check the history range (vers_get_history_range()) and based on (max_ts - min_ts) difference compute the number of created partitions and set STARTS value to round down min_ts value (vers_set_starts()) if it was not specified by user or if the user specified it incorrectly. In the latter case it will print warning about wrongly specified user value. 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. vers_get_history_range() detects if the 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 table scan to read the stats. There is test_mdev-25529 debug keyword to check the both and compare results. A warning is printed if the algorithm uses slow scan. Field_vers_trx_id::get_timestamp() is implemented for TRX_ID based versioning to get epoch value. It works in vers_get_history_range() but since partitioning is not enabled for TRX_ID versioning create temporary table fails with error, requiring timestamp-based system fields. This method will be useful when partitioning will be enabled for TRX_ID which is mostly performance problems to solve. 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/partition2.result | 530 ++++++++++++++++++ mysql-test/suite/versioning/t/partition2.test | 230 ++++++++ sql/field.cc | 53 ++ sql/field.h | 1 + sql/partition_info.cc | 4 +- sql/share/errmsg-utf8.txt | 8 + sql/sql_partition.cc | 154 ++++- sql/sql_table.cc | 173 +++++- sql/table.h | 2 + 9 files changed, 1151 insertions(+), 4 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/partition2.result b/mysql-test/suite/versioning/r/partition2.result new file mode 100644 index 0000000000000..378365da702c6 --- /dev/null +++ b/mysql-test/suite/versioning/r/partition2.result @@ -0,0 +1,530 @@ +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 +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 3 +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 +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 +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 3 +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 +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 +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 +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 4164 `t1`: STARTS timestamp 2002-01-01 00:00:00 is later than query timestamp 2000-01-02 03:01:23, first history partition may exceed INTERVAL value +Warning 4265 `t1`: STARTS timestamp 2002-01-01 00:00:00 is above earliest history date 2000-01-01 00:10:00 and was set to 2000-01-01 00:00:00 +# STARTS changed and WARN_VERS_WRONG_STARTS is here +alter table t1 force partition by system_time interval 1 hour +starts '2003-01-01 00:00:00' auto; +Warnings: +Warning 4265 `t1`: STARTS timestamp 2003-01-01 00:00:00 is above earliest history date 2000-01-01 00:10:00 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 4164 `t1`: STARTS timestamp 2004-01-01 00:00:00 is later than query timestamp 2000-01-02 03:01:23, first history partition may exceed INTERVAL value +Warning 4265 `t1`: STARTS timestamp 2004-01-01 00:00:00 is above earliest history date 2000-01-01 00:10:00 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 doesn't change 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 +# Explicit partition list ignores AUTO FORCE +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 7 +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 4164 `t1`: STARTS timestamp 2001-01-01 00:00:00 is later than query timestamp 2000-01-03 00:00:00, first history partition may exceed INTERVAL value +Warning 4265 `t1`: STARTS timestamp 2001-01-01 00:00:00 is above earliest history date 2000-01-01 00:00:00 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 4266 `t1`: ROW END index not found, using slow scan +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; diff --git a/mysql-test/suite/versioning/t/partition2.test b/mysql-test/suite/versioning/t/partition2.test new file mode 100644 index 0000000000000..5c07790084b23 --- /dev/null +++ b/mysql-test/suite/versioning/t/partition2.test @@ -0,0 +1,230 @@ +--source include/have_partition.inc +--source include/have_innodb.inc +--source include/maybe_debug.inc + +--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.00000021 +select *, row_start, row_end from t1 for system_time all; + +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); + +alter table t1 remove partitioning; +alter table t1 partition by system_time interval 1 hour +starts '2002-01-01 00:00:00' auto; + +--echo # STARTS changed and WARN_VERS_WRONG_STARTS is here +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 doesn't change 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 # Explicit partition list ignores AUTO FORCE +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; + +if ($have_debug) +{ + --disable_query_log + set debug_dbug= @old_dbug; + --enable_query_log +} + +--disable_prepare_warnings diff --git a/sql/field.cc b/sql/field.cc index aa20a08be45b1..b390d5a7fde48 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -2406,6 +2406,59 @@ bool Field_vers_trx_id::get_date(MYSQL_TIME *ltime, date_mode_t fuzzydate, ulong } +/** + Retrieve the UNIX timestamp for a transaction-versioned field value. + + Reads the transaction ID from the raw field storage at @p pos and looks + it up in the transaction registry table (TRT). If the stored value equals + @c ULONGLONG_MAX (the open-ended "infinity" sentinel), returns + @c TIMESTAMP_MAX_VALUE immediately without a TRT lookup. + + If the transaction ID is not found in the TRT, emits @c ER_VERS_NO_TRX_ID + as a warning and returns @c -1. + + @note A missing TRT entry is reported as a warning rather than a fatal + error because the registry row may have been purged after the + versioned row was written; the caller can still continue. + + @param[in] pos Pointer to the raw 8-byte field value containing + the transaction ID. + @param[out] sec_part If non-NULL, receives the microsecond part of the + commit timestamp. + + @return Commit timestamp in seconds since epoch, @c TIMESTAMP_MAX_VALUE + for the infinity sentinel, or @c -1 if the transaction ID is not + found in the TRT. +*/ +my_time_t Field_vers_trx_id::get_timestamp(const uchar *pos, ulong *sec_part) const +{ + ulonglong trx_id= uint8korr(pos); + THD *thd= get_thd(); + if (trx_id == ULONGLONG_MAX) + { + if (sec_part) + { + *sec_part= TIME_MAX_SECOND_PART; + } + return TIMESTAMP_MAX_VALUE; + } + TR_table trt(thd); + if (trt.query(trx_id)) + { + return trt[TR_table::FLD_COMMIT_TS]->get_timestamp(sec_part); + } + + if (sec_part) + *sec_part= 0; + + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + ER_VERS_NO_TRX_ID, ER_THD(thd, ER_VERS_NO_TRX_ID), + (longlong) trx_id); + + return -1; +} + + Field_str::Field_str(uchar *ptr_arg,uint32 len_arg, uchar *null_ptr_arg, uchar null_bit_arg, utype unireg_check_arg, const LEX_CSTRING *field_name_arg, diff --git a/sql/field.h b/sql/field.h index a509c194bb101..0e8bb0bd598e0 100644 --- a/sql/field.h +++ b/sql/field.h @@ -3003,6 +3003,7 @@ class Field_vers_trx_id :public Field_longlong { { return get_date(ltime, fuzzydate, (ulonglong) val_int()); } + my_time_t get_timestamp(const uchar *pos, ulong *sec_part) const override; bool test_if_equality_guarantees_uniqueness(const Item *item) const override; Data_type_compatibility can_optimize_keypart_ref(const Item_bool_func *, const Item *) diff --git a/sql/partition_info.cc b/sql/partition_info.cc index 8b7abfbb3e4c3..43c66ee677355 100644 --- a/sql/partition_info.cc +++ b/sql/partition_info.cc @@ -2857,7 +2857,9 @@ 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 && + !(auto_hist && (thd->lex->alter_info.flags & ALTER_RECREATE))) + { TimestampString str_interval(thd, vers_info->interval.start); TimestampString str_query(thd, thd->query_start()); push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 242241af37454..92d03b8bb8211 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -12406,3 +12406,11 @@ ER_WARN_QB_NAME_PATH_VIEW_NOT_FOUND eng "Hint %s is ignored. `%s` required at element #%u of the path is not found in the target query block." ER_WARN_QB_NAME_PATH_NOT_SUPPORTED_INSIDE_VIEW eng "Hint %s is ignored. QB_NAME hints with path are not supported inside view definitions." +WARN_VERS_WRONG_STARTS + chi "%sQ: STARTS 时间戳 %s 晚于最早历史日期 %s,已被设置为 %s" + eng "%sQ: STARTS timestamp %s is above earliest history date %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 索引,正在使用慢速扫描" + eng "%sQ: ROW END index not found, using slow scan" + spa "%sQ: no se encontró el índice ROW END, usando escaneo lento" diff --git a/sql/sql_partition.cc b/sql/sql_partition.cc index e997b596cbc58..72bbfaab3caed 100644 --- a/sql/sql_partition.cc +++ b/sql/sql_partition.cc @@ -100,6 +100,29 @@ 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*); +/** + 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 second-based intervals the count is computed arithmetically + using interval2sec(). For month- or year-based intervals, boundaries are + advanced step by step using date_add_interval() in the current session + time zone. + + @param thd Current thread. + @param end Right boundary (inclusive) of the history range as a + UNIX timestamp. + @param interval Partition interval definition. + @param hist_parts Out parameter receiving the number of history partitions + needed. + + @retval false Success. + @retval true Error: interval overflow or number of partitions would + exceed @c MAX_PARTITIONS. +*/ +static bool vers_calc_hist_parts(THD *thd, my_time_t end, + const Vers_part_info::interval_t &interval, + uint *hist_parts); #endif uint32 get_next_partition_id_range(PARTITION_ITERATOR* part_iter); @@ -6138,6 +6161,70 @@ the generated partition syntax in a correct manner. } } + if (table->versioned() && + (alter_info->partition_flags & ALTER_PARTITION_INFO) && + part_info->part_type == VERSIONING_PARTITION && + part_info->vers_info->auto_hist && + alt_part_info->use_default_partitions && + (!*fast_alter_table || (alter_info->flags & ALTER_RECREATE))) + { + if (*fast_alter_table) + *fast_alter_table= false; + my_timespec_t min_ts, max_ts; + Vers_part_info *vers_info= part_info->vers_info; + auto &interval= vers_info->interval; + if (table->vers_get_history_range(thd, min_ts, max_ts)) + DBUG_RETURN(true); + if (max_ts.sec > MY_TIME_T_MIN) /* there is history in the table */ + { + DBUG_ASSERT(max_ts.sec < TIMESTAMP_MAX_VALUE); + DBUG_ASSERT(min_ts.sec <= max_ts.sec); + if (max_ts.usec) + { + max_ts.sec++; + max_ts.usec= 0; + } + if (vers_info->starts_clause) + { + if (interval.start > min_ts.sec) + { + TimestampString str_interval(thd, interval.start); + if (part_info->vers_set_starts(thd, min_ts.sec)) + { + my_error(ER_PART_WRONG_VALUE, MYF(0), + table->s->table_name.str, "STARTS"); + DBUG_RETURN(true); + } + TimestampString str_min_ts(thd, min_ts); + TimestampString str_interval2(thd, interval.start); + 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.cstr(), str_min_ts.cstr(), str_interval2.cstr()); + + } + } + else if (part_info->vers_set_starts(thd, min_ts.sec)) + { + my_error(ER_PART_WRONG_VALUE, MYF(0), + table->s->table_name.str, "STARTS"); + DBUG_RETURN(true); + } + + part_info->use_default_num_partitions= false; + part_info->use_default_partitions= true; + part_info->default_partitions_setup= false; + part_info->partitions.empty(); + + uint hist_parts= 0; + if (vers_calc_hist_parts(thd, max_ts.sec, interval, &hist_parts)) + DBUG_RETURN(true); + part_info->num_parts= hist_parts + 1; + } /* if (max_ts.sec > MY_TIME_T_MIN) */ + else + DBUG_ASSERT(max_ts == MY_TIMESPEC_MIN); /* no history in table */ + } /* if (need to get history range) */ + /* Set up partition default_engine_type either from the create_info or from the previus table @@ -6163,7 +6250,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: @@ -6174,6 +6261,71 @@ the generated partition syntax in a correct manner. } +static bool vers_calc_hist_parts(THD *thd, my_time_t end, + const Vers_part_info::interval_t &interval, + uint *hist_parts) +{ + uint error= 0; + my_time_t start= interval.start; + DBUG_ASSERT(hist_parts != NULL); + *hist_parts= 0; + + if (end < start) + return false; + + if (end == start) + { + *hist_parts= 1; + return false; + } + + if (!(interval.step.year || interval.step.month)) + { + my_time_t range= end - start; + const longlong i_sec= interval2sec(&interval.step); + DBUG_ASSERT(i_sec > 0); + + *hist_parts= static_cast(range / i_sec); + if ((*hist_parts) * i_sec != range) + (*hist_parts)++; + if (*hist_parts >= MAX_PARTITIONS) + { + my_error(ER_TOO_MANY_PARTITIONS_ERROR, MYF(0)); + return true; + } + return false; + } + + MYSQL_TIME boundary; + thd->variables.time_zone->gmt_sec_to_TIME(&boundary, start); + + while (start < end) + { + if (date_add_interval(thd, &boundary, interval.type, interval.step)) + { + my_error(ER_DATA_OUT_OF_RANGE, MYF(0), "TIMESTAMP", "INTERVAL"); + return true; + } + + start= thd->variables.time_zone->TIME_to_gmt_sec(&boundary, &error); + if (error) + { + my_error(ER_DATA_OUT_OF_RANGE, MYF(0), "TIMESTAMP", "INTERVAL"); + return true; + } + + (*hist_parts)++; + if (*hist_parts >= MAX_PARTITIONS) + { + my_error(ER_TOO_MANY_PARTITIONS_ERROR, MYF(0)); + return true; + } + } + + return false; +} + + /* 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 4d2c1d6861647..1f9cc0b95a32f 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -64,6 +64,7 @@ #include "rpl_rli.h" #include "log.h" #include "vector_mhnsw.h" +#include "key.h" #ifdef WITH_WSREP #include "wsrep_mysqld.h" @@ -2488,7 +2489,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); @@ -2532,7 +2533,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); @@ -13936,3 +13937,171 @@ bool HA_CREATE_INFO:: } return false; } + + +#ifdef WITH_PARTITION_STORAGE_ENGINE +/** + 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 and last entries to obtain the range. If no suitable + index exists, falls back to a full table scan with a warning. Open-ended + "current" rows (@c MY_TIMESPEC_MAX) are skipped when scanning. + + 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. + @param max_ts Out parameter: maximum historical row-end timestamp. + + @retval false Success. + @retval true Handler or conversion error. +*/ +bool TABLE::vers_get_history_range(THD *thd, my_timespec_t &min_ts, + my_timespec_t &max_ts) +{ + DBUG_ASSERT(versioned()); + // Find best key + KEY *key= key_info, *best_key= NULL; + uint best_idx; + 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++) + { + /* + Note: we don't check keys_in_use_for_query as ALTER TABLE doesn't call + setup_tables(). + */ + DBUG_ASSERT(key->key_part->fieldnr > 0); + const ulong index_flags= file->index_flags(idx, 0, false); + if (key->key_part->fieldnr - 1 == end_field_idx && + (index_flags & HA_READ_ORDER) && + !(index_flags & HA_ONLY_WHOLE_INDEX) && + !(key->key_part->key_part_flag & HA_REVERSE_SORT) && + (!best_key || best_key->key_length > key->key_length)) + { + best_key= key; + best_idx= idx; + } + } + + int error; + my_timespec_t ts; + min_ts= MY_TIMESPEC_MAX; + max_ts= MY_TIMESPEC_MIN; +#ifndef DBUG_OFF + my_timespec_t min_ts2, max_ts2; +#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)); + if ((error= file->ha_external_lock(thd, F_RDLCK))) + goto end; + + if (best_key) + { + uchar search_key[MAX_KEY_LENGTH]; + KEY *best_key_info= key_info + best_idx; + end_field->set_max(); + KEY_PART_INFO *key_part= best_key_info->key_part; + const uint key_prefix_len= key_part[0].store_length; + key_copy(search_key, record[0], best_key_info, key_prefix_len); + + /* Get range from index */ + if ((error= file->ha_index_init(best_idx, true))) + goto end_unlock; + + if (!(error= file->ha_index_first(record[0]))) + { + min_ts.sec= end_field->get_timestamp(&min_ts.usec); + error= file->ha_index_read_map(record[0], (uchar*) search_key, + (key_part_map) 1, HA_READ_BEFORE_KEY); + if (!error) + { + max_ts.sec= end_field->get_timestamp(&max_ts.usec); + } + } + + file->ha_index_end(); + + if (error == HA_ERR_END_OF_FILE || error == HA_ERR_KEY_NOT_FOUND) + error= 0; + else 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")) + goto jump_scan; +#endif /* DBUG_OFF */ + } + else + { + 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 +jump_scan: +#endif /* DBUG_OFF */ + /* Get range by scan */ + if ((error= file->ha_rnd_init(1))) + goto end_unlock; + + /* can_continue_handler_scan() is only for heap (record changed protection) */ + while (!(error= file->can_continue_handler_scan()) && + !(error= file->ha_rnd_next(record[0]))) + { + ts.sec= end_field->get_timestamp(&ts.usec); + if (ts == MY_TIMESPEC_MAX) + continue; + if (ts < min_ts) + min_ts= ts; + if (ts > max_ts) + max_ts= ts; + } + + file->ha_rnd_end(); +#ifndef DBUG_OFF + if (best_key) + { + DBUG_ASSERT(DBUG_IF("test_mdev-25529")); + DBUG_ASSERT(min_ts == min_ts2); + DBUG_ASSERT(max_ts == max_ts2); + } +#endif /* DBUG_OFF */ + } + + if (error == HA_ERR_END_OF_FILE) + error= 0; + +end_unlock: + file->ha_external_unlock(thd); + +end: + if (error) + { + myf flags= 0; + + if (file->is_fatal_error(error, HA_CHECK_ALL)) + flags|= ME_FATAL; /* Other handler errors are fatal */ + + file->print_error(error, MYF(flags)); + } + + column_bitmaps_set(save_read_set, save_write_set); + file->column_bitmaps_signal(false); + return (bool) error; +} +#endif /* WITH_PARTITION_STORAGE_ENGINE */ diff --git a/sql/table.h b/sql/table.h index ba15cf45a1257..2ebad4c3586ae 100644 --- a/sql/table.h +++ b/sql/table.h @@ -2024,6 +2024,8 @@ struct TABLE #ifdef WITH_PARTITION_STORAGE_ENGINE bool vers_switch_partition(THD *thd, TABLE_LIST *table_list, Open_table_context *ot_ctx); + bool vers_get_history_range(THD *thd, my_timespec_t &min_ts, + my_timespec_t &max_ts); #endif bool vers_implicit() const;