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/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/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/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/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 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 a55a0840bf220..43c66ee677355 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: @@ -2824,6 +2828,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()) @@ -2852,30 +2857,21 @@ 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, - 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()); } } } 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; } @@ -2887,6 +2883,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 diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 611ca88b2d130..92d03b8bb8211 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" @@ -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_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_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/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 */ 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 7b6e705a86379..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 @@ -1420,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 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 /* 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;