1
0
mirror of https://github.com/MariaDB/server.git synced 2026-01-06 05:22:24 +03:00

MDEV-6112 multiple triggers per table

This is similar to MysQL Worklog 3253, but with
a different implementation. The disk format and
SQL syntax is identical with MySQL 5.7.

Fetures supported:
- "Any" ammount of any trigger
- Supports FOLLOWS and PRECEDES to be
  able to put triggers in a certain execution order.

Implementation details:
- Class Trigger added to hold information about a trigger.
  Before this trigger information was stored in a set of lists in
  Table_triggers_list and in Table_triggers_list::bodies
- Each Trigger has a next field that poinst to the next Trigger with the
  same action and time.
- When accessing a trigger, we now always access all linked triggers
- The list are now only used to load and save trigger files.
- MySQL trigger test case (trigger_wl3253) added and we execute these
  identically.
- Even more gracefully handling of wrong trigger files than before. This
  is useful if a trigger file uses functions or syntax not provided by
  the server.
- Each trigger now has a "Created" field that shows when the trigger was
  created, with 2 decimals.

Other comments:
- Many of the changes in test files was done because of the new "Created"
  field in the trigger file. This shows up in SHOW ... TRIGGER and when
  using information_schema.trigger.
- Don't check if all memory is released if on uses --gdb;  This is needed
  to be able to get a list from safemalloc of not freed memory while
  debugging.
- Added option to trim_whitespace() to know how many prefix characters
  was skipped.
- Changed a few ulonglong sql_mode to sql_mode_t, to find some wrong usage
  of sql_mode.
This commit is contained in:
Monty
2016-10-02 15:35:08 +03:00
parent 0bae1957dd
commit 8be53a389c
73 changed files with 2553 additions and 1175 deletions

View File

