mirror of
https://github.com/MariaDB/server.git
synced 2025-08-01 03:47:19 +03:00
MDEV-28883 Re-design the upper level of handling UPDATE and DELETE statements
This patch introduces a new way of handling UPDATE and DELETE commands at the top level after the parsing phase. This new way of processing update and delete statements can be seen in the implementation of the prepare() and execute() methods from the new Sql_cmd_dml class. This class derived from the Sql_cmd class can be considered as an interface class for processing such commands as SELECT, INSERT, UPDATE, DELETE and other comands manipulating data in tables. With this patch processing of update and delete statements after parsing proceeds by the following schema: - precheck of the access rights is performed for the used tables - the used tables are opened - context analysis phase is performed for the statement - the used tables are locked - the statement is optimized and executed - clean-up is performed for the statement The implementation of the method Sql_cmd_dml::execute() adheres this schema. The virtual functions of the class Sql_cmd_dml used for precheck of the access rights, context analysis, optimization and execution allow to adjust this schema for processing data manipulation statements of any types. This schema of processing data manipulation statements is taken from the current MySQL code. Moreover the definition the class Sql_cmd_dml introduced in this patch is almost a full replica of such class in the existing MySQL. However the implementation of the derived classes for update and delete statements is quite different. This implementation employs the JOIN class for all kinds of update and delete statements. It allows to perform main bulk of context analysis actions by the function JOIN::prepare(). This guarantees that characteristics and properties of the statement tree discovered for optimization phase when doing context analysis are the same for single-table and multi-table updates and deletes. With this patch the following functions are gone: mysql_prepare_update(), mysql_multi_update_prepare(), mysql_update(), mysql_multi_update(), mysql_prepare_delete(), mysql_multi_delete_prepare(), mysql_delete(). The code within these functions have been used as much as possible though. The functions mysql_test_update() and mysql_test_delete() are also not needed anymore. The method Sql_cmd_dml::prepare() serves processing - update/delete statement - PREPARE stmt FROM "<update/delete statement>" - EXECUTE stmt when stmt is prepared from update/delete statement. Approved by Oleksandr Byelkin <sanja@mariadb.com>
This commit is contained in:
261
sql/sql_parse.cc
261
sql/sql_parse.cc
@ -34,9 +34,7 @@
|
||||
#include "sql_locale.h" // my_locale_en_US
|
||||
#include "log.h" // flush_error_log
|
||||
#include "sql_view.h" // mysql_create_view, mysql_drop_view
|
||||
#include "sql_delete.h" // mysql_delete
|
||||
#include "sql_insert.h" // mysql_insert
|
||||
#include "sql_update.h" // mysql_update, mysql_multi_update
|
||||
#include "sql_partition.h" // struct partition_info
|
||||
#include "sql_db.h" // mysql_change_db, mysql_create_db,
|
||||
// mysql_rm_db, mysql_upgrade_db,
|
||||
@ -3445,7 +3443,6 @@ int
|
||||
mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt)
|
||||
{
|
||||
int res= 0;
|
||||
int up_result= 0;
|
||||
LEX *lex= thd->lex;
|
||||
/* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */
|
||||
SELECT_LEX *select_lex= lex->first_select_lex();
|
||||
@ -3457,7 +3454,6 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt)
|
||||
SELECT_LEX_UNIT *unit= &lex->unit;
|
||||
#ifdef HAVE_REPLICATION
|
||||
/* have table map for update for multi-update statement (BUG#37051) */
|
||||
bool have_table_map_for_update= FALSE;
|
||||
/* */
|
||||
Rpl_filter *rpl_filter;
|
||||
#endif
|
||||
@ -3579,7 +3575,6 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt)
|
||||
if (lex->sql_command == SQLCOM_UPDATE_MULTI &&
|
||||
thd->table_map_for_update)
|
||||
{
|
||||
have_table_map_for_update= TRUE;
|
||||
table_map table_map_for_update= thd->table_map_for_update;
|
||||
uint nr= 0;
|
||||
TABLE_LIST *table;
|
||||
@ -4388,130 +4383,15 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt)
|
||||
break;
|
||||
}
|
||||
case SQLCOM_UPDATE:
|
||||
{
|
||||
WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE);
|
||||
ha_rows found= 0, updated= 0;
|
||||
DBUG_ASSERT(first_table == all_tables && first_table != 0);
|
||||
WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE);
|
||||
|
||||
if (update_precheck(thd, all_tables))
|
||||
break;
|
||||
|
||||
/*
|
||||
UPDATE IGNORE can be unsafe. We therefore use row based
|
||||
logging if mixed or row based logging is available.
|
||||
TODO: Check if the order of the output of the select statement is
|
||||
deterministic. Waiting for BUG#42415
|
||||
*/
|
||||
if (lex->ignore)
|
||||
lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_UPDATE_IGNORE);
|
||||
|
||||
DBUG_ASSERT(select_lex->limit_params.offset_limit == 0);
|
||||
unit->set_limit(select_lex);
|
||||
MYSQL_UPDATE_START(thd->query());
|
||||
res= up_result= mysql_update(thd, all_tables,
|
||||
select_lex->item_list,
|
||||
lex->value_list,
|
||||
select_lex->where,
|
||||
select_lex->order_list.elements,
|
||||
select_lex->order_list.first,
|
||||
unit->lim.get_select_limit(),
|
||||
lex->ignore, &found, &updated);
|
||||
MYSQL_UPDATE_DONE(res, found, updated);
|
||||
/* mysql_update return 2 if we need to switch to multi-update */
|
||||
if (up_result != 2)
|
||||
break;
|
||||
if (thd->lex->period_conditions.is_set())
|
||||
{
|
||||
DBUG_ASSERT(0); // Should never happen
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
/* fall through */
|
||||
case SQLCOM_UPDATE_MULTI:
|
||||
case SQLCOM_DELETE:
|
||||
case SQLCOM_DELETE_MULTI:
|
||||
{
|
||||
DBUG_ASSERT(first_table == all_tables && first_table != 0);
|
||||
/* if we switched from normal update, rights are checked */
|
||||
if (up_result != 2)
|
||||
{
|
||||
WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE);
|
||||
if ((res= multi_update_precheck(thd, all_tables)))
|
||||
break;
|
||||
}
|
||||
else
|
||||
res= 0;
|
||||
DBUG_ASSERT(lex->m_sql_cmd != NULL);
|
||||
|
||||
unit->set_limit(select_lex);
|
||||
/*
|
||||
We can not use mysql_explain_union() because of parameters of
|
||||
mysql_select in mysql_multi_update so just set the option if needed
|
||||
*/
|
||||
if (thd->lex->describe)
|
||||
{
|
||||
select_lex->set_explain_type(FALSE);
|
||||
select_lex->options|= SELECT_DESCRIBE;
|
||||
}
|
||||
|
||||
res= mysql_multi_update_prepare(thd);
|
||||
|
||||
#ifdef HAVE_REPLICATION
|
||||
/* Check slave filtering rules */
|
||||
if (unlikely(thd->slave_thread && !have_table_map_for_update))
|
||||
{
|
||||
if (all_tables_not_ok(thd, all_tables))
|
||||
{
|
||||
if (res!= 0)
|
||||
{
|
||||
res= 0; /* don't care of prev failure */
|
||||
thd->clear_error(); /* filters are of highest prior */
|
||||
}
|
||||
/* we warn the slave SQL thread */
|
||||
my_error(ER_SLAVE_IGNORED_TABLE, MYF(0));
|
||||
break;
|
||||
}
|
||||
if (res)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
#endif /* HAVE_REPLICATION */
|
||||
if (res)
|
||||
break;
|
||||
if (opt_readonly &&
|
||||
!(thd->security_ctx->master_access & PRIV_IGNORE_READ_ONLY) &&
|
||||
some_non_temp_table_to_be_updated(thd, all_tables))
|
||||
{
|
||||
my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "--read-only");
|
||||
break;
|
||||
}
|
||||
#ifdef HAVE_REPLICATION
|
||||
} /* unlikely */
|
||||
#endif
|
||||
{
|
||||
multi_update *result_obj;
|
||||
MYSQL_MULTI_UPDATE_START(thd->query());
|
||||
res= mysql_multi_update(thd, all_tables,
|
||||
&select_lex->item_list,
|
||||
&lex->value_list,
|
||||
select_lex->where,
|
||||
select_lex->options,
|
||||
lex->duplicates,
|
||||
lex->ignore,
|
||||
unit,
|
||||
select_lex,
|
||||
&result_obj);
|
||||
if (result_obj)
|
||||
{
|
||||
MYSQL_MULTI_UPDATE_DONE(res, result_obj->num_found(),
|
||||
result_obj->num_updated());
|
||||
res= FALSE; /* Ignore errors here */
|
||||
delete result_obj;
|
||||
}
|
||||
else
|
||||
{
|
||||
MYSQL_MULTI_UPDATE_DONE(1, 0, 0);
|
||||
}
|
||||
}
|
||||
res = lex->m_sql_cmd->execute(thd);
|
||||
thd->abort_on_warning= 0;
|
||||
break;
|
||||
}
|
||||
case SQLCOM_REPLACE:
|
||||
@ -4773,129 +4653,6 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt)
|
||||
|
||||
break;
|
||||
}
|
||||
case SQLCOM_DELETE:
|
||||
{
|
||||
WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE);
|
||||
select_result *sel_result= NULL;
|
||||
DBUG_ASSERT(first_table == all_tables && first_table != 0);
|
||||
WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE);
|
||||
|
||||
if ((res= delete_precheck(thd, all_tables)))
|
||||
break;
|
||||
DBUG_ASSERT(select_lex->limit_params.offset_limit == 0);
|
||||
unit->set_limit(select_lex);
|
||||
|
||||
MYSQL_DELETE_START(thd->query());
|
||||
Protocol *save_protocol= NULL;
|
||||
|
||||
if (lex->has_returning())
|
||||
{
|
||||
/* This is DELETE ... RETURNING. It will return output to the client */
|
||||
if (thd->lex->analyze_stmt)
|
||||
{
|
||||
/*
|
||||
Actually, it is ANALYZE .. DELETE .. RETURNING. We need to produce
|
||||
output and then discard it.
|
||||
*/
|
||||
sel_result= new (thd->mem_root) select_send_analyze(thd);
|
||||
save_protocol= thd->protocol;
|
||||
thd->protocol= new Protocol_discard(thd);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!lex->result && !(sel_result= new (thd->mem_root) select_send(thd)))
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
res = mysql_delete(thd, all_tables,
|
||||
select_lex->where, &select_lex->order_list,
|
||||
unit->lim.get_select_limit(), select_lex->options,
|
||||
lex->result ? lex->result : sel_result);
|
||||
|
||||
if (save_protocol)
|
||||
{
|
||||
delete thd->protocol;
|
||||
thd->protocol= save_protocol;
|
||||
}
|
||||
|
||||
if (thd->lex->analyze_stmt || thd->lex->describe)
|
||||
{
|
||||
if (!res)
|
||||
res= thd->lex->explain->send_explain(thd);
|
||||
}
|
||||
|
||||
delete sel_result;
|
||||
MYSQL_DELETE_DONE(res, (ulong) thd->get_row_count_func());
|
||||
break;
|
||||
}
|
||||
case SQLCOM_DELETE_MULTI:
|
||||
{
|
||||
WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE);
|
||||
DBUG_ASSERT(first_table == all_tables && first_table != 0);
|
||||
TABLE_LIST *aux_tables= thd->lex->auxiliary_table_list.first;
|
||||
multi_delete *result;
|
||||
WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_UPDATE_DELETE);
|
||||
|
||||
if ((res= multi_delete_precheck(thd, all_tables)))
|
||||
break;
|
||||
|
||||
/* condition will be TRUE on SP re-excuting */
|
||||
if (select_lex->item_list.elements != 0)
|
||||
select_lex->item_list.empty();
|
||||
if (add_item_to_list(thd, new (thd->mem_root) Item_null(thd)))
|
||||
goto error;
|
||||
|
||||
THD_STAGE_INFO(thd, stage_init);
|
||||
if ((res= open_and_lock_tables(thd, all_tables, TRUE, 0)))
|
||||
break;
|
||||
|
||||
MYSQL_MULTI_DELETE_START(thd->query());
|
||||
if (unlikely(res= mysql_multi_delete_prepare(thd)))
|
||||
{
|
||||
MYSQL_MULTI_DELETE_DONE(1, 0);
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (likely(!thd->is_fatal_error))
|
||||
{
|
||||
result= new (thd->mem_root) multi_delete(thd, aux_tables,
|
||||
lex->table_count_update);
|
||||
if (likely(result))
|
||||
{
|
||||
if (unlikely(select_lex->vers_setup_conds(thd, aux_tables)))
|
||||
goto multi_delete_error;
|
||||
res= mysql_select(thd,
|
||||
select_lex->get_table_list(),
|
||||
select_lex->item_list,
|
||||
select_lex->where,
|
||||
0, (ORDER *)NULL, (ORDER *)NULL, (Item *)NULL,
|
||||
(ORDER *)NULL,
|
||||
(select_lex->options | thd->variables.option_bits |
|
||||
SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK |
|
||||
OPTION_SETUP_TABLES_DONE) & ~OPTION_BUFFER_RESULT,
|
||||
result, unit, select_lex);
|
||||
res|= (int)(thd->is_error());
|
||||
|
||||
MYSQL_MULTI_DELETE_DONE(res, result->num_deleted());
|
||||
if (res)
|
||||
result->abort_result_set(); /* for both DELETE and EXPLAIN DELETE */
|
||||
else
|
||||
{
|
||||
if (lex->describe || lex->analyze_stmt)
|
||||
res= thd->lex->explain->send_explain(thd);
|
||||
}
|
||||
multi_delete_error:
|
||||
delete result;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
res= TRUE; // Error
|
||||
MYSQL_MULTI_DELETE_DONE(1, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SQLCOM_DROP_SEQUENCE:
|
||||
case SQLCOM_DROP_TABLE:
|
||||
{
|
||||
@ -7733,12 +7490,16 @@ void create_select_for_variable(THD *thd, LEX_CSTRING *var_name)
|
||||
}
|
||||
|
||||
|
||||
void mysql_init_multi_delete(LEX *lex)
|
||||
void mysql_init_delete(LEX *lex)
|
||||
{
|
||||
lex->sql_command= SQLCOM_DELETE_MULTI;
|
||||
lex->init_select();
|
||||
lex->first_select_lex()->limit_params.clear();
|
||||
lex->unit.lim.clear();
|
||||
}
|
||||
|
||||
void mysql_init_multi_delete(LEX *lex)
|
||||
{
|
||||
lex->sql_command= SQLCOM_DELETE_MULTI;
|
||||
lex->first_select_lex()->table_list.
|
||||
save_and_clear(&lex->auxiliary_table_list);
|
||||
lex->query_tables= 0;
|
||||
|
Reference in New Issue
Block a user