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