← Back to team overview

maria-developers team mailing list archive

Re: c39f74ce0d9: MDEV-16974 Application period tables: UPDATE

 

Hi, Nikita!

See comments below

On Jan 06, Nikita Malyavin wrote:
> revision-id: c39f74ce0d9 (versioning-1.0.7-4-gc39f74ce0d9)
> parent(s): fd18ed07d65
> author: Nikita Malyavin <nikitamalyavin@xxxxxxxxx>
> committer: Nikita Malyavin <nikitamalyavin@xxxxxxxxx>
> timestamp: 2018-12-03 13:19:18 +1000
> message:
> 
> MDEV-16974 Application period tables: UPDATE
> 
> diff --git a/mysql-test/suite/period/r/delete.result b/mysql-test/suite/period/r/delete.result
> index 30c9220d6f9..ccb8663bf72 100644
> --- a/mysql-test/suite/period/r/delete.result
> +++ b/mysql-test/suite/period/r/delete.result
> @@ -83,6 +83,9 @@ log
>  <INS: 3, 1997-01-01, 2000-01-01
>  >INS: 4, 2018-01-01, 2020-01-01
>  <INS: 4, 2018-01-01, 2020-01-01
> +# multi-table DELETE is prohibited
> +delete t, t1 from t for portion of apptime from '2000-01-01' to '2018-01-01', t1;
> +ERROR HY000: PORTION OF time is not allowed here

why not ER_PARSE_ERROR?

>  delete history from t2 for portion of apptime from '2000-01-01' to '2018-01-01';
>  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 '' at line 1
>  delete from t for portion of othertime from '2000-01-01' to '2018-01-01';
> diff --git a/mysql-test/suite/period/r/update.result b/mysql-test/suite/period/r/update.result
> new file mode 100644
> index 00000000000..1f59102c131
> --- /dev/null
> +++ b/mysql-test/suite/period/r/update.result
> @@ -0,0 +1,244 @@
> +create or replace table t (id int, s date, e date, period for apptime(s,e));
> +insert into t values(1, '1999-01-01', '2018-12-12');
> +insert into t values(1, '1999-01-01', '2017-01-01');
> +insert into t values(1, '2017-01-01', '2019-01-01');
> +insert into t values(2, '1998-01-01', '2018-12-12');
> +insert into t values(3, '1997-01-01', '2015-01-01');
> +insert into t values(4, '2016-01-01', '2020-01-01');

I'd insert also, say, (5, '2010-01-01', '2015-01-01').

> +create or replace table t1 (id int, s date, e date, period for apptime(s,e));
> +insert t1 select * from t;
> +create or replace table t2 (id int, s date, e date, period for apptime(s,e));
> +insert t2 select * from t;
> +update t for portion of apptime from '2000-01-01' to '2018-01-01'
> +       set id=id + 5;
> +select * from t;

same comment about unsorted selects

