1
0
mirror of https://github.com/MariaDB/server.git synced 2025-08-08 11:22:35 +03:00

MDEV-10164: Add support for TRIGGERS that fire on multiple events

Added capability to create a trigger associated with several trigger
events. For this goal, the syntax of the CREATE TRIGGER statement
was extended to support the syntax structure { event [ OR ... ] }
for the `trigger_event` clause. Since one trigger will be able to
handle several events it should be provided a way to determine what
kind of event is handled on execution of a trigger. For this goal
support of the clauses INSERTING, UPDATING , DELETING was added by
this patch. These clauses can be used inside a trigger body to detect
what kind of trigger action is currently processed using the following
boilerplate:
  IF INSERTING THEN ...
  ELSIF UPDATING THEN ...
  ELSIF DELETING THEN ...
In case one of the clauses INSERTING, UPDATING, DELETING specified in
a trigger's body not matched with a trigger event type, the error
ER_INCOMPATIBLE_EVENT_FLAG is emitted.

After this patch be pushed, one Trigger object will be associated with
several trigger events. It means that the array
  Table_triggers_list::triggers
can contain several pointers to the same Trigger object in array members
corresponding to different events. Moreover, support of several trigger
events for the same trigger requires that the data members `next` and
`action_order` of the Trigger class be converted to arrays to store
relating information per trigger event base.

Ability to specify the same trigger for different event types results in
necessity to handle invalid cases on execution of the multi-event
trigger, when the OLD or NEW qualifiers doesn't match a current event
type against that the trigger is run. The clause OLD should produces
the NULL value for INSERT event, whereas the clause NEW should produce
the NULL value for DELETE event.
This commit is contained in:
Dmitry Shulga
2025-04-19 18:36:03 +07:00
parent 86ec20189a
commit ecb7c9b692
24 changed files with 1245 additions and 74 deletions

View File

