← Back to team overview

maria-developers team mailing list archive

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

 

Hi, Sergei!

Jan 9, 2019 06:09, Sergei Golubchik <serg@xxxxxxxxxxx>:

>
> > 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?
>
> Resolved in DELETE commit
 ✅

>  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').
>
> Ok. I've also changed id= id+5 to id= id+6 in relevant places, to keep the
result set disjoint with original values
✅

> +select * from t;
>
> same comment about unsorted selects
>
> Added. ✅

> +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 ...'"
>
> I've got rid of using table_primary_ident, as You suggested below, which
resolves it
✅

> +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 ...'" ?
>
>  same as above✅

> +# 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>.

✅


> > +# 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?
>
> It's  ER_PARSE_ERROR now ✅

> +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?
>
>   It's  ER_PARSE_ERROR now ✅

> +# 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)

✅

> +# 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
>
>  Not exactly:) it's a test for crossover with versioning, which is not so
strictly compilant, especially trx_id feature

> +--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,
>
> moved ✅

> --- /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.
>
> that's because DEFAULT NOW ON UPDATE NOW is implicitly added to TIMESTAMP
fields.
I did not know what to do with it first, so created MDEV-17094 to decide
what to do with it later.
Now I know -- implicit DNUN just should not be added to period fields.
I'll make a separate commit related to that task, if You don't mind

> 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
>
> This error seems to be unused... Considering that, do You still want the
macro added to mysql.h?


> >      /*
> >       * 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 ?
>
> SELECT * FROM t FOR UPDATE was just failing to parse, but that's not
appropriate anymore since latest parser changes.
This code is removed✅

> >    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() ?
>
> Same here. Hunk removed.✅


> > 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
>
> done✅


> > 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.
>
> Yes, thanks. I've reimplemented the parser part to distinguish table
definitions and removed all that lexer code.

>            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?
>
> Not a big difference, actually. Alexey just wanted is_set to be reused
here.
Rebased it into delete.


> > -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
>
>  moved✅


> > -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
>
> ✅


> > -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().
>
> Yes, it was just to make sure it's true. Extracted to DBUG_ASSERT


> > +  {
> > +    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
>
>  Seems to be working, thanks!
Updated ✅

> @@ -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.
>
> Removed  has_period_triggers ✅

> >    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")
>
> Disagree. Pls take a loot at 14.14 <update statement: searched> 7)a)vi)
❗

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().
>
> Seems to be left from the time I tried to implement using explicit values
as an extension to the standard.
Extracted them to DBUG_ASSERT.
✅

BTW, maybe we can run CHECK constraints in DBUG_ASSERT in
table_update_generated_fields
also.
...Added; let's see how will it stay.


> > -      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?
>
> Can't be in this branch. Orphan; removed✅


> > +      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"
>
> Yes, looks like it never says so. And it was implemented in that way.
Those two checks --  need_update && !record_was_same -- could be safely
omitted, they don't change the behavior.
Because in that way lcond and rcond are false, anyway.

Maybe it's better to remove them -- the optimization is quite arguable
here. Up to You, ok?



Thanks for the review!

Nikita Malyavin

Follow ups

References