> +id	s	e
> +6	2000-01-01	2018-01-01
> +6	2000-01-01	2017-01-01
> +6	2017-01-01	2018-01-01
> +7	2000-01-01	2018-01-01
> +8	2000-01-01	2015-01-01
> +9	2016-01-01	2018-01-01
> +1	1999-01-01	2000-01-01
> +1	2018-01-01	2018-12-12
> +1	1999-01-01	2000-01-01
> +1	2018-01-01	2019-01-01
> +2	1998-01-01	2000-01-01
> +2	2018-01-01	2018-12-12
> +3	1997-01-01	2000-01-01
> +4	2018-01-01	2020-01-01
> +# Check triggers
> +update t1 for portion of apptime from '2000-01-01' to '2018-01-01'
> +       set id=id + 5;
> +select * from t1 order by id, s, e;
> +id	s	e
> +1	1999-01-01	2000-01-01
> +1	1999-01-01	2000-01-01
> +1	2018-01-01	2018-12-12
> +1	2018-01-01	2019-01-01
> +2	1998-01-01	2000-01-01
> +2	2018-01-01	2018-12-12
> +3	1997-01-01	2000-01-01
> +4	2018-01-01	2020-01-01
> +6	2000-01-01	2017-01-01
> +6	2000-01-01	2018-01-01
> +6	2017-01-01	2018-01-01
> +7	2000-01-01	2018-01-01
> +8	2000-01-01	2015-01-01
> +9	2016-01-01	2018-01-01
> +select * from log_tbl;
> +log
> +>UPD: 1, 1999-01-01, 2018-12-12 -> 6, 2000-01-01, 2018-01-01
> +<UPD: 1, 1999-01-01, 2018-12-12 -> 6, 2000-01-01, 2018-01-01
> +>INS: 1, 1999-01-01, 2000-01-01
> +<INS: 1, 1999-01-01, 2000-01-01
> +>INS: 1, 2018-01-01, 2018-12-12
> +<INS: 1, 2018-01-01, 2018-12-12
> +>UPD: 1, 1999-01-01, 2017-01-01 -> 6, 2000-01-01, 2017-01-01
> +<UPD: 1, 1999-01-01, 2017-01-01 -> 6, 2000-01-01, 2017-01-01
> +>INS: 1, 1999-01-01, 2000-01-01
> +<INS: 1, 1999-01-01, 2000-01-01
> +>UPD: 1, 2017-01-01, 2019-01-01 -> 6, 2017-01-01, 2018-01-01
> +<UPD: 1, 2017-01-01, 2019-01-01 -> 6, 2017-01-01, 2018-01-01
> +>INS: 1, 2018-01-01, 2019-01-01
> +<INS: 1, 2018-01-01, 2019-01-01
> +>UPD: 2, 1998-01-01, 2018-12-12 -> 7, 2000-01-01, 2018-01-01
> +<UPD: 2, 1998-01-01, 2018-12-12 -> 7, 2000-01-01, 2018-01-01
> +>INS: 2, 1998-01-01, 2000-01-01
> +<INS: 2, 1998-01-01, 2000-01-01
> +>INS: 2, 2018-01-01, 2018-12-12
> +<INS: 2, 2018-01-01, 2018-12-12
> +>UPD: 3, 1997-01-01, 2015-01-01 -> 8, 2000-01-01, 2015-01-01
> +<UPD: 3, 1997-01-01, 2015-01-01 -> 8, 2000-01-01, 2015-01-01
> +>INS: 3, 1997-01-01, 2000-01-01
> +<INS: 3, 1997-01-01, 2000-01-01
> +>UPD: 4, 2016-01-01, 2020-01-01 -> 9, 2016-01-01, 2018-01-01
> +<UPD: 4, 2016-01-01, 2020-01-01 -> 9, 2016-01-01, 2018-01-01
> +>INS: 4, 2018-01-01, 2020-01-01
> +<INS: 4, 2018-01-01, 2020-01-01
> +# INSERT trigger only also works
> +drop trigger tr1upd_t2;
> +drop trigger tr2upd_t2;
> +update t2 for portion of apptime from '2000-01-01' to '2018-01-01'
> +       set id=id + 5;
> +select * from t2 order by id, s, e;
> +id	s	e
> +1	1999-01-01	2000-01-01
> +1	1999-01-01	2000-01-01
> +1	2018-01-01	2018-12-12
> +1	2018-01-01	2019-01-01
> +2	1998-01-01	2000-01-01
> +2	2018-01-01	2018-12-12
> +3	1997-01-01	2000-01-01
> +4	2018-01-01	2020-01-01
> +6	2000-01-01	2017-01-01
> +6	2000-01-01	2018-01-01
> +6	2017-01-01	2018-01-01
> +7	2000-01-01	2018-01-01
> +8	2000-01-01	2015-01-01
> +9	2016-01-01	2018-01-01
> +select * from log_tbl;
> +log
> +>INS: 1, 1999-01-01, 2000-01-01
> +<INS: 1, 1999-01-01, 2000-01-01
> +>INS: 1, 2018-01-01, 2018-12-12
> +<INS: 1, 2018-01-01, 2018-12-12
> +>INS: 1, 1999-01-01, 2000-01-01
> +<INS: 1, 1999-01-01, 2000-01-01
> +>INS: 1, 2018-01-01, 2019-01-01
> +<INS: 1, 2018-01-01, 2019-01-01
> +>INS: 2, 1998-01-01, 2000-01-01
> +<INS: 2, 1998-01-01, 2000-01-01
> +>INS: 2, 2018-01-01, 2018-12-12
> +<INS: 2, 2018-01-01, 2018-12-12
> +>INS: 3, 1997-01-01, 2000-01-01
> +<INS: 3, 1997-01-01, 2000-01-01
> +>INS: 4, 2018-01-01, 2020-01-01
> +<INS: 4, 2018-01-01, 2020-01-01
> +select * from t for portion of apptime from 0 to 1 for system_time all;
> +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 '' at line 1