@@ -51,22 +51,92 @@ enum trg_action_time_type
TRG_ACTION_BEFORE= 0, TRG_ACTION_AFTER= 1, TRG_ACTION_MAX
};
enum trigger_order_type
{
TRG_ORDER_NONE= 0,
TRG_ORDER_FOLLOWS= 1,
TRG_ORDER_PRECEDES= 2
};
struct st_trg_execution_order
{
/**
FOLLOWS or PRECEDES as specified in the CREATE TRIGGER statement.
*/
enum trigger_order_type ordering_clause;
/**
Trigger name referenced in the FOLLOWS/PRECEDES clause of the
CREATE TRIGGER statement.
*/
LEX_STRING anchor_trigger_name;
};
class Table_triggers_list;
/**
This class holds all information about triggers of table.
TODO: Will it be merged into TABLE in the future ?
The trigger object
*/
class Table_triggers_list: public Sql_alloc
class Trigger :public Sql_alloc
{
/** Triggers as SPs grouped by event, action_time */
sp_head *bodies[TRG_EVENT_MAX][TRG_ACTION_MAX];
public:
Trigger(Table_triggers_list *base_arg, sp_head *code):
base(base_arg), body(code), next(0), trigger_fields(0), action_order(0)
{
bzero((char *)&subject_table_grants, sizeof(subject_table_grants));
}
~Trigger();
Table_triggers_list *base;
sp_head *body;
Trigger *next; /* Next trigger of same type */
/**
Heads of the lists linking items for all fields used in triggers
grouped by event and action_time.
*/
Item_trigger_field *trigger_fields[TRG_EVENT_MAX][TRG_ACTION_MAX];
Item_trigger_field *trigger_fields;
LEX_STRING name;
LEX_STRING on_table_name; /* Raw table name */
LEX_STRING definition;
LEX_STRING definer;
/* Character sets used */
LEX_STRING client_cs_name;
LEX_STRING connection_cl_name;
LEX_STRING db_cl_name;
GRANT_INFO subject_table_grants;
ulonglong sql_mode;
/* Store create time. Can't be mysql_time_t as this holds also sub seconds */
ulonglong create_time;
trg_event_type event;
trg_action_time_type action_time;
uint action_order;
bool is_fields_updated_in_trigger(MY_BITMAP *used_fields);
void get_trigger_info(LEX_STRING *stmt, LEX_STRING *body,
LEX_STRING *definer);
/* Functions executed over each active trigger */
bool change_on_table_name(void* param_arg);
bool change_table_name(void* param_arg);
bool add_to_file_list(void* param_arg);
};
typedef bool (Trigger::*Triggers_processor)(void *arg);
/**
This class holds all information about triggers of table.
*/
class Table_triggers_list: public Sql_alloc
{
friend class Trigger;
/* Points to first trigger for a certain type */
Trigger *triggers[TRG_EVENT_MAX][TRG_ACTION_MAX];
/**
Copy of TABLE::Field array which all fields made nullable
(using extra_null_bitmap, if needed). Used for NEW values in
@@ -90,22 +160,6 @@ class Table_triggers_list: public Sql_alloc
/* TABLE instance for which this triggers list object was created */
TABLE *trigger_table;
/**
Names of triggers.
Should correspond to order of triggers on definitions_list,
used in CREATE/DROP TRIGGER for looking up trigger by name.
*/
List<LEX_STRING> names_list;
/**
List of "ON table_name" parts in trigger definitions, used for
updating trigger definitions during RENAME TABLE.
*/
List<LEX_STRING> on_table_names_list;
/**
Grant information for each trigger (pair: subject table, trigger definer).
*/
GRANT_INFO subject_table_grants[TRG_EVENT_MAX][TRG_ACTION_MAX];
/**
This flag indicates that one of the triggers was not parsed successfully,
@@ -127,6 +181,7 @@ class Table_triggers_list: public Sql_alloc
the trigger file.
*/
char m_parse_error_message[MYSQL_ERRMSG_SIZE];
uint count; /* Number of triggers */
public:
/**
@@ -138,6 +193,8 @@ public:
List of sql modes for triggers
*/
List<ulonglong> definition_modes_list;
/** Create times for triggers */
List<ulonglong> create_times;
List<LEX_STRING> definers_list;
@@ -152,11 +209,9 @@ public:
Table_triggers_list(TABLE *table_arg)
:record0_field(0), extra_null_bitmap(0), record1_field(0),
trigger_table(table_arg),
m_has_unparseable_trigger(false)
m_has_unparseable_trigger(false), count(0)
{
bzero((char *)bodies, sizeof(bodies));
bzero((char *)trigger_fields, sizeof(trigger_fields));
bzero((char *)&subject_table_grants, sizeof(subject_table_grants));
bzero((char *) triggers, sizeof(triggers));
}
~Table_triggers_list();
@@ -165,26 +220,9 @@ public:
bool process_triggers(THD *thd, trg_event_type event,
trg_action_time_type time_type,
bool old_row_is_record1);
bool get_trigger_info(THD *thd, trg_event_type event,
trg_action_time_type time_type,
LEX_STRING *trigger_name, LEX_STRING *trigger_stmt,
ulong *sql_mode,
LEX_STRING *definer,
LEX_STRING *client_cs_name,
LEX_STRING *connection_cl_name,
LEX_STRING *db_cl_name);
void get_trigger_info(THD *thd,
int trigger_idx,
LEX_STRING *trigger_name,
ulonglong *sql_mode,
LEX_STRING *sql_original_stmt,
LEX_STRING *client_cs_name,
LEX_STRING *connection_cl_name,
LEX_STRING *db_cl_name);
int find_trigger_by_name(const LEX_STRING *trigger_name);
void empty_lists();
bool create_lists_needed_for_files(MEM_ROOT *root);
bool save_trigger_file(THD *thd, const char *db, const char *table_name);
static bool check_n_load(THD *thd, const char *db, const char *table_name,
TABLE *table, bool names_only);
@@ -194,15 +232,32 @@ public:
const char *old_table,
const char *new_db,
const char *new_table);
void add_trigger(trg_event_type event_type,
trg_action_time_type action_time,
trigger_order_type ordering_clause,
LEX_STRING *anchor_trigger_name,
Trigger *trigger);
Trigger *get_trigger(trg_event_type event_type,
trg_action_time_type action_time)
{
return triggers[event_type][action_time];
}
/* Simpler version of the above, to avoid casts in the code */
Trigger *get_trigger(uint event_type, uint action_time)
{
return get_trigger((trg_event_type) event_type,
(trg_action_time_type) action_time);
}
bool has_triggers(trg_event_type event_type,
trg_action_time_type action_time)
{
return (bodies[event_type][action_time] != NULL);
return get_trigger(event_type,action_time) != 0;
}
bool has_delete_triggers()
{
return (bodies[TRG_EVENT_DELETE][TRG_ACTION_BEFORE] ||
bodies[TRG_EVENT_DELETE][TRG_ACTION_AFTER]);
return (has_triggers(TRG_EVENT_DELETE,TRG_ACTION_BEFORE) ||
has_triggers(TRG_EVENT_DELETE,TRG_ACTION_AFTER));
}
void mark_fields_used(trg_event_type event);
@@ -215,10 +270,6 @@ public:
Query_tables_list *prelocking_ctx,
TABLE_LIST *table_list);
bool is_fields_updated_in_trigger(MY_BITMAP *used_fields,
trg_event_type event_type,
trg_action_time_type action_time);
Field **nullable_fields() { return record0_field; }
void reset_extra_null_bitmap()
{
@@ -227,12 +278,16 @@ public:
bzero(extra_null_bitmap, null_bytes);
}
Trigger *find_trigger(const LEX_STRING *name, bool remove_from_list);
Trigger* for_all_triggers(Triggers_processor func, void *arg);
private:
bool prepare_record_accessors(TABLE *table);
LEX_STRING* change_table_name_in_trignames(const char *old_db_name,
const char *new_db_name,
LEX_STRING *new_table_name,
LEX_STRING *stopper);
Trigger *change_table_name_in_trignames(const char *old_db_name,
const char *new_db_name,
LEX_STRING *new_table_name,
Trigger *trigger);
bool change_table_name_in_triggers(THD *thd,
const char *old_db_name,
const char *new_db_name,
@@ -257,9 +312,6 @@ inline Field **TABLE::field_to_fill()
}
extern const LEX_STRING trg_action_time_type_names[];
extern const LEX_STRING trg_event_type_names[];
bool add_table_for_trigger(THD *thd,
const sp_name *trg_name,
bool continue_if_not_exist,