Details
-
Task
-
Status: Stalled (View Workflow)
-
Major
-
Resolution: Unresolved
-
None
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;
|
}
|