• Dmitry Shulga's avatar
    MDEV-22805: SIGSEGV in check_fields on UPDATE · 97b10b7f
    Dmitry Shulga authored
    For debug build of MariaDB server running of the following test case
    will hit the assert `thd->lex->sql_command == SQLCOM_UPDATE' in the function
    check_fields() on attempt to execute the UPDATE statement.
    
      CREATE TABLE t1 (a INT);
      UPDATE t1 FOR PORTION OF APPTIME FROM (SELECT 1 FROM t1) TO 2 SET a = 1;
    
    Stack trace to the fired assert statement
      DBUG_ASSERT(thd->lex->sql_command == SQLCOM_UPDATE)
    listed below:
      mysql_execute_command() ->
        mysql_multi_update_prepare() -->
          Multiupdate_prelocking_strategy::handle_end() -->
            check_fiels()
    
    It's worth to note that this stack trace looks like a multi update
    statement is being executed. The fired assert is checked inside the
    function check_fields() in case table->has_period() returns the value
    true that in turns happens when temporal period specified in the UPDATE
    statement. Condition specified in the DEBUG_ASSERT statement returns
    the false value since the data member thd->lex->sql_command have the
    value SQLCOM_UPDATE_MULTI. So, the main question is why a program control
    flow go to the path prescribed for handling MULTI update statement
    despite of the fact that the ordinary UPDATE statement being executed.
    
    The answer is a way that SQL grammar rules written.
    
    When the statement
      UPDATE t1 FOR PORTION OF APPTIME FROM (SELECT 1 FROM t1) TO 2 SET a = 1;
    being parsed an action for the rule 'table_primary_ident' (part of this action
    is listed below to simplify description) is  invoked to handle the table
    name 't1' specified in the clause 'SELECT 1 FROM t1'.
    
    table_primary_ident:
      table_ident opt_use_partition opt_for_system_time_clause
      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,
    
    This action calls the method st_select_lex::add_table_to_list()
    to add the table name 't1' to the list of tables being used by the statement.
    
    Later, an action for the following grammar rule
    update_table_list:
      table_ident opt_use_partition for_portion_of_time_clause
      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,
    
    is invoked to handle the clause 't1 FOR PORTION OF APPTIME FROM ... TO 2'.
    This action also calls the method st_select_lex::add_table_to_list()
    to add the table name 't1' to the list of tables being used by the statement.
    
    In result the table name 't1' contained twice in this list.
    
    Presence of duplicate names for the table 't1' in a list of table used by
    a statement leads to the fact that the function unique_table() called
    from the function mysql_update() returns the value true that forces
    implementation of the function mysql_update() to return the value 2 as
    a signal to fall through the case boundary of the switch statement placed
    in the function mysql_execute_statement() and start handling of the case
    for sql_command SQLCOM_UPDATE_MULTI. The compound statement block for the
    case SQLCOM_UPDATE_MULTI invokes the function mysql_multi_update_prepare()
    that executes the statement
      set thd->lex->sql_command= SQLCOM_UPDATE_MULTI;
    and after that calls the method
      Multiupdate_prelocking_strategy::handle_end(). Finally, this method
    invokes the check_field() function and assert is fired.
    
    The above analysis shows that update for a table that simultaneously specified
    both as a destination table of UPDATE statement and as a table taking part in
    subquery is actually treated by MariaDB server as multi-update statement.
    Taking into account that multi-update statement for temporal period
    table is not supported yet by MariaDB, correct way to fix the bug is to return
    the error ER_NOT_SUPPORTED_YET for this case.
    97b10b7f
sql_update.cc 95.7 KB