should be "right syntax to use near 'for portion of apptime from ...'"

> +update t for portion of apptime from 0 to 1 for system_time all set id=1;
> +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 'set id=1' at line 1

why not "right syntax to use near 'for system_time all ...'" ?

> +# Modifying period start/end fields is forbidden.
> +# SQL17:

fix the reference, please
(SQL:2016, Part 2, section ... paragraph ...)

> +# Neither BSTARTCOL nor BENDCOL shall be an explicit <object column>
> +# contained in the <set clause list>.
> +update t for portion of apptime from '2000-01-01' to '2018-01-01'
> +       set id= id + 5, s=subdate(s, 5), e=adddate(e, 5);
> +ERROR HY000: Column `s` used in period `apptime` specified in update SET list
> +# Precision timestamps
> +create or replace table t (id int, s timestamp(5), e timestamp(5),
> +period for apptime(s,e));
> +insert into t values(1, '1999-01-01', '2018-12-12');
> +insert into t values(1, '1999-01-01', '2017-01-01');
> +update t for portion of apptime from '2000-01-01 00:00:00.00015'
> +                                to '2018-01-01 12:34:56.31415'
> +         set id= id + 5;
> +select * from t;
> +id	s	e
> +6	2000-01-01 00:00:00.00015	2018-01-01 12:34:56.31415
> +6	2000-01-01 00:00:00.00015	2017-01-01 00:00:00.00000
> +1	1999-01-01 00:00:00.00000	2000-01-01 00:00:00.00015
> +1	2018-01-01 12:34:56.31415	2018-12-12 00:00:00.00000
> +1	1999-01-01 00:00:00.00000	2000-01-01 00:00:00.00015
> +# Strings
> +create or replace table t (id int, str text, s date, e date,
> +period for apptime(s,e));
> +insert into t values(1, 'data', '1999-01-01', '2018-12-12');
> +insert into t values(1, 'other data', '1999-01-01', '2018-12-12');
> +update t for portion of apptime from '2000-01-01' to '2018-01-01'
> +       set id= id + 5;
> +select * from t;
> +id	str	s	e
> +6	data	2000-01-01	2018-01-01
> +6	other data	2000-01-01	2018-01-01
> +1	data	1999-01-01	2000-01-01
> +1	data	2018-01-01	2018-12-12
> +1	other data	1999-01-01	2000-01-01
> +1	other data	2018-01-01	2018-12-12
> +# multi-table UPDATE is prohibited
> +create or replace table t1(x int);
> +update t for portion of apptime from '2000-01-01' to '2018-01-01', t1
> +set t.id= t.id + 5;
> +ERROR HY000: PORTION OF time is not allowed here

why not ER_PARSE_ERROR?

> +update t1 set x= (select id from t for portion of apptime from '2000-01-01' to '2018-01-01');
> +ERROR HY000: PORTION OF time is not allowed here

why not ER_PARSE_ERROR?

> +# SQL17:

fix the reference, please

> +# Let FROMVAL be <point in time 1>. FROMVAL shall not generally contain a
> +# reference to a column of T or a <routine invocation>
> +# whose subject routine is an SQL-invoked routine that
> +# is possibly non-deterministic or that possibly modifies SQL-data.
> +# ...Same for <point in time 2> (TOVAL)
> +update t for portion of apptime from 5*(5+s) to 1 set t.id= t.id + 5;
> +ERROR HY000: Values in range FOR PORTION OF `apptime` should be constant expressions
> +update t for portion of apptime from 1 to e set t.id= t.id + 5;
> +ERROR HY000: Values in range FOR PORTION OF `apptime` should be constant expressions
> +set @s= '2000-01-01';
> +set @e= '2018-01-01';
> +create or replace function f() returns date return @e;
> +create or replace function g() returns date not deterministic return @e;
> +create or replace function h() returns date deterministic return @e;
> +update t for portion of apptime from @s to f() set t.id= t.id + 5;
> +ERROR HY000: Values in range FOR PORTION OF `apptime` should be constant expressions
> +update t for portion of apptime from @s to g() set t.id= t.id + 5;
> +ERROR HY000: Values in range FOR PORTION OF `apptime` should be constant expressions
> +# success
> +update t for portion of apptime from @s to h() set t.id= t.id + 5;
> +# select value is cached
> +update t for portion of apptime from (select s from t2 limit 1) to h() set t.id= t.id + 5;
> +# auto_inrement field is updated
> +create or replace table t (id int primary key auto_increment, x int,
> +s date, e date, period for apptime(s, e));
> +insert into t values (default, 1, '1999-01-01', '2018-12-12');
> +update t for portion of apptime from '2000-01-01' to '2018-01-01' set x= x + 5;
> +select * from t;
> +id	x	s	e
> +1	6	2000-01-01	2018-01-01
> +2	1	1999-01-01	2000-01-01
> +3	1	2018-01-01	2018-12-12
> +truncate t;
> +insert into t values (default, 1, '1999-01-01', '2018-12-12');
> +update t for portion of apptime from '2000-01-01' to '2018-01-01' set x= 1;
> +select * from t;
> +id	x	s	e
> +1	1	2000-01-01	2018-01-01
> +2	1	1999-01-01	2000-01-01
> +3	1	2018-01-01	2018-12-12
> +# generated columns are updated
> +create or replace table t (x int, s date, e date,
> +xs date as (s) stored, xe date as (e) stored,
> +period for apptime(s, e));
> +insert into t values(1, '1999-01-01', '2018-12-12', default, default);
> +select * from t;
> +x	s	e	xs	xe
> +1	1999-01-01	2018-12-12	1999-01-01	2018-12-12
> +update t for portion of apptime from '2000-01-01' to '2018-01-01' set x= x + 5;
> +select *, xs=s and xe=e from t;
> +x	s	e	xs	xe	xs=s and xe=e
> +6	2000-01-01	2018-01-01	2000-01-01	2018-01-01	1
> +1	1999-01-01	2000-01-01	1999-01-01	2000-01-01	1
> +1	2018-01-01	2018-12-12	2018-01-01	2018-12-12	1
> +# system_time columns are updated
> +create or replace table t (x int, s date, e date,
> +row_start SYS_TYPE as row start invisible,
> +row_end SYS_TYPE as row end invisible,

a bit strange to use invisible in tests for strict standard compliance :)
but ok, whatever

> +period for apptime(s, e),
> +period for system_time(row_start, row_end)) with system versioning;
> +insert into t values(1, '1999-01-01', '2018-12-12'),
> +(2, '1999-01-01', '1999-12-12');
> +select row_start into @ins_time from t limit 1;
> +select * from t order by x, s, e;
> +x	s	e
> +1	1999-01-01	2018-12-12
> +2	1999-01-01	1999-12-12
> +update t for portion of apptime from '2000-01-01' to '2018-01-01' set x= x + 5;
> +select *, if(row_start = @ins_time, "OLD", "NEW"), check_row(row_start, row_end)
> +from t for system_time all
> +order by x, s, e, row_start;
> +x	s	e	if(row_start = @ins_time, "OLD", "NEW")	check_row(row_start, row_end)
> +1	1999-01-01	2000-01-01	NEW	CURRENT ROW
> +1	1999-01-01	2018-12-12	OLD	HISTORICAL ROW
> +1	2018-01-01	2018-12-12	NEW	CURRENT ROW
> +2	1999-01-01	1999-12-12	OLD	CURRENT ROW
> +6	2000-01-01	2018-01-01	NEW	CURRENT ROW
> +create or replace database test;
> diff --git a/mysql-test/suite/period/t/delete.test b/mysql-test/suite/period/t/delete.test
> index b34de6c37e7..39a68959523 100644
> --- a/mysql-test/suite/period/t/delete.test
> +++ b/mysql-test/suite/period/t/delete.test
> @@ -34,6 +34,10 @@ drop trigger tr2del_t2;
>  delete from t2 for portion of APPTIME from '2000-01-01' to '2018-01-01';
>  select * from log_tbl;
>  
> +--echo # multi-table DELETE is prohibited
> +--error ER_PORTION_OF_TIME_NOT_ALLOWED
> +delete t, t1 from t for portion of apptime from '2000-01-01' to '2018-01-01', t1;

move this into your MDEV-16973 commit is possible, please,

> +
>  --error ER_PARSE_ERROR
>  delete history from t2 for portion of apptime from '2000-01-01' to '2018-01-01';
>  
> diff --git a/mysql-test/suite/period/t/update.opt b/mysql-test/suite/period/t/update.opt
> new file mode 100644
> index 00000000000..92a881bef67
> --- /dev/null
> +++ b/mysql-test/suite/period/t/update.opt
> @@ -0,0 +1 @@
> +--explicit_defaults_for_timestamp

Why?
it's best to avoid non-default command line options, unless you actually
test this particular option. If you just need it for convenience - don't.

every non-default command line options means a server restart, and they're slow.

write DEFAULT NULL explicitly when needed.

> diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt
> index 7ceff1a215e..c5a359b09e8 100644
> --- a/sql/share/errmsg-utf8.txt
> +++ b/sql/share/errmsg-utf8.txt
> @@ -7951,3 +7951,12 @@ ER_PERIOD_NOT_FOUND
>  
>  ER_PERIOD_SYSTEM_TIME_NOT_ALLOWED
>          eng "period SYSTEM_TIME is not allowed here"
> +
> +ER_PORTION_OF_TIME_NOT_ALLOWED
> +        eng "PORTION OF time is not allowed here"
> +
> +ER_PERIOD_PORTION_OF_TIME_CONSTANT
> +        eng "Values in range FOR PORTION OF %`s should be constant expressions"

Reuse ER_NO_CONST_EXPR_IN_RANGE_OR_LIST_ERROR please. Like

ER_NO_CONST_EXPR_IN_RANGE_OR_LIST_ERROR -> ER_NOT_CONSTANT_EXPRESSION
eng "Expression in RANGE/LIST VALUES must be constant" ->
eng "Expression in %s must be constant"

and in mysql.h

#define ER_NO_CONST_EXPR_IN_RANGE_OR_LIST_ERROR ER_NOT_CONSTANT_EXPRESSION

> +
> +ER_PERIOD_COLUMNS_UPDATED
> +        eng "Column %`s used in period %`s specified in update SET list"
> diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc
> index 7a18ac82236..93d2f417a50 100644
> --- a/sql/sql_lex.cc
> +++ b/sql/sql_lex.cc
> @@ -1343,13 +1343,16 @@ int Lex_input_stream::lex_token(YYSTYPE *yylval, THD *thd)
>      /*
>       * Additional look-ahead to resolve doubtful cases like:
>       * SELECT ... FOR UPDATE
> -     * SELECT ... FOR SYSTEM_TIME ... .
> +     * SELECT ... FOR SYSTEM_TIME ...
> +     * SELECT ... FOR PORTION OF  ... .

Can you show an example of the statement where you need a second look-ahead
to distinguish between FOR PORTION OF and FOR SYSTEM_TIME or FOR UPDATE ?

>       */
>      token= lex_one_token(yylval, thd);
>      add_digest_token(token, yylval);
>      switch(token) {
>      case SYSTEM_TIME_SYM:
>        return FOR_SYSTEM_TIME_SYM;
> +    case PORTION:
> +      return FOR_PORTION_SYM;
>      default:
>        /*
>          Save the token following 'FOR_SYM'
> diff --git a/sql/sql_base.cc b/sql/sql_base.cc
> index 220fcf6cc34..44989b20fae 100644
> --- a/sql/sql_base.cc
> +++ b/sql/sql_base.cc
> @@ -8121,6 +8121,12 @@ int setup_conds(THD *thd, TABLE_LIST *tables, List<TABLE_LIST> &leaves,
>  
>    for (table= tables; table; table= table->next_local)
>    {
> +    if (table->has_period() && !thd->lex->portion_of_time_applicable())
> +    {
> +      my_error(ER_PORTION_OF_TIME_NOT_ALLOWED, MYF(0));
> +      goto err_no_arena;
> +    }

I don't understand that either, why do you need to check the correct syntax
in setup_conds() ?

> +
>      if (select_lex == thd->lex->first_select_lex() &&
>          select_lex->first_cond_optimization &&
>          table->merged_for_insert &&
> diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc
> index e76eb729536..4726557d762 100644
> --- a/sql/sql_delete.cc
> +++ b/sql/sql_delete.cc
> @@ -752,6 +752,10 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
>      delete_record= true;
>    }
>  
> +  /* SQL standard defines DELETE FOR PORTTION OF time as 0-2 INSERTS + DELETE
> +   * We can substitute INSERT+DELETE with one UPDATE, but only if there are
> +   * no triggers set. It is also meaningless for system-versioned table
> +   */

1. format multi-line comments like everywhere in the code, please
2. every time you refer to the standard, say where the standard says that
3. move this comment in your MDEV-16973 commit, if possible

>    portion_of_time_through_update= !has_period_triggers
>                                    && !table->versioned(VERS_TIMESTAMP);
>  
> diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
> index 0c368b40836..10535326ce5 100644
> --- a/sql/sql_yacc.yy
> +++ b/sql/sql_yacc.yy
> @@ -11910,12 +11911,13 @@ join_table_parens:
>  
>  
>  table_primary_ident:
> -          table_ident opt_use_partition opt_for_system_time_clause
> +          table_ident opt_use_partition
> +          opt_for_portion_of_time_clause opt_for_system_time_clause

Oh, no. FOR PORTION OF has very few well defined places where the standard
allows it. Don't put it into the generic used-everywhere table_primary_ident.

That's why you needed FOR_PORTION_SYM and a second look-ahead.

>            opt_table_alias_clause opt_key_definition
>            {
>              SELECT_LEX *sel= Select;
>              sel->table_join_options= 0;
> -            if (!($$= Select->add_table_to_list(thd, $1, $4,
> +            if (!($$= Select->add_table_to_list(thd, $1, $5,
>                                                  Select->get_table_join_options(),
>                                                  YYPS->m_lock_type,
>                                                  YYPS->m_mdl_type,
> diff --git a/sql/table.h b/sql/table.h
> index 1b897c1b4c6..f104f4fc99c 100644
> --- a/sql/table.h
> +++ b/sql/table.h
> @@ -2420,7 +2423,7 @@ struct TABLE_LIST
>  
>    bool has_period() const
>    {
> -    return period_conditions.name;
> +    return period_conditions.is_set();

why?

>    }
>  
>    /**
> 
> diff --git a/sql/table.cc b/sql/table.cc
> index 8f1c94dd633..93908a9e2e3 100644
> --- a/sql/table.cc
> +++ b/sql/table.cc
> @@ -6456,7 +6456,7 @@ void TABLE::mark_columns_needed_for_delete(bool with_period)
>      retrieve the row again.
>  */
>  
> -void TABLE::mark_columns_needed_for_update()
> +void TABLE::mark_columns_needed_for_update(bool with_period)

just like in the delete case, I think this is the caller's problem

>  {
>    DBUG_ENTER("TABLE::mark_columns_needed_for_update");
>    bool need_signal= false;
> @@ -7870,19 +7871,41 @@ static int period_make_insert(TABLE *table, Item *src, Field *dst)
>  }
>  
> +int TABLE::cut_fields_for_portion_of_time(THD *thd, const vers_select_conds_t &period_conds)

looks like something that belongs to sql_update.cc, not TABLE

> +{
> +  bool lcond= period_conds.field_start->val_datetime_packed(thd)
> +              < period_conds.start.item->val_datetime_packed(thd);
> +  bool rcond= period_conds.field_end->val_datetime_packed(thd)
> +              > period_conds.end.item->val_datetime_packed(thd);
> +
> +  Field *start_field= field[s->period.start_fieldno];
> +  Field *end_field= field[s->period.end_fieldno];
> +
> +  int res= 0;
> +  if (lcond && !start_field->has_explicit_value())
> +    res= period_conds.start.item->save_in_field(start_field, true);
> +
> +  if (likely(!res) && rcond && !end_field->has_explicit_value())
> +    res= period_conds.end.item->save_in_field(end_field, true);
> +
> +  return res;
> +}
> +
> -int TABLE::update_portion_of_time(THD *thd, vers_select_conds_t &period_conds,
> +int TABLE::update_portion_of_time(THD *thd, const vers_select_conds_t &period_conds,
>                                    bool *inside_period)

looks like something that belongs to sql_delete.cc, not TABLE

>  {
>    bool lcond= period_conds.field_start->val_datetime_packed(thd)
> @@ -7917,7 +7940,8 @@ int TABLE::update_portion_of_time(THD *thd, vers_select_conds_t &period_conds,
>    return res;
>  }
>  
> -int TABLE::insert_portion_of_time(THD *thd, vers_select_conds_t &period_conds)
> +int TABLE::insert_portion_of_time(THD *thd, const vers_select_conds_t &period_conds,
> +                                  ha_rows &rows_inserted)

pointers or _constant_ references. Here, rows_inserted must be a pointer.

>  {
>    bool lcond= period_conds.field_start->val_datetime_packed(thd)
>                < period_conds.start.item->val_datetime_packed(thd);
> diff --git a/sql/sql_update.cc b/sql/sql_update.cc
> index 27bd6f04670..d5240f5524e 100644
> --- a/sql/sql_update.cc
> +++ b/sql/sql_update.cc
> @@ -177,6 +178,21 @@ static bool check_fields(THD *thd, List<Item> &items, bool update_view)
>        f->set_has_explicit_value();
>      }
>    }
> +
> +  if (thd->lex->sql_command == SQLCOM_UPDATE && table->has_period())

what else can sql_command be here? SQLCOM_UPDATE_MULTI?
SQLCOM_UPDATE_MULTI cannot have table->has_period().

> +  {
> +    for (List_iterator_fast<Item> it(items); (item=it++);)
> +    {
> +      Lex_ident name(item->name);
> +      vers_select_conds_t &period= table->period_conditions;
> +      if (name.streq(period.field_start->name)
> +          || name.streq(period.field_end->name))

Don't compare names, compare fields.
The field to be updated is, I guess, item->field_for_view_update()->field

> +      {
> +        my_error(ER_PERIOD_COLUMNS_UPDATED, MYF(0), name.str, period.name.str);
> +        return true;
> +      }
> +    }
> +  }
>    return FALSE;
>  }
>  
> @@ -323,6 +339,7 @@ int mysql_update(THD *thd,
>    List<Item> all_fields;
>    killed_state killed_status= NOT_KILLED;
>    bool has_triggers, binlog_is_row, do_direct_update= FALSE;
> +  bool has_period_triggers= false;

same as for sql_delete.cc, one has_triggers is enough, second has_period_triggers
is quite confusing.

>    Update_plan query_plan(thd->mem_root);
>    Explain_update *explain;
>    TABLE_LIST *update_source_table;
> @@ -880,14 +905,25 @@ int mysql_update(THD *thd,
>        explain->tracker.on_record_after_where();
>        store_record(table,record[1]);
>  
> +      if (table_list->has_period())
> +        table->cut_fields_for_portion_of_time(thd, table_list->period_conditions);

1. this should be done after BEFORE triggers
(see SQL:2016, part 2, 15.13 "Effect of replacing rows in base tables")

2. why in cut_fields_for_portion_of_time() you check for has_explicit_value()?
fields cannot have explicit values there, you've checked that in check_fields().

> +
>        if (fill_record_n_invoke_before_triggers(thd, table, fields, values, 0,
>                                                 TRG_EVENT_UPDATE))
>          break; /* purecov: inspected */
>  
>        found++;
>  
> -      if (!can_compare_record || compare_record(table))
> +      bool record_was_same= false;
> +      bool need_update= !can_compare_record || compare_record(table) ||
> +                        thd->lex->sql_command == SQLCOM_DELETE;

why sql_command would be SQLCOM_DELETE here?

> +
> +      if (need_update)
>        {
> +        if (table->versioned(VERS_TIMESTAMP) &&
> +            thd->lex->sql_command == SQLCOM_DELETE)
> +          table->vers_update_end();
> +
>          if (table->default_field && table->update_default_fields(1, ignore))
>          {
>            error= 1;
> @@ -994,6 +1032,13 @@ int mysql_update(THD *thd,
>          break;
>        }
>  
> +      if (need_update && !record_was_same && table_list->has_period())

I suspect this should happen even if record_was_same. The standard never says
"if new values are the same as old values, don't update anything"

> +      {
> +        restore_record(table, record[1]);
> +        table->insert_portion_of_time(thd, table_list->period_conditions,
> +                                      rows_inserted);
> +      }
> +
>        if (!--limit && using_limit)
>        {
>          /*

Regards,
Sergei
Chief Architect MariaDB
and security@xxxxxxxxxxx


Follow ups