[MDEV-27159] Re-design the upper level of handling DML commands Created: 2021-12-03  Updated: 2024-01-30

Status: Stalled
Project: MariaDB Server
Component/s: Data Manipulation - Delete, Data Manipulation - Insert, Data Manipulation - Update
Fix Version/s: 11.6

Type: Task Priority: Major
Reporter: Igor Babaev Assignee: Igor Babaev
Resolution: Unresolved Votes: 1
Labels: None

Sub-Tasks:
Key
Summary
Type
Status
Assignee
MDEV-28883 Re-design the upper level of handling... Technical task Closed Igor Babaev  
MDEV-29310 Re-design the upper level of handling... Technical task Stalled Igor Babaev  

 Description   

The main goal of this task is to re-design the top level for UPDATE/DELETE/INSERT statements to follow the same approach of handling these statements as MySQL 8.0. With this approach DML statements are processed by the methods 'prepare' and execute of the class Sql_cmd_dml derived from the class Sql_com that already exists
in MariaDB.
Here's the declaration of the class Sql_cmd_dml:

 class Sql_cmd_dml : public Sql_cmd
{
public:
  /// @return true if data change statement, false if not (SELECT statement)
  virtual bool is_data_change_stmt() const { return true; }
 
  /**
    Command-specific resolving (doesn't include LEX::prepare())
 
    @param thd  Current THD.
    @returns false on success, true on error
  */
  virtual bool prepare(THD *thd);
 
  /**
    Execute this query once
 
    @param thd Thread handler
    @returns false on success, true on error
  */
  virtual bool execute(THD *thd);
 
  virtual bool is_dml() const { return true; }
 
protected:
  Sql_cmd_dml()
      : Sql_cmd(), lex(nullptr), result(nullptr), m_empty_query(false) {}
 
  /// @return true if query is guaranteed to return no data
  /**
    @todo Also check this for the following cases:
          - Empty source for multi-table UPDATE and DELETE.
          - Check empty query expression for INSERT
  */
  bool is_empty_query() const
  {
    DBUG_ASSERT(is_prepared());
    return m_empty_query;
  }
 
  /// Set statement as returning no data
  void set_empty_query() { m_empty_query = true; }
 
  /**
    Perform a precheck of table privileges for the specific operation.
 
    @details
    Check that user has some relevant privileges for all tables involved in
    the statement, e.g. SELECT privileges for tables selected from, INSERT
    privileges for tables inserted into, etc. This function will also populate
    TABLE_LIST::grant with all privileges the user has for each table, which
    is later used during checking of column privileges.
    Note that at preparation time, views are not expanded yet. Privilege
    checking is thus rudimentary and must be complemented with later calls to
    SELECT_LEX::check_view_privileges().
    The reason to call this function at such an early stage is to be able to
    quickly reject statements for which the user obviously has insufficient
    privileges.
 
    @param thd thread handler
    @returns false if success, true if false
  */
  virtual bool precheck(THD *thd) = 0;
 
  /**
    Perform the command-specific parts of DML command preparation,
    to be called from prepare()
 
    @param thd the current thread
    @returns false if success, true if error
  */
  virtual bool prepare_inner(THD *thd) = 0;
 
  /**
    The inner parts of query optimization and execution.
    Single-table DML operations needs to reimplement this.
 
    @param thd Thread handler
    @returns false on success, true on error
  */
  virtual bool execute_inner(THD *thd);
 
  virtual DML_prelocking_strategy *get_dml_prelocking_strategy() = 0;
 
   uint table_count;
 
 protected:
  LEX *lex;              ///< Pointer to LEX for this statement
  select_result *result; ///< Pointer to object for handling of the result
  bool m_empty_query;    ///< True if query will produce no rows
};

The implementations of the virtual functions

 virtual bool precheck(THD *thd) = 0;
 virtual bool prepare_inner(THD *thd) = 0;
 virtual bool execute_inner(THD *thd);

will be different for different types of statements.

The method Sql_cmd_dml::execute execute a DML statement.

bool Sql_cmd_dml::execute(THD *thd)
{
  lex = thd->lex;
  bool res;
 
  SELECT_LEX_UNIT *unit = &lex->unit;
  SELECT_LEX *select_lex= lex->first_select_lex();
 
  if (!is_prepared())
  {
    if (prepare(thd))
       goto err;
  }
  else
  {
    if (precheck(thd))
      goto err;
    if (open_tables_for_query(thd, lex->query_tables, &table_count, 0,
                              get_dml_prelocking_strategy()))
      goto err;
  }
 
  THD_STAGE_INFO(thd, stage_init);
 
  DBUG_ASSERT(!lex->is_query_tables_locked());
  /*
    Locking of tables is done after preparation but before optimization.
    This allows to do better partition pruning and avoid locking unused
    partitions. As a consequence, in such a case, prepare stage can rely only
    on metadata about tables used and not data from them.
  */
  if (!is_empty_query())
  {
    if (lock_tables(thd, lex->query_tables, table_count, 0))
      goto err;
  }
 
  unit->set_limit(select_lex);
 
  // Perform statement-specific execution
  res = execute_inner(thd);
 
  if (res)
    goto err;
 
  // "unprepare" this object since unit->cleanup actually unprepares
  unprepare(thd);
 
  THD_STAGE_INFO(thd, stage_end);
 
  return res;
 
err:
  DBUG_ASSERT(thd->is_error() || thd->killed);
  THD_STAGE_INFO(thd, stage_end);
  (void)unit->cleanup();
 
  return thd->is_error();
}

The method Sql_cmd_dml::prepare performs the prepare phase.

bool Sql_cmd_dml::prepare(THD *thd)
{
  lex= thd->lex;
  SELECT_LEX_UNIT *unit= &lex->unit;
 
  DBUG_ASSERT(!is_prepared());
 
  // Perform a coarse statement-specific privilege check.
  if (precheck(thd))
     goto err;
 
  lex->context_analysis_only|= CONTEXT_ANALYSIS_ONLY_DERIVED;
 
  if (open_tables_for_query(thd, lex->query_tables, &table_count, 0,
                            get_dml_prelocking_strategy()))
  {
    if (thd->is_error())
      goto err;
    (void)unit->cleanup();
    return true;
  }
 
  if (prepare_inner(thd))
    goto err;
 
  lex->context_analysis_only&= ~CONTEXT_ANALYSIS_ONLY_DERIVED;
 
  set_prepared();
  unit->set_prepared();
 
  return false;
 
err:
  DBUG_ASSERT(thd->is_error());
  DBUG_PRINT("info", ("report_error: %d", thd->is_error()));
 
  (void)unit->cleanup();
 
  return true;
}


Generated at Thu Feb 08 09:50:47 UTC 2024 using Jira 8.20.16#820016-sha1:9d11dbea5f4be3d4cc21f03a88dd11d8c8687422.