diff --git a/mysql-test/main/mysqld--help.result b/mysql-test/main/mysqld--help.result index 6121ac791f3..5a91d06a768 100644 --- a/mysql-test/main/mysqld--help.result +++ b/mysql-test/main/mysqld--help.result @@ -1384,6 +1384,8 @@ The following specify which files/extra groups are read (specified before remain Versioning ALTER TABLE mode. ERROR: Fail ALTER with error; KEEP: Keep historical system rows and subject them to ALTER + --system-versioning-insert-history + Allows direct inserts into ROW_START and ROW_END columns --table-cache=# Deprecated; use --table-open-cache instead. --table-definition-cache=# The number of cached table definitions @@ -1856,6 +1858,7 @@ sync-relay-log 10000 sync-relay-log-info 10000 sysdate-is-now FALSE system-versioning-alter-history ERROR +system-versioning-insert-history FALSE table-definition-cache 400 tc-heuristic-recover OFF tcp-keepalive-interval 0 diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result index 8a9f1d942f0..b0174607ce2 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_embedded.result @@ -3552,6 +3552,16 @@ NUMERIC_BLOCK_SIZE NULL ENUM_VALUE_LIST NULL READ_ONLY NO COMMAND_LINE_ARGUMENT NULL +VARIABLE_NAME SYSTEM_VERSIONING_INSERT_HISTORY +VARIABLE_SCOPE SESSION +VARIABLE_TYPE BOOLEAN +VARIABLE_COMMENT Allows direct inserts into ROW_START and ROW_END columns +NUMERIC_MIN_VALUE NULL +NUMERIC_MAX_VALUE NULL +NUMERIC_BLOCK_SIZE NULL +ENUM_VALUE_LIST OFF,ON +READ_ONLY NO +COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME TABLE_DEFINITION_CACHE VARIABLE_SCOPE GLOBAL VARIABLE_TYPE BIGINT UNSIGNED diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result index 0221ecb3861..831db50d52e 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result @@ -4272,6 +4272,16 @@ NUMERIC_BLOCK_SIZE NULL ENUM_VALUE_LIST NULL READ_ONLY NO COMMAND_LINE_ARGUMENT NULL +VARIABLE_NAME SYSTEM_VERSIONING_INSERT_HISTORY +VARIABLE_SCOPE SESSION +VARIABLE_TYPE BOOLEAN +VARIABLE_COMMENT Allows direct inserts into ROW_START and ROW_END columns +NUMERIC_MIN_VALUE NULL +NUMERIC_MAX_VALUE NULL +NUMERIC_BLOCK_SIZE NULL +ENUM_VALUE_LIST OFF,ON +READ_ONLY NO +COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME TABLE_DEFINITION_CACHE VARIABLE_SCOPE GLOBAL VARIABLE_TYPE BIGINT UNSIGNED diff --git a/mysql-test/suite/versioning/r/insert.result b/mysql-test/suite/versioning/r/insert.result index 2645d0184e8..c2ca4392a9f 100644 --- a/mysql-test/suite/versioning/r/insert.result +++ b/mysql-test/suite/versioning/r/insert.result @@ -112,3 +112,192 @@ x y 9 9001 drop table t1; drop table t2; +# +# MDEV-16546 System versioning setting to allow history modification +# +set @@session.time_zone='+00:00'; +create table t1(x int primary key) with system versioning; +create table t2(y int primary key, +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; +create table t3(z int primary key, +row_start timestamp(6) as row start, +row_end timestamp(6) as row end, +period for system_time (row_start, row_end)) +with system versioning; +insert into t1(x, row_start, row_end) values (2, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +ERROR 42S22: Unknown column 'row_start' in 'field list' +insert into t2(y, row_start, row_end) values (2, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +ERROR HY000: The value specified for generated column 'row_start' in table 't2' has been ignored +set @@system_versioning_insert_history= 1; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `x` int(11) NOT NULL, + PRIMARY KEY (`x`) +) ENGINE=DEFAULT_ENGINE DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci WITH SYSTEM VERSIONING +insert into t1(x, row_start, row_end) values (3, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +insert into t2(y, row_start, row_end) values (4, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +insert into t3 values (5, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +select x, row_start, row_end from t1 for system_time all; +x row_start row_end +3 1980-01-01 00:00:00.000000 1980-01-01 00:00:01.000000 +select y, row_start, row_end from t2 for system_time all; +y row_start row_end +4 1980-01-01 00:00:00.000000 1980-01-01 00:00:01.000000 +select z, row_start, row_end from t3 for system_time all; +z row_start row_end +5 1980-01-01 00:00:00.000000 1980-01-01 00:00:01.000000 +insert into t1(x) values (1); +insert into t2(y) values (1); +update t1 set x= x + 1; +update t1 set row_start= '1971-01-01 00:00:00'; +ERROR 42S22: Unknown column 'row_start' in 'field list' +update t2 set row_start= '1971-01-01 00:00:00'; +ERROR HY000: The value specified for generated column 'row_start' in table 't2' has been ignored +insert t1 (x) values (2) on duplicate key update x= 3, row_end= '1970-01-01 00:00:00'; +ERROR 42S22: Unknown column 'row_end' in 'field list' +insert t2 (y) values (1) on duplicate key update y= 3, row_end= '1970-01-01 00:00:00'; +ERROR HY000: The value specified for generated column 'row_end' in table 't2' has been ignored +insert into t1 values (4); +insert into t1 set x= 5, row_start= '1980-01-01 00:00:00', row_end= '1980-01-01 00:00:01'; +insert into t1(x, row_start, row_end) values (6, '1980-01-01 00:00:01', '1980-01-01 00:00:00'); +ERROR HY000: Incorrect row_start value: '1980-01-01 00:00:01.000000' +insert into t1(x, row_start, row_end) values (7, '1980-01-01 00:00:11', '1980-01-01 00:00:11'); +ERROR HY000: Incorrect row_start value: '1980-01-01 00:00:11.000000' +insert into t1(x, row_start) values (8, '1980-01-01 00:00:22'); +ERROR HY000: Incorrect row_start value: '1980-01-01 00:00:22.000000' +insert into t1(x, row_end) values (9, '1980-01-01 00:00:33'); +ERROR HY000: Incorrect row_start value: '0000-00-00 00:00:00.000000' +insert into t1(x, row_end) values (10, TIMESTAMP'2038-01-19 03:14:07.999999'); +ERROR HY000: Incorrect row_start value: '0000-00-00 00:00:00.000000' +select x, check_row_ts(row_start, row_end) from t1 for system_time all order by x; +x check_row_ts(row_start, row_end) +1 HISTORICAL ROW +2 CURRENT ROW +3 HISTORICAL ROW +4 CURRENT ROW +5 HISTORICAL ROW +select x, row_start, row_end from t1 for system_time all +where x > 1 and row_end < TIMESTAMP'2038-01-19 03:14:07.999999' order by x, row_start, row_end; +x row_start row_end +3 1980-01-01 00:00:00.000000 1980-01-01 00:00:01.000000 +5 1980-01-01 00:00:00.000000 1980-01-01 00:00:01.000000 +# Direct insert is not possible for TRX_ID versioning +create or replace table t2(y int primary key, +row_start bigint unsigned as row start, +row_end bigint unsigned as row end, +period for system_time (row_start, row_end)) +with system versioning engine innodb; +insert into t2(y, row_start, row_end) values (0, 1, 2); +ERROR HY000: The value specified for generated column 'row_start' in table 't2' has been ignored +set @@system_versioning_insert_history= 0; +## INSERT..SELECT +create or replace table t2 like t1; +set @@system_versioning_insert_history= 1; +insert into t2 (x, row_start, row_end) select x, row_start, row_end from t1 for system_time all; +select x, check_row_ts(row_start, row_end) from t2 for system_time all order by x; +x check_row_ts(row_start, row_end) +1 HISTORICAL ROW +2 CURRENT ROW +3 HISTORICAL ROW +4 CURRENT ROW +5 HISTORICAL ROW +select x, row_start, row_end from t2 for system_time all +where x > 1 and row_end < TIMESTAMP'2038-01-19 03:14:07.999999' order by x, row_start, row_end; +x row_start row_end +3 1980-01-01 00:00:00.000000 1980-01-01 00:00:01.000000 +5 1980-01-01 00:00:00.000000 1980-01-01 00:00:01.000000 +set @@system_versioning_insert_history= 0; +# REPLACE / REPLACE .. SELECT +create or replace table t2(a int primary key, +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; +replace into t2 (a, row_start, row_end) values (1, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +ERROR HY000: The value specified for generated column 'row_start' in table 't2' has been ignored +replace into t2 (a, row_start, row_end) select x, row_start, row_end from t1; +ERROR HY000: The value specified for generated column 'row_start' in table 't2' has been ignored +create or replace table t2 (a int primary key) with system versioning; +replace into t2 (a, row_start, row_end) values (1, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +ERROR 42S22: Unknown column 'row_start' in 'field list' +replace into t2 (a, row_start, row_end) select x, row_start, row_end from t1; +ERROR 42S22: Unknown column 'row_start' in 'field list' +set @@system_versioning_insert_history= 1; +replace into t2 (a, row_start) values (1, '1980-01-01 00:00:00'); +ERROR HY000: Incorrect row_start value: '1980-01-01 00:00:00.000000' +replace into t2 (a, row_end) values (0, '1980-01-01 00:00:00'); +ERROR HY000: Incorrect row_start value: '0000-00-00 00:00:00.000000' +replace into t2 (a, row_start, row_end) values (1, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +select a, row_start, row_end from t2 for system_time all order by a, row_start, row_end; +a row_start row_end +1 1980-01-01 00:00:00.000000 1980-01-01 00:00:01.000000 +# Changing row_end via REPLACE is NOT possible, we just insert new row: +replace into t2 (a, row_start, row_end) values (1, '1980-01-01 00:00:00', '1990-01-01 00:00:01'); +select a, row_start, row_end from t2 for system_time all order by a, row_start, row_end; +a row_start row_end +1 1980-01-01 00:00:00.000000 1980-01-01 00:00:01.000000 +1 1980-01-01 00:00:00.000000 1990-01-01 00:00:01.000000 +# But changing row_start via REPLACE is possible: +replace into t2 (a, row_start, row_end) values (1, '1971-01-01 00:00:00', '1980-01-01 00:00:01'); +select a, row_start, row_end from t2 for system_time all order by a, row_start, row_end; +a row_start row_end +1 1971-01-01 00:00:00.000000 1980-01-01 00:00:01.000000 +1 1980-01-01 00:00:00.000000 1990-01-01 00:00:01.000000 +replace into t2 (a, row_start, row_end) select x, row_start, row_end from t1 for system_time all +where x > 1 and row_end < TIMESTAMP'2038-01-19 03:14:07.999999'; +select a, row_start, row_end from t2 for system_time all order by a, row_start, row_end; +a row_start row_end +1 1971-01-01 00:00:00.000000 1980-01-01 00:00:01.000000 +1 1980-01-01 00:00:00.000000 1990-01-01 00:00:01.000000 +3 1980-01-01 00:00:00.000000 1980-01-01 00:00:01.000000 +5 1980-01-01 00:00:00.000000 1980-01-01 00:00:01.000000 +# LOAD DATA +select x, row_start, row_end into outfile 'DATAFILE' from t1 for system_time all; +create or replace table t3 like t1; +show create table t3; +Table Create Table +t3 CREATE TABLE `t3` ( + `x` int(11) NOT NULL, + PRIMARY KEY (`x`) +) ENGINE=DEFAULT_ENGINE DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci WITH SYSTEM VERSIONING +set @@system_versioning_insert_history= 1; +show create table t3; +Table Create Table +t3 CREATE TABLE `t3` ( + `x` int(11) NOT NULL, + PRIMARY KEY (`x`) +) ENGINE=DEFAULT_ENGINE DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci WITH SYSTEM VERSIONING +load data infile 'DATAFILE' into table t3 (x, row_start, row_end); +select x, check_row_ts(row_start, row_end) from t3 for system_time all order by x; +x check_row_ts(row_start, row_end) +1 HISTORICAL ROW +2 CURRENT ROW +3 HISTORICAL ROW +4 CURRENT ROW +5 HISTORICAL ROW +select row_start = '1980-01-01 00:00:00', row_end = '1980-01-01 00:00:01' from t3 for system_time all where x = 3; +row_start = '1980-01-01 00:00:00' row_end = '1980-01-01 00:00:01' +1 1 +# Honor secure_timestamp option +# restart: --secure-timestamp=YES +set @@system_versioning_insert_history= 1; +insert into t1(x, row_start, row_end) values (8, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +ERROR 42S22: Unknown column 'row_start' in 'field list' +# restart: --secure-timestamp=REPLICATION +set @@system_versioning_insert_history= 1; +insert into t1(x, row_start, row_end) values (9, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +ERROR 42S22: Unknown column 'row_start' in 'field list' +# restart: --secure-timestamp=SUPER +set @@system_versioning_insert_history= 1; +insert into t1(x, row_start, row_end) values (10, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +create user nobody; +use test; +insert into t1(x, row_start, row_end) values (7, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +ERROR 42S22: Unknown column 'row_start' in 'field list' +use test; +# restart: --secure-timestamp=NO +drop tables t1, t2, t3; diff --git a/mysql-test/suite/versioning/r/rpl.result b/mysql-test/suite/versioning/r/rpl.result index 3e0bc85cea7..f342cd725a2 100644 --- a/mysql-test/suite/versioning/r/rpl.result +++ b/mysql-test/suite/versioning/r/rpl.result @@ -1,6 +1,7 @@ include/master-slave.inc [connection master] connection slave; +set @@session.time_zone='+00:00'; connection master; CREATE TABLE t1 (x int) with system versioning; insert into t1 values (1); @@ -384,4 +385,55 @@ connection slave; connection master; drop tables t, t2; set timestamp= default; +# +# MDEV-16546 System versioning setting to allow history modification +# +create table t1(x int) with system versioning; +insert into t1(x) values (1); +insert into t1(x, row_start, row_end) values (2, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +ERROR 42S22: Unknown column 'row_start' in 'field list' +set @@system_versioning_insert_history= 1; +insert into t1(x, row_start, row_end) values (3, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +update t1 set x= x + 1; +connection slave; +set @@session.time_zone='+00:00'; +select x, check_row_ts(row_start, row_end) from t1 for system_time all order by x; +x check_row_ts(row_start, row_end) +1 HISTORICAL ROW +2 CURRENT ROW +3 HISTORICAL ROW +select row_start = '1980-01-01 00:00:00', row_end = '1980-01-01 00:00:01' from t1 for system_time all where x = 3; +row_start = '1980-01-01 00:00:00' row_end = '1980-01-01 00:00:01' +1 1 +## INSERT..SELECT +connection master; +create or replace table t2 like t1; +set @@system_versioning_insert_history= 1; +insert into t2 (x, row_start, row_end) select x, row_start, row_end from t1 for system_time all; +connection slave; +select x, check_row_ts(row_start, row_end) from t2 for system_time all order by x; +x check_row_ts(row_start, row_end) +1 HISTORICAL ROW +2 CURRENT ROW +3 HISTORICAL ROW +select row_start = '1980-01-01 00:00:00', row_end = '1980-01-01 00:00:01' from t2 for system_time all where x = 3; +row_start = '1980-01-01 00:00:00' row_end = '1980-01-01 00:00:01' +1 1 +# LOAD DATA +connection master; +select x, row_start, row_end into outfile 'DATAFILE' from t1 for system_time all; +create or replace table t3 like t1; +set @@system_versioning_insert_history= 1; +load data infile 'DATAFILE' into table t3 (x, row_start, row_end); +connection slave; +select x, check_row_ts(row_start, row_end) from t3 for system_time all order by x; +x check_row_ts(row_start, row_end) +1 HISTORICAL ROW +2 CURRENT ROW +3 HISTORICAL ROW +select row_start = '1980-01-01 00:00:00', row_end = '1980-01-01 00:00:01' from t3 for system_time all where x = 3; +row_start = '1980-01-01 00:00:00' row_end = '1980-01-01 00:00:01' +1 1 +connection master; +drop tables t1, t2, t3; include/rpl_end.inc diff --git a/mysql-test/suite/versioning/t/insert.test b/mysql-test/suite/versioning/t/insert.test index 4e8c91315c6..37307b3bf97 100644 --- a/mysql-test/suite/versioning/t/insert.test +++ b/mysql-test/suite/versioning/t/insert.test @@ -81,4 +81,161 @@ select x, y from t2; drop table t1; drop table t2; +--echo # +--echo # MDEV-16546 System versioning setting to allow history modification +--echo # +set @@session.time_zone='+00:00'; +let $MAX_TIMESTAMP= TIMESTAMP'2038-01-19 03:14:07.999999'; + +create table t1(x int primary key) with system versioning; +create table t2(y int primary key, + 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; +create table t3(z int primary key, + row_start timestamp(6) as row start, + row_end timestamp(6) as row end, + period for system_time (row_start, row_end)) +with system versioning; +--error ER_BAD_FIELD_ERROR +insert into t1(x, row_start, row_end) values (2, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +--error ER_WARNING_NON_DEFAULT_VALUE_FOR_GENERATED_COLUMN +insert into t2(y, row_start, row_end) values (2, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +set @@system_versioning_insert_history= 1; +--replace_result $default_engine DEFAULT_ENGINE +show create table t1; +insert into t1(x, row_start, row_end) values (3, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +insert into t2(y, row_start, row_end) values (4, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +insert into t3 values (5, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); + +select x, row_start, row_end from t1 for system_time all; +select y, row_start, row_end from t2 for system_time all; +select z, row_start, row_end from t3 for system_time all; + +insert into t1(x) values (1); +insert into t2(y) values (1); + +update t1 set x= x + 1; +--error ER_BAD_FIELD_ERROR +update t1 set row_start= '1971-01-01 00:00:00'; +--error ER_WARNING_NON_DEFAULT_VALUE_FOR_GENERATED_COLUMN +update t2 set row_start= '1971-01-01 00:00:00'; +--error ER_BAD_FIELD_ERROR +insert t1 (x) values (2) on duplicate key update x= 3, row_end= '1970-01-01 00:00:00'; +--error ER_WARNING_NON_DEFAULT_VALUE_FOR_GENERATED_COLUMN +insert t2 (y) values (1) on duplicate key update y= 3, row_end= '1970-01-01 00:00:00'; +# this should work, row_start/row_end must be mentioned explicitly: +insert into t1 values (4); +insert into t1 set x= 5, row_start= '1980-01-01 00:00:00', row_end= '1980-01-01 00:00:01'; +--error ER_WRONG_VALUE +insert into t1(x, row_start, row_end) values (6, '1980-01-01 00:00:01', '1980-01-01 00:00:00'); +--error ER_WRONG_VALUE +insert into t1(x, row_start, row_end) values (7, '1980-01-01 00:00:11', '1980-01-01 00:00:11'); +--error ER_WRONG_VALUE +insert into t1(x, row_start) values (8, '1980-01-01 00:00:22'); +# NOTE: having row_start=0 might be useful and can mean +# "there is no information on when history was started" (an opposite to row_end=MAX_TIMESTAMP) +--error ER_WRONG_VALUE +insert into t1(x, row_end) values (9, '1980-01-01 00:00:33'); +--error ER_WRONG_VALUE +eval insert into t1(x, row_end) values (10, $MAX_TIMESTAMP); +select x, check_row_ts(row_start, row_end) from t1 for system_time all order by x; +eval select x, row_start, row_end from t1 for system_time all +where x > 1 and row_end < $MAX_TIMESTAMP order by x, row_start, row_end; +--echo # Direct insert is not possible for TRX_ID versioning +create or replace table t2(y int primary key, + row_start bigint unsigned as row start, + row_end bigint unsigned as row end, + period for system_time (row_start, row_end)) +with system versioning engine innodb; +--error ER_WARNING_NON_DEFAULT_VALUE_FOR_GENERATED_COLUMN +insert into t2(y, row_start, row_end) values (0, 1, 2); +set @@system_versioning_insert_history= 0; + +--echo ## INSERT..SELECT +create or replace table t2 like t1; +set @@system_versioning_insert_history= 1; +insert into t2 (x, row_start, row_end) select x, row_start, row_end from t1 for system_time all; +select x, check_row_ts(row_start, row_end) from t2 for system_time all order by x; +eval select x, row_start, row_end from t2 for system_time all +where x > 1 and row_end < $MAX_TIMESTAMP order by x, row_start, row_end; +set @@system_versioning_insert_history= 0; + +--echo # REPLACE / REPLACE .. SELECT +create or replace table t2(a int primary key, + 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; +--error ER_WARNING_NON_DEFAULT_VALUE_FOR_GENERATED_COLUMN +replace into t2 (a, row_start, row_end) values (1, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +--error ER_WARNING_NON_DEFAULT_VALUE_FOR_GENERATED_COLUMN +replace into t2 (a, row_start, row_end) select x, row_start, row_end from t1; +create or replace table t2 (a int primary key) with system versioning; +--error ER_BAD_FIELD_ERROR +replace into t2 (a, row_start, row_end) values (1, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +--error ER_BAD_FIELD_ERROR +replace into t2 (a, row_start, row_end) select x, row_start, row_end from t1; +set @@system_versioning_insert_history= 1; +--error ER_WRONG_VALUE +replace into t2 (a, row_start) values (1, '1980-01-01 00:00:00'); +--error ER_WRONG_VALUE +replace into t2 (a, row_end) values (0, '1980-01-01 00:00:00'); +replace into t2 (a, row_start, row_end) values (1, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +select a, row_start, row_end from t2 for system_time all order by a, row_start, row_end; +--echo # Changing row_end via REPLACE is NOT possible, we just insert new row: +# NOTE: because multiple versions of history row with a=1 may exist, so what REPLACE should change? +replace into t2 (a, row_start, row_end) values (1, '1980-01-01 00:00:00', '1990-01-01 00:00:01'); +select a, row_start, row_end from t2 for system_time all order by a, row_start, row_end; +--echo # But changing row_start via REPLACE is possible: +replace into t2 (a, row_start, row_end) values (1, '1971-01-01 00:00:00', '1980-01-01 00:00:01'); +select a, row_start, row_end from t2 for system_time all order by a, row_start, row_end; +eval replace into t2 (a, row_start, row_end) select x, row_start, row_end from t1 for system_time all +where x > 1 and row_end < $MAX_TIMESTAMP; +select a, row_start, row_end from t2 for system_time all order by a, row_start, row_end; + +--echo # LOAD DATA +--let DATAFILE= $MYSQLTEST_VARDIR/tmp/test_versioning_t3.data +--replace_result $DATAFILE DATAFILE +eval select x, row_start, row_end into outfile '$DATAFILE' from t1 for system_time all; +create or replace table t3 like t1; +--replace_result $default_engine DEFAULT_ENGINE +show create table t3; +set @@system_versioning_insert_history= 1; +--replace_result $default_engine DEFAULT_ENGINE +show create table t3; +--replace_result $DATAFILE DATAFILE +eval load data infile '$DATAFILE' into table t3 (x, row_start, row_end); +select x, check_row_ts(row_start, row_end) from t3 for system_time all order by x; +select row_start = '1980-01-01 00:00:00', row_end = '1980-01-01 00:00:01' from t3 for system_time all where x = 3; +--remove_file $DATAFILE + +--echo # Honor secure_timestamp option +--let $restart_parameters= --secure-timestamp=YES +--source include/restart_mysqld.inc +set @@system_versioning_insert_history= 1; +--error ER_BAD_FIELD_ERROR +insert into t1(x, row_start, row_end) values (8, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +--let $restart_parameters= --secure-timestamp=REPLICATION +--source include/restart_mysqld.inc +set @@system_versioning_insert_history= 1; +--error ER_BAD_FIELD_ERROR +insert into t1(x, row_start, row_end) values (9, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +--let $restart_parameters= --secure-timestamp=SUPER +--source include/restart_mysqld.inc +set @@system_versioning_insert_history= 1; +insert into t1(x, row_start, row_end) values (10, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +create user nobody; +change_user nobody; +use test; +--error ER_BAD_FIELD_ERROR +insert into t1(x, row_start, row_end) values (7, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +change_user root; +use test; +--let $restart_parameters= --secure-timestamp=NO +--source include/restart_mysqld.inc + +drop tables t1, t2, t3; + -- source suite/versioning/common_finish.inc diff --git a/mysql-test/suite/versioning/t/rpl.test b/mysql-test/suite/versioning/t/rpl.test index 45ac3e62d7f..3096bd60cd4 100644 --- a/mysql-test/suite/versioning/t/rpl.test +++ b/mysql-test/suite/versioning/t/rpl.test @@ -1,6 +1,7 @@ --source suite/versioning/engines.inc --source include/have_partition.inc --source include/master-slave.inc +--source suite/versioning/common.inc #BUG#12662190 - COM_COMMIT IS NOT INCREMENTED FROM THE BINARY LOGS ON SLAVE, COM_BEGIN IS #Testing command counters -BEFORE. @@ -10,6 +11,7 @@ let $slave_com_commit_before= query_get_value(SHOW GLOBAL STATUS LIKE 'com_commi let $slave_com_insert_before= query_get_value(SHOW GLOBAL STATUS LIKE 'com_insert', Value, 1); let $slave_com_delete_before= query_get_value(SHOW GLOBAL STATUS LIKE 'com_delete', Value, 1); let $slave_com_update_before= query_get_value(SHOW GLOBAL STATUS LIKE 'com_update', Value, 1); +set @@session.time_zone='+00:00'; connection master; CREATE TABLE t1 (x int) with system versioning; @@ -296,4 +298,46 @@ alter table t drop partition p0; drop tables t, t2; set timestamp= default; +--echo # +--echo # MDEV-16546 System versioning setting to allow history modification +--echo # +create table t1(x int) with system versioning; +insert into t1(x) values (1); +--error ER_BAD_FIELD_ERROR +insert into t1(x, row_start, row_end) values (2, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +set @@system_versioning_insert_history= 1; +insert into t1(x, row_start, row_end) values (3, '1980-01-01 00:00:00', '1980-01-01 00:00:01'); +update t1 set x= x + 1; +sync_slave_with_master; +set @@session.time_zone='+00:00'; +select x, check_row_ts(row_start, row_end) from t1 for system_time all order by x; +select row_start = '1980-01-01 00:00:00', row_end = '1980-01-01 00:00:01' from t1 for system_time all where x = 3; + +--echo ## INSERT..SELECT +connection master; +create or replace table t2 like t1; +set @@system_versioning_insert_history= 1; +insert into t2 (x, row_start, row_end) select x, row_start, row_end from t1 for system_time all; +sync_slave_with_master; +select x, check_row_ts(row_start, row_end) from t2 for system_time all order by x; +select row_start = '1980-01-01 00:00:00', row_end = '1980-01-01 00:00:01' from t2 for system_time all where x = 3; + +--echo # LOAD DATA +connection master; +--let DATAFILE= $MYSQLTEST_VARDIR/tmp/test_versioning_t3.data +--replace_result $DATAFILE DATAFILE +eval select x, row_start, row_end into outfile '$DATAFILE' from t1 for system_time all; +create or replace table t3 like t1; +set @@system_versioning_insert_history= 1; +--replace_result $DATAFILE DATAFILE +eval load data infile '$DATAFILE' into table t3 (x, row_start, row_end); +sync_slave_with_master; +select x, check_row_ts(row_start, row_end) from t3 for system_time all order by x; +select row_start = '1980-01-01 00:00:00', row_end = '1980-01-01 00:00:01' from t3 for system_time all where x = 3; +--remove_file $DATAFILE + +connection master; +drop tables t1, t2, t3; + +--source suite/versioning/common_finish.inc --source include/rpl_end.inc diff --git a/sql/handler.cc b/sql/handler.cc index 4a8b58951d3..99bfc8c5c72 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -7573,6 +7573,30 @@ int handler::ha_write_row(const uchar *buf) DBUG_RETURN(error); } + if (table->versioned() && !table->vers_write) + { + Field *row_start= table->vers_start_field(); + Field *row_end= table->vers_end_field(); + MYSQL_TIME ltime; + + bitmap_set_bit(table->read_set, row_start->field_index); + bitmap_set_bit(table->read_set, row_end->field_index); + + /* + Inserting the history row directly, check ROW_START <= ROW_END and + ROW_START is non-zero. + */ + if ((row_start->cmp(row_start->ptr, row_end->ptr) >= 0) || + row_start->get_date(<ime, Datetime::Options( + TIME_NO_ZERO_DATE, time_round_mode_t(time_round_mode_t::FRAC_NONE)))) + { + String val; + row_start->val_str(&val); + my_error(ER_WRONG_VALUE, MYF(0), row_start->field_name.str, val.ptr()); + DBUG_RETURN(HA_ERR_GENERIC); + } + } + MYSQL_INSERT_ROW_START(table_share->db.str, table_share->table_name.str); mark_trx_read_write(); increment_statistics(&SSV::ha_write_count); diff --git a/sql/item.cc b/sql/item.cc index 8bba7b65f2e..0021acfa8f5 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -6127,6 +6127,13 @@ bool Item_field::fix_fields(THD *thd, Item **reference) else if (!from_field) goto error; + if (thd->column_usage == MARK_COLUMNS_WRITE && + from_field != view_ref_found && + thd->vers_insert_history(from_field)) + { + DBUG_ASSERT(from_field->table->versioned()); + from_field->table->vers_write= false; + } table_list= (cached_table ? cached_table : from_field != view_ref_found ? from_field->table->pos_in_table_list : 0); diff --git a/sql/log_event.cc b/sql/log_event.cc index d4d672e1dca..eb51e897480 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -2357,6 +2357,9 @@ void Format_description_log_event::deduct_options_written_to_bin_log() return; } options_written_to_bin_log|= OPTION_EXPLICIT_DEF_TIMESTAMP; + if (server_version_split < Version(10, 11, 0)) + return; + options_written_to_bin_log|= OPTION_INSERT_HISTORY; DBUG_ASSERT(options_written_to_bin_log == OPTIONS_WRITTEN_TO_BIN_LOG); } diff --git a/sql/log_event.h b/sql/log_event.h index fb18e17aaf0..5267227b373 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -529,7 +529,8 @@ class String; */ #define OPTIONS_WRITTEN_TO_BIN_LOG (OPTION_EXPLICIT_DEF_TIMESTAMP |\ OPTION_AUTO_IS_NULL | OPTION_NO_FOREIGN_KEY_CHECKS | \ - OPTION_RELAXED_UNIQUE_CHECKS | OPTION_NOT_AUTOCOMMIT | OPTION_IF_EXISTS) + OPTION_RELAXED_UNIQUE_CHECKS | OPTION_NOT_AUTOCOMMIT | OPTION_IF_EXISTS |\ + OPTION_INSERT_HISTORY) #define CHECKSUM_CRC32_SIGNATURE_LEN 4 /** diff --git a/sql/log_event_server.cc b/sql/log_event_server.cc index 3aac06a3410..7036c5bf1d0 100644 --- a/sql/log_event_server.cc +++ b/sql/log_event_server.cc @@ -1042,7 +1042,8 @@ void Query_log_event::pack_info(Protocol *protocol) if (flags2 & (OPTION_NO_FOREIGN_KEY_CHECKS | OPTION_AUTO_IS_NULL | OPTION_RELAXED_UNIQUE_CHECKS | OPTION_NO_CHECK_CONSTRAINT_CHECKS | - OPTION_IF_EXISTS)) + OPTION_IF_EXISTS | + OPTION_INSERT_HISTORY)) { buf.append(STRING_WITH_LEN("set ")); if (flags2 & OPTION_NO_FOREIGN_KEY_CHECKS) diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 1338d85865f..1e9aeafb1c2 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -9995,9 +9995,8 @@ ER_UNKNOWN_DATA_TYPE ER_UNKNOWN_OPERATOR eng "Operator does not exists: '%-.128s'" spa "El operador no existe: '%-.128s'" -ER_WARN_HISTORY_ROW_START_TIME - eng "Table `%s.%s` history row start '%s' is later than row end '%s'" - spa "En la historia de la tabla `%s.%s` el inicio de fila '%s' es posterior al fin de fila '%s'" +ER_UNUSED_29 + eng "You should never see it" ER_PART_STARTS_BEYOND_INTERVAL eng "%`s: STARTS is later than query time, first history partition may exceed INTERVAL value" spa "%`s: STARTS es posterior al momento de consulta (query), la primera partición de historia puede exceder el valor INTERVAL" diff --git a/sql/sql_base.cc b/sql/sql_base.cc index d78968190e0..4638674ba52 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -6311,7 +6311,8 @@ find_field_in_table(THD *thd, TABLE *table, const char *name, size_t length, if (field->invisible == INVISIBLE_SYSTEM && thd->column_usage != MARK_COLUMNS_READ && - thd->column_usage != COLUMNS_READ) + thd->column_usage != COLUMNS_READ && + !thd->vers_insert_history(field)) DBUG_RETURN((Field*)0); } else @@ -8895,7 +8896,8 @@ fill_record(THD *thd, TABLE *table_arg, List &fields, List &values, if (table->next_number_field && rfield->field_index == table->next_number_field->field_index) table->auto_increment_field_not_null= TRUE; - const bool skip_sys_field= rfield->vers_sys_field(); // TODO: && !thd->vers_modify_history() [MDEV-16546] + const bool skip_sys_field= rfield->vers_sys_field() && + (update || !thd->vers_insert_history(rfield)); if ((rfield->vcol_info || skip_sys_field) && !value->vcol_assignment_allowed_value() && table->s->table_category != TABLE_CATEGORY_TEMPORARY) @@ -9180,7 +9182,10 @@ fill_record(THD *thd, TABLE *table, Field **ptr, List &values, if (field->field_index == autoinc_index) table->auto_increment_field_not_null= TRUE; - if ((unlikely(field->vcol_info) || (vers_sys_field && !ignore_errors)) && + if ((unlikely(field->vcol_info) || + (vers_sys_field && + !ignore_errors && + !thd->vers_insert_history(field))) && !value->vcol_assignment_allowed_value() && table->s->table_category != TABLE_CATEGORY_TEMPORARY) { diff --git a/sql/sql_class.h b/sql/sql_class.h index 08e88308347..f048ef22f9b 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -5574,6 +5574,39 @@ public: { lex= backup_lex; } + + bool vers_insert_history(const Field *field) const + { + if (!field->vers_sys_field()) + return false; + DBUG_ASSERT(field->table->versioned()); + if (field->table->versioned(VERS_TRX_ID)) + return false; + if (!(variables.option_bits & OPTION_INSERT_HISTORY)) + return false; + if (lex->sql_command != SQLCOM_INSERT && + lex->sql_command != SQLCOM_INSERT_SELECT && + lex->sql_command != SQLCOM_REPLACE && + lex->sql_command != SQLCOM_REPLACE_SELECT && + lex->sql_command != SQLCOM_LOAD) + return false; + switch (opt_secure_timestamp) + { + case SECTIME_NO: + return true; + case SECTIME_SUPER: + if (security_ctx->master_access & SUPER_ACL) + return true; + return false; + case SECTIME_REPL: + if (slave_thread) + return true; + return false; + case SECTIME_YES: + return false; + } + return false; + } }; diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 1e5d64885c1..930001714f3 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -373,6 +373,7 @@ static int check_update_fields(THD *thd, TABLE_LIST *insert_table_list, { TABLE *table= insert_table_list->table; my_bool UNINIT_VAR(autoinc_mark); + enum_sql_command sql_command_save= thd->lex->sql_command; table->next_number_field_updated= FALSE; @@ -387,10 +388,17 @@ static int check_update_fields(THD *thd, TABLE_LIST *insert_table_list, field_index); } + thd->lex->sql_command= SQLCOM_UPDATE; + /* Check the fields we are going to modify */ if (setup_fields(thd, Ref_ptr_array(), update_fields, MARK_COLUMNS_WRITE, 0, NULL, 0)) + { + thd->lex->sql_command= sql_command_save; return -1; + } + + thd->lex->sql_command= sql_command_save; if (insert_table_list->is_view() && insert_table_list->is_merged_derived() && @@ -1735,11 +1743,17 @@ int mysql_prepare_insert(THD *thd, TABLE_LIST *table_list, if (check_duplic_insert_without_overlaps(thd, table, duplic) != 0) DBUG_RETURN(true); - if (table->versioned(VERS_TIMESTAMP) && duplic == DUP_REPLACE) + if (table->versioned(VERS_TIMESTAMP)) { // Additional memory may be required to create historical items. - if (table_list->set_insert_values(thd->mem_root)) + if (duplic == DUP_REPLACE && table_list->set_insert_values(thd->mem_root)) DBUG_RETURN(1); + + Field *row_start= table->vers_start_field(); + Field *row_end= table->vers_end_field(); + if (!fields.elements && !(row_start->invisible && row_end->invisible) && + thd->vers_insert_history(row_start)) + table->vers_write= false; } if (!select_insert) @@ -1802,8 +1816,7 @@ static int last_uniq_key(TABLE *table,uint keynr) int vers_insert_history_row(TABLE *table) { DBUG_ASSERT(table->versioned(VERS_TIMESTAMP)); - if (!table->vers_write) - return 0; + DBUG_ASSERT(table->vers_write); restore_record(table,record[1]); // Set Sys_end to now() @@ -2102,6 +2115,7 @@ int write_record(THD *thd, TABLE *table, COPY_INFO *info, select_result *sink) { if (table->versioned(VERS_TRX_ID)) { + DBUG_ASSERT(table->vers_write); bitmap_set_bit(table->write_set, table->vers_start_field()->field_index); table->file->column_bitmaps_signal(); table->vers_start_field()->store(0, false); @@ -2115,7 +2129,7 @@ int write_record(THD *thd, TABLE *table, COPY_INFO *info, select_result *sink) info->deleted++; if (!table->file->has_transactions()) thd->transaction->stmt.modified_non_trans_table= TRUE; - if (table->versioned(VERS_TIMESTAMP)) + if (table->versioned(VERS_TIMESTAMP) && table->vers_write) { store_record(table, record[2]); error= vers_insert_history_row(table); @@ -4191,7 +4205,6 @@ int select_insert::send_data(List &values) DBUG_RETURN(1); } - table->vers_write= table->versioned(); if (table_list) // Not CREATE ... SELECT { switch (table_list->view_check_option(thd, info.ignore)) { @@ -4203,7 +4216,6 @@ int select_insert::send_data(List &values) } error= write_record(thd, table, &info, sel_result); - table->vers_write= table->versioned(); table->auto_increment_field_not_null= FALSE; if (likely(!error)) diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 2f9e8eb81ff..b61d2609aed 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -1773,7 +1773,7 @@ err: static bool select_like_stmt_test(Prepared_statement *stmt, int (*specific_prepare)(THD *thd), - ulong setup_tables_done_option) + ulonglong setup_tables_done_option) { DBUG_ENTER("select_like_stmt_test"); THD *thd= stmt->thd; @@ -1810,7 +1810,7 @@ static bool select_like_stmt_test_with_open(Prepared_statement *stmt, TABLE_LIST *tables, int (*specific_prepare)(THD *thd), - ulong setup_tables_done_option) + ulonglong setup_tables_done_option) { uint table_count= 0; DBUG_ENTER("select_like_stmt_test_with_open"); diff --git a/sql/sql_priv.h b/sql/sql_priv.h index 381ac26f3a3..6f4eff4880c 100644 --- a/sql/sql_priv.h +++ b/sql/sql_priv.h @@ -147,8 +147,7 @@ #define OPTION_RELAXED_UNIQUE_CHECKS (1ULL << 27) // THD, user, binlog #define OPTION_IF_EXISTS (1ULL << 28) // binlog #define OPTION_SCHEMA_TABLE (1ULL << 29) // SELECT, intern -/** Flag set if setup_tables already done */ -#define OPTION_SETUP_TABLES_DONE (1ULL << 30) // intern +#define OPTION_INSERT_HISTORY (1ULL << 30) /** If not set then the thread will ignore all warnings with level notes. */ #define OPTION_SQL_NOTES (1ULL << 31) // THD, user /** @@ -187,6 +186,8 @@ #define OPTION_BIN_COMMIT_OFF (1ULL << 43) /* The following is used to detect a conflict with DISTINCT */ #define SELECT_ALL (1ULL << 44) // SELECT, user, parser +/** Flag set if setup_tables already done */ +#define OPTION_SETUP_TABLES_DONE (1ULL << 45) // intern #define OPTION_LEX_FOUND_COMMENT (1ULL << 0) // intern, parser diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 45fed46e375..f1ff1ca2d1d 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -560,7 +560,7 @@ static void trace_table_dependencies(THD *thd, */ bool handle_select(THD *thd, LEX *lex, select_result *result, - ulong setup_tables_done_option) + ulonglong setup_tables_done_option) { bool res; SELECT_LEX *select_lex= lex->first_select_lex(); diff --git a/sql/sql_select.h b/sql/sql_select.h index 9174d5f7d93..48452ce34a9 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -2086,7 +2086,7 @@ int join_read_key2(THD *thd, struct st_join_table *tab, TABLE *table, struct st_table_ref *table_ref); bool handle_select(THD *thd, LEX *lex, select_result *result, - ulong setup_tables_done_option); + ulonglong setup_tables_done_option); bool mysql_select(THD *thd, TABLE_LIST *tables, List &list, COND *conds, uint og_num, ORDER *order, ORDER *group, Item *having, ORDER *proc_param, ulonglong select_type, diff --git a/sql/sql_union.cc b/sql/sql_union.cc index 758edbdcbaa..0a92422676a 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -33,7 +33,7 @@ #include "item_windowfunc.h" bool mysql_union(THD *thd, LEX *lex, select_result *result, - SELECT_LEX_UNIT *unit, ulong setup_tables_done_option) + SELECT_LEX_UNIT *unit, ulonglong setup_tables_done_option) { DBUG_ENTER("mysql_union"); bool res; diff --git a/sql/sql_union.h b/sql/sql_union.h index 3776831bdd7..2cda0cbba36 100644 --- a/sql/sql_union.h +++ b/sql/sql_union.h @@ -23,7 +23,7 @@ struct LEX; typedef class st_select_lex_unit SELECT_LEX_UNIT; bool mysql_union(THD *thd, LEX *lex, select_result *result, - SELECT_LEX_UNIT *unit, ulong setup_tables_done_option); + SELECT_LEX_UNIT *unit, ulonglong setup_tables_done_option); #endif /* SQL_UNION_INCLUDED */ diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index de7390f9500..e324e860d13 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -6954,3 +6954,10 @@ static Sys_var_ulonglong Sys_max_rowid_filter_size( SESSION_VAR(max_rowid_filter_size), CMD_LINE(REQUIRED_ARG), VALID_RANGE(1024, (ulonglong)~(intptr)0), DEFAULT(128*1024), BLOCK_SIZE(1)); + +static Sys_var_bit Sys_system_versioning_insert_history( + "system_versioning_insert_history", + "Allows direct inserts into ROW_START and ROW_END columns", + SESSION_VAR(option_bits), CMD_LINE(OPT_ARG), + OPTION_INSERT_HISTORY, DEFAULT(FALSE), + NO_MUTEX_GUARD, IN_BINLOG);