@@ -389,9 +389,13 @@ Trigger* Table_triggers_list::for_all_triggers(Triggers_processor func,
{
for (Trigger *trigger= get_trigger(i,j) ;
trigger ;
trigger= trigger->next)
if ((trigger->*func)(arg))
trigger= trigger->next[i])
if (is_the_right_most_event_bit(trigger->events, (trg_event_type)i) &&
(trigger->*func)(arg))
{
(trigger->*func)(arg);
return trigger;
}
}
}
return 0;
@@ -997,7 +1001,11 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,
if (lex->trg_chistics.ordering_clause != TRG_ORDER_NONE)
{
if (!(trigger= find_trigger(&lex->trg_chistics.anchor_trigger_name, 0)) ||
trigger->event != lex->trg_chistics.event ||
/*
check that every event listed for the trigger being created is also
specified for anchored trigger
*/
!is_subset_of_trg_events(trigger->events, lex->trg_chistics.events) ||
trigger->action_time != lex->trg_chistics.action_time)
{
my_error(ER_REFERENCED_TRG_DOES_NOT_EXIST, MYF(0),
@@ -1133,8 +1141,9 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,
trigger->db_cl_name= get_default_db_collation(thd, tables->db.str)->coll_name;
trigger->name= Lex_ident_trigger(lex->spname->m_name);
/* Add trigger in it's correct place */
add_trigger(lex->trg_chistics.event,
add_trigger(lex->trg_chistics.events,
lex->trg_chistics.action_time,
lex->trg_chistics.ordering_clause,
Lex_ident_trigger(lex->trg_chistics.anchor_trigger_name),
@@ -1239,7 +1248,7 @@ bool Trigger::add_to_file_list(void* param_arg)
bool Trigger::match_updatable_columns(List<Item> &fields)
{
DBUG_ASSERT(event == TRG_EVENT_UPDATE);
DBUG_ASSERT(is_trg_event_on(events, TRG_EVENT_UPDATE));
/*
No table columns were specified in OF col1, col2 ... colN of
@@ -1359,29 +1368,53 @@ bool Table_triggers_list::save_trigger_file(THD *thd, const LEX_CSTRING *db,
Trigger *Table_triggers_list::find_trigger(const LEX_CSTRING *name,
bool remove_from_list)
{
Trigger *trigger = nullptr;
for (uint i= 0; i < (uint)TRG_EVENT_MAX; i++)
{
for (uint j= 0; j < (uint)TRG_ACTION_MAX; j++)
{
Trigger **parent, *trigger;
Trigger **parent;
for (parent= &triggers[i][j];
(trigger= *parent);
parent= &trigger->next)
parent= &trigger->next[i])
{
if (trigger->name.streq(*name))
{
if (remove_from_list)
{
*parent= trigger->next;
*parent= trigger->next[i];
count--;
/*
in case only one event left or was assigned to this trigger
return it, else continue iterations to remove the trigger
from all events entries.
*/
if (trigger->events != (1 << i))
{
/*
Turn off event bits in the mask as the trigger is removed
from the array for corresponding trigger event action.
Eventually, we come to the last event this trigger is
associated to. The associated trigger be returned from
the method and finally deleted.
*/
trigger->events &= ~(1 << i);
continue;
}
}
return trigger;
}
}
}
}
return 0;
/*
We come to this point if either remove_from_list == true and
the trigger is associated with multiple events, or there is no a trigger
with requested name.
*/
return trigger;
}
@@ -1476,15 +1509,26 @@ Table_triggers_list::~Table_triggers_list()
{
DBUG_ENTER("Table_triggers_list::~Table_triggers_list");
for (uint i= 0; i < (uint)TRG_EVENT_MAX; i++)
/*
Iterate over trigger events in descending order to delete only the last
instance of the Trigger class in case there are several events associated
with the trigger.
*/
for (int i= (int)TRG_EVENT_MAX - 1; i >= 0; i--)
{
for (uint j= 0; j < (uint)TRG_ACTION_MAX; j++)
{
Trigger *next, *trigger;
for (trigger= get_trigger(i,j) ; trigger ; trigger= next)
{
next= trigger->next;
delete trigger;
next= trigger->next[i];
/*
Since iteration along triggers is performed in descending order
deleting an instance of the Trigger class for the right most event
bit guarantees that the instance is deleted only once.
*/
if (is_the_right_most_event_bit(trigger->events, (trg_event_type)i))
delete trigger;
}
}
}
@@ -1792,7 +1836,7 @@ bool Table_triggers_list::check_n_load(THD *thd, const LEX_CSTRING *db,
thd->spcont= NULL;
/* The following is for catching parse errors */
lex.trg_chistics.event= TRG_EVENT_MAX;
lex.trg_chistics.events= TRG_EVENT_UNKNOWN;
lex.trg_chistics.action_time= TRG_ACTION_MAX;
Deprecated_trigger_syntax_handler error_handler;
thd->push_internal_handler(&error_handler);
@@ -1851,13 +1895,21 @@ bool Table_triggers_list::check_n_load(THD *thd, const LEX_CSTRING *db,
lex.trg_chistics.on_update_col_names))
goto err_with_lex_cleanup;
/* event can only be TRG_EVENT_MAX in case of fatal parse errors */
if (lex.trg_chistics.event != TRG_EVENT_MAX)
trigger_list->add_trigger(lex.trg_chistics.event,
/*
events can be equal TRG_EVENT_UNKNOWN only in case of
fatal parse errors
*/
if (lex.trg_chistics.events != TRG_EVENT_UNKNOWN)
{
const Lex_ident_trigger
anchor_trg_name(lex.trg_chistics.anchor_trigger_name);
trigger_list->add_trigger(lex.trg_chistics.events,
lex.trg_chistics.action_time,
TRG_ORDER_NONE,
Lex_ident_trigger(lex.trg_chistics.anchor_trigger_name),
anchor_trg_name,
trigger);
}
if (unlikely(parse_error))
{
@@ -2011,6 +2063,36 @@ error:
}
void Table_triggers_list::add_trigger(trg_event_set trg_events,
trg_action_time_type action_time,
trigger_order_type ordering_clause,
const Lex_ident_trigger &
anchor_trigger_name,
Trigger *trigger)
{
if (is_trg_event_on(trg_events, TRG_EVENT_INSERT))
add_trigger(TRG_EVENT_INSERT,
action_time,
ordering_clause,
anchor_trigger_name,
trigger);
if (is_trg_event_on(trg_events, TRG_EVENT_UPDATE))
add_trigger(TRG_EVENT_UPDATE,
action_time,
ordering_clause,
anchor_trigger_name,
trigger);
if (is_trg_event_on(trg_events, TRG_EVENT_DELETE))
add_trigger(TRG_EVENT_DELETE,
action_time,
ordering_clause,
anchor_trigger_name,
trigger);
}
/**
Add trigger in the correct position according to ordering clause
Also update action order
@@ -2028,14 +2110,14 @@ void Table_triggers_list::add_trigger(trg_event_type event,
Trigger **parent= &triggers[event][action_time];
uint position= 0;
for ( ; *parent ; parent= &(*parent)->next, position++)
for ( ; *parent ; parent= &(*parent)->next[event], position++)
{
if (ordering_clause != TRG_ORDER_NONE &&
anchor_trigger_name.streq((*parent)->name))
{
if (ordering_clause == TRG_ORDER_FOLLOWS)
{
parent= &(*parent)->next; // Add after this one
parent= &(*parent)->next[event]; // Add after this one
position++;
}
break;
@@ -2043,15 +2125,15 @@ void Table_triggers_list::add_trigger(trg_event_type event,
}
/* Add trigger where parent points to */
trigger->next= *parent;
trigger->next[event]= *parent;
*parent= trigger;
/* Update action_orders and position */
trigger->event= event;
trigger->events|= trg2bit(event);
trigger->action_time= action_time;
trigger->action_order= ++position;
while ((trigger= trigger->next))
trigger->action_order= ++position;
trigger->action_order[event]= ++position;
while ((trigger= trigger->next[event]))
trigger->action_order[event]= ++position;
count++;
}
@@ -2217,7 +2299,7 @@ bool Table_triggers_list::drop_all_triggers(THD *thd, const LEX_CSTRING *db,
Trigger *trigger;
for (trigger= table.triggers->get_trigger(i,j) ;
trigger ;
trigger= trigger->next)
trigger= trigger->next[i])
{
/*
Trigger, which body we failed to parse during call
@@ -2613,6 +2695,40 @@ static inline bool do_skip_row_indicator(Diagnostics_area *da,
}
/**
This class is responsible for storing a kind of current trigger event
for processing of NEW/OLD clauses inside trigger's body.
Before start processing of triggers for the given event type, the event type
pushed into the stack of events in constructor of the class
Trigger_event_guard and popped after processing all triggers of this event
type by running destructor of the class Trigger_event_guard.
Every time when the NEW or OLD clause is evaluated on processing a trigger
body, the event type of trigger being executed is consulted to determine
whether a value of the clause can produce meaning value: for INSERT event,
evaluation of the OLD clause should return NULL; for DELETE event, evaluation
of the NEW clause should return NULL.
@see Item_trigger_field::check_new_old_qulifiers_comform_with_trg_event()
@see Item_trigger_field::save_in_field()
@see Item_trigger_field::val_*()
*/
class Trigger_event_guard
{
Statement *m_stmt;
public:
Trigger_event_guard(Statement *stmt,
trg_event_type event)
: m_stmt{stmt}
{
m_stmt->push_current_trg_event(event);
}
~Trigger_event_guard()
{
m_stmt->pop_current_trg_event();
}
};
/**
Execute trigger for given (event, time) pair.
@@ -2708,6 +2824,7 @@ bool Table_triggers_list::process_triggers(THD *thd,
void *save_bulk_param= thd->bulk_param;
thd->bulk_param= nullptr;
Trigger_event_guard guard(thd, event);
do {
thd->lex->current_select= NULL;
@@ -2742,7 +2859,7 @@ bool Table_triggers_list::process_triggers(THD *thd,
}
status_var_increment(thd->status_var.executed_triggers);
} while (!err_status && (trigger= trigger->next));
} while (!err_status && (trigger= trigger->next[event]));
thd->bulk_param= save_bulk_param;
thd->lex->current_select= save_current_select;
@@ -2782,7 +2899,7 @@ add_tables_and_routines_for_triggers(THD *thd,
{
Trigger *triggers= table_list->table->triggers->get_trigger(i,j);
for ( ; triggers ; triggers= triggers->next)
for ( ; triggers ; triggers= triggers->next[i])
{
sp_head *trigger= triggers->body;
@@ -2828,7 +2945,7 @@ bool Table_triggers_list::match_updatable_columns(List<Item> *fields)
{
for (Trigger *trigger= get_trigger(TRG_EVENT_UPDATE, i) ;
trigger ;
trigger= trigger->next)
trigger= trigger->next[TRG_EVENT_UPDATE])
if (trigger->match_updatable_columns(*fields))
return true;
}
@@ -2859,7 +2976,7 @@ void Table_triggers_list::mark_fields_used(trg_event_type event)
{
for (Trigger *trigger= get_trigger(event,action_time);
trigger ;
trigger= trigger->next)
trigger= trigger->next[event])
{
/*
Skip a trigger that was parsed with an error.