mirror of
https://github.com/MariaDB/server.git
synced 2025-08-08 11:22:35 +03:00
MDEV-34724: Skipping a row operation from a trigger
Implementation of this task adds ability to raise the signal with SQLSTATE '02TRG' from a BEFORE INSERT/UPDATE/DELETE trigger and handles this signal as an indicator meaning 'to throw away the current row' on processing the INSERT/UPDATE/DELETE statement. The signal with SQLSTATE '02TRG' has special meaning only in case it is raised inside BEFORE triggers, for AFTER trigger's this value of SQLSTATE isn't treated in any special way. In according with SQL standard, the SQLSTATE class '02' means NO DATA and sql_errno for this class is set to value ER_SIGNAL_NOT_FOUND by current implementation of MariaDB server. Implementation of this task assigns the value ER_SIGNAL_SKIP_ROW_FROM_TRIGGER to sql_errno in Diagnostics_area in case the signal is raised from a trigger and SQLSTATE has value '02TRG'. To catch signal with SQLTSATE '02TRG' and handle it in special way, the methods Table_triggers_list::process_triggers select_insert::store_values select_create::store_values Rows_log_event::process_triggers and the overloaded function fill_record_n_invoke_before_triggers were extended with extra out parameter for returning the flag whether to skip the current values being processed by INSERT/UPDATE/DELETE statement. This extra parameter is passed as nullptr in case of AFTER trigger and BEFORE trigger this parameter points to a variable to store a marker whether to skip the current record or store it by calling write_record().
This commit is contained in:
@@ -2568,6 +2568,41 @@ end:
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Check that a BEFORE trigger has raised the signal to inform that
|
||||
a current row being processed must be skipped.
|
||||
|
||||
@param da Diagnostics area
|
||||
@param[out] skip_row_indicator where to store the fact about skipping
|
||||
the row
|
||||
@param time_type time when trigger is invoked (i.e. before or
|
||||
after)
|
||||
|
||||
@return true in case the current row must be skipped, else false
|
||||
*/
|
||||
|
||||
static inline bool do_skip_row_indicator(Diagnostics_area *da,
|
||||
bool *skip_row_indicator,
|
||||
trg_action_time_type time_type)
|
||||
{
|
||||
if (!skip_row_indicator)
|
||||
return false;
|
||||
|
||||
if (time_type == TRG_ACTION_BEFORE &&
|
||||
/*
|
||||
The '02' class signals a 'no data' condition, the subclass '02TRG'
|
||||
means 'no data in trigger' and this condition shouldn't be treated
|
||||
as an error.
|
||||
*/
|
||||
strcmp(da->get_sqlstate(), "02TRG") == 0)
|
||||
{
|
||||
*skip_row_indicator= true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Execute trigger for given (event, time) pair.
|
||||
|
||||
@@ -2578,6 +2613,8 @@ end:
|
||||
@param event
|
||||
@param time_type
|
||||
@param old_row_is_record1
|
||||
@param[out] skip_row_indicator the flag to tell whether a row must be
|
||||
skipped by the INSERT statement
|
||||
|
||||
@return Error status.
|
||||
@retval FALSE on success.
|
||||
@@ -2588,6 +2625,7 @@ bool Table_triggers_list::process_triggers(THD *thd,
|
||||
trg_event_type event,
|
||||
trg_action_time_type time_type,
|
||||
bool old_row_is_record1,
|
||||
bool *skip_row_indicator,
|
||||
List<Item> *fields_in_update_stmt)
|
||||
{
|
||||
bool err_status;
|
||||
@@ -2595,6 +2633,18 @@ bool Table_triggers_list::process_triggers(THD *thd,
|
||||
Trigger *trigger;
|
||||
SELECT_LEX *save_current_select;
|
||||
|
||||
/*
|
||||
skip_row_indicator != nullptr for BEFORE INSERT/UPDATE/DELETE triggers
|
||||
*/
|
||||
DBUG_ASSERT((time_type == TRG_ACTION_BEFORE && skip_row_indicator) ||
|
||||
(time_type == TRG_ACTION_AFTER && !skip_row_indicator));
|
||||
/*
|
||||
In case skip_indicator points to an out variable, its initial value
|
||||
must be false
|
||||
*/
|
||||
DBUG_ASSERT(!skip_row_indicator ||
|
||||
(skip_row_indicator && *skip_row_indicator == false));
|
||||
|
||||
if (check_for_broken_triggers())
|
||||
return TRUE;
|
||||
|
||||
@@ -2618,8 +2668,21 @@ bool Table_triggers_list::process_triggers(THD *thd,
|
||||
*/
|
||||
DBUG_ASSERT(trigger_table->pos_in_table_list->trg_event_map & trg2bit(event));
|
||||
|
||||
thd->reset_sub_statement_state(&statement_state, SUB_STMT_TRIGGER);
|
||||
|
||||
if (time_type == TRG_ACTION_AFTER)
|
||||
thd->reset_sub_statement_state(&statement_state, SUB_STMT_TRIGGER);
|
||||
else
|
||||
/*
|
||||
For time type TRG_ACTION_BEFORE, set extra flag SUB_STMT_BEFORE_TRIGGER
|
||||
at sub statement state in addition to SUB_STMT_TRIGGER in order to
|
||||
be able to reset the m_sql_errno to the value
|
||||
ER_SIGNAL_SKIP_ROW_FROM_TRIGGER
|
||||
in case signal is raised with SQLSTATE "02TRG" from within a
|
||||
BEFORE trigger and don't modify m_sql_errno in case the signal is raised
|
||||
from AFTER trigger.
|
||||
@see Sql_state_errno_level::assign_defaults
|
||||
*/
|
||||
thd->reset_sub_statement_state(&statement_state,
|
||||
SUB_STMT_TRIGGER | SUB_STMT_BEFORE_TRIGGER);
|
||||
/*
|
||||
Reset current_select before call execute_trigger() and
|
||||
restore it after return from one. This way error is set
|
||||
@@ -2656,6 +2719,18 @@ bool Table_triggers_list::process_triggers(THD *thd,
|
||||
&trigger_table->s->db,
|
||||
&trigger_table->s->table_name,
|
||||
&trigger->subject_table_grants);
|
||||
|
||||
if (err_status &&
|
||||
do_skip_row_indicator(thd->get_stmt_da(), skip_row_indicator,
|
||||
time_type))
|
||||
{
|
||||
/* Reset DA that is set on handling the statement
|
||||
SIGNAL SSQLSTATE "02TRG"
|
||||
raised from within a trigger */
|
||||
err_status= false;
|
||||
thd->get_stmt_da()->reset_diagnostics_area();
|
||||
}
|
||||
|
||||
status_var_increment(thd->status_var.executed_triggers);
|
||||
} while (!err_status && (trigger= trigger->next));
|
||||
thd->bulk_param= save_bulk_param;
|
||||
|
Reference in New Issue
Block a user