Commit 2086f96c authored by Dmitry Shulga's avatar Dmitry Shulga

MDEV-5816: Stored programs: validation of stored program statements

Re-designed a way by that Item_trigger_field objects are arranged in memory.

Item_trigger_field objects created on parsing a trigger's statement
is now stored in a per statement list. All lists of Item_trigger_field
objects created on parsing the whole trigger's body are organized
in the structure "list of lists". So, use binary cycle to iterate every
Item_trigger_field object created on parsing a trigger body.

To organize the data structure 'list of lists' the new data member
  Item_trigger_field::next_trig_field_list
is introduced that links lists in this hierarchy structure.

This re-design is performed in order to avoid refences to already
deleted items on re-compilation of failed trigger's statememt.
Referencing to already deleted items could take place on re-parsing
a trigger's statement since every Item created for a statement
being re-parsed is deleted before the statement be re-parsed,
but deleted items are still referenced from sp_head. So, to avoid
access to dangling references a per statement list of Item_trigger_field
objects are cleared right after the current SP statement be cleaned up
and before re-parsing be started.
parent 465c81b3
......@@ -6950,6 +6950,14 @@ class Item_trigger_field : public Item_field,
public:
/* Next in list of all Item_trigger_field's in trigger */
Item_trigger_field *next_trg_field;
/**
Pointer to the next list of Item_trigger_field objects. This pointer
is used to organize an intrusive list of lists of Item_trigger_field
objects managed by sp_head.
*/
SQL_I_List<Item_trigger_field> *next_trig_field_list;
/* Pointer to Table_trigger_list object for table of this trigger */
Table_triggers_list *triggers;
/* Is this item represents row from NEW or OLD row ? */
......@@ -6984,7 +6992,8 @@ Item_trigger_field(THD *thd, Name_resolution_context *context_arg,
const LEX_CSTRING &field_name_arg,
privilege_t priv, const bool ro)
:Item_field(thd, context_arg, field_name_arg),
table_grants(NULL), next_trg_field(NULL), triggers(NULL),
table_grants(nullptr), next_trg_field(nullptr),
next_trig_field_list(nullptr), triggers(nullptr),
row_version(row_ver_arg), field_idx(NO_CACHED_FIELD_INDEX),
read_only (ro), original_privilege(priv), want_privilege(priv)
{
......
......@@ -3052,6 +3052,39 @@ int sp_head::add_instr(sp_instr *instr)
*/
instr->mem_root= &main_mem_root;
instr->m_lineno= m_thd->m_parser_state->m_lip.yylineno;
/*
Check if SP is a trigger and there are Item_trigger_field objects
created on parsing the current SP instruction.
*/
if (m_handler->type() == SP_TYPE_TRIGGER &&
m_cur_instr_trig_field_items.elements)
{
/*
Get a pointer to a list of Item_trigger_field objects owned by
the current SP instruction. This list is used for storing
Item_trigger_field objects that can be created on parsing
the SP instruction's statement. If the current SP instruction is not
an instance of the class sp_lex_instr or derived class then
the value nullptr is returned by the virtual method
get_instr_trig_field_list().
*/
SQL_I_List<Item_trigger_field> *instr_trig_fld_list=
instr->get_instr_trig_field_list();
/*
If the current SP instruction can store a list of Item_trigger_field
objects created on its parsing then move these Item_trigger_field
objects to the SP instruction's list.
*/
if (instr_trig_fld_list)
{
m_cur_instr_trig_field_items.save_and_clear(instr_trig_fld_list);
m_trg_table_fields.link_in_list(
instr_trig_fld_list,
&instr_trig_fld_list->first->next_trig_field_list);
}
}
return insert_dynamic(&m_instr, (uchar*)&instr);
}
......
......@@ -980,13 +980,13 @@ class sp_head :private Query_arena,
public:
/*
List of all items (Item_trigger_field objects) representing fields in
old/new version of row in trigger. We use this list for checking whenever
all such fields are valid at trigger creation time and for binding these
fields to TABLE object at table open (altough for latter pointer to table
being opened is probably enough).
List of lists of Item_trigger_field objects representing all fields in
old/new version of row in trigger. We use this list of lists for checking
whenever all such fields are valid at trigger creation time and for binding
these fields to TABLE object at table open (although for latter pointer
to table being opened is probably enough).
*/
SQL_I_List<Item_trigger_field> m_trg_table_fields;
SQL_I_List<SQL_I_List<Item_trigger_field> > m_trg_table_fields;
/**
The object of the Trigger class corresponding to this sp_head object.
......@@ -996,6 +996,12 @@ class sp_head :private Query_arena,
trigger's instruction.
*/
Trigger *m_trg= nullptr;
/*
List of Item_trigger_field objects created on parsing
a current instruction of trigger's body
*/
SQL_I_List<Item_trigger_field> m_cur_instr_trig_field_items;
}; // class sp_head : public Sql_alloc
......
......@@ -546,7 +546,7 @@ void sp_lex_instr::get_query(String *sql_query) const
}
void sp_lex_instr::cleanup_before_parsing()
void sp_lex_instr::cleanup_before_parsing(enum_sp_type sp_type)
{
Item *current= free_list;
......@@ -558,6 +558,14 @@ void sp_lex_instr::cleanup_before_parsing()
}
free_list= nullptr;
if (sp_type == SP_TYPE_TRIGGER)
/*
Some of deleted items can be referenced from the list
m_cur_trigger_stmt_items. Clean up the list content to avoid
dangling references.
*/
m_cur_trigger_stmt_items.empty();
}
......@@ -568,17 +576,36 @@ void sp_lex_instr::cleanup_before_parsing()
@param sp sp_head object of the trigger
*/
static void setup_table_fields_for_trigger(THD *thd, sp_head *sp)
bool sp_lex_instr::setup_table_fields_for_trigger(
THD *thd, sp_head *sp,
SQL_I_List<Item_trigger_field> *next_trig_items_list)
{
bool result= false;
DBUG_ASSERT(sp->m_trg);
for (Item_trigger_field *trg_field= sp->m_trg_table_fields.first;
for (Item_trigger_field *trg_field= sp->m_cur_instr_trig_field_items.first;
trg_field;
trg_field= trg_field->next_trg_field)
{
trg_field->setup_field(thd, sp->m_trg->base->get_subject_table(),
&sp->m_trg->subject_table_grants);
result= trg_field->fix_fields_if_needed(thd, (Item **)0);
}
/*
Move the list of Item_trigger_field objects, that have just been
filled in on parsing the trigger's statement, into the instruction list
owned by SP instruction.
*/
if (sp->m_cur_instr_trig_field_items.elements)
{
sp->m_cur_instr_trig_field_items.save_and_clear(
&m_cur_trigger_stmt_items);
m_cur_trigger_stmt_items.first->next_trig_field_list= next_trig_items_list;
}
return result;
}
LEX* sp_lex_instr::parse_expr(THD *thd, sp_head *sp)
......@@ -599,7 +626,18 @@ LEX* sp_lex_instr::parse_expr(THD *thd, sp_head *sp)
return nullptr;
}
cleanup_before_parsing();
/*
Remember a pointer to the next list of Item_trigger_field objects.
The current list of Item_trigger_field objects is cleared up in the
method cleanup_before_parsing().
*/
SQL_I_List<Item_trigger_field> *saved_ptr_to_next_trg_items_list= nullptr;
if (m_cur_trigger_stmt_items.elements)
saved_ptr_to_next_trg_items_list=
m_cur_trigger_stmt_items.first->next_trig_field_list;
cleanup_before_parsing(sp->m_handler->type());
Parser_state parser_state;
......@@ -668,7 +706,8 @@ LEX* sp_lex_instr::parse_expr(THD *thd, sp_head *sp)
parsing_failed= on_after_expr_parsing(thd);
if (sp->m_handler->type() == SP_TYPE_TRIGGER)
setup_table_fields_for_trigger(thd, sp);
setup_table_fields_for_trigger(thd, sp,
saved_ptr_to_next_trg_items_list);
/*
Assign the list of items created on parsing to the current
......@@ -1101,7 +1140,7 @@ bool sp_instr_set_trigger_field::on_after_expr_parsing(THD *thd)
if (!value || !trigger_field)
return true;
thd->spcont->m_sp->m_trg_table_fields.link_in_list(
thd->spcont->m_sp->m_cur_instr_trig_field_items.link_in_list(
trigger_field, &trigger_field->next_trg_field);
return false;
......
......@@ -198,6 +198,10 @@ class sp_instr :public Query_arena, public Sql_alloc
virtual PSI_statement_info* get_psi_info() = 0;
virtual SQL_I_List<Item_trigger_field>* get_instr_trig_field_list()
{
return nullptr;
}
}; // class sp_instr : public Sql_alloc
......@@ -387,6 +391,11 @@ class sp_lex_instr : public sp_instr
*/
LEX *parse_expr(THD *thd, sp_head *sp);
SQL_I_List<Item_trigger_field>* get_instr_trig_field_list() override
{
return &m_cur_trigger_stmt_items;
}
protected:
/**
@return the expression query string. This string can't be passed directly
......@@ -420,10 +429,35 @@ class sp_lex_instr : public sp_instr
sp_lex_keeper m_lex_keeper;
private:
/**
List of Item_trigger_field objects created on parsing of a SQL statement
corresponding to this SP-instruction.
*/
SQL_I_List<Item_trigger_field> m_cur_trigger_stmt_items;
/**
Clean up items previously created on behalf of the current instruction.
*/
void cleanup_before_parsing();
void cleanup_before_parsing(enum_sp_type sp_type);
/**
Set up field object for every NEW/OLD item of the trigger and
move the list of Item_trigger_field objects, created on parsing the current
trigger's instruction, from sp_head to trigger's SP instruction object.
@param thd current thread
@param sp sp_head object of the trigger
@param next_trig_items_list pointer to the next list of Item_trigger_field
objects that used as a link between lists
to support list of lists structure.
@return false on success, true on failure
*/
bool setup_table_fields_for_trigger(
THD *thd, sp_head *sp,
SQL_I_List<Item_trigger_field> *next_trig_items_list);
};
......
......@@ -242,7 +242,8 @@ bool LEX::set_trigger_new_row(const LEX_CSTRING *name, Item *val,
Let us add this item to list of all Item_trigger_field
objects in trigger.
*/
sphead->m_trg_table_fields.link_in_list(trg_fld, &trg_fld->next_trg_field);
sphead->m_cur_instr_trig_field_items.link_in_list(trg_fld,
&trg_fld->next_trg_field);
return sphead->add_instr(sp_fld);
}
......@@ -7954,7 +7955,8 @@ Item *LEX::create_and_link_Item_trigger_field(THD *thd,
in trigger.
*/
if (likely(trg_fld))
sphead->m_trg_table_fields.link_in_list(trg_fld, &trg_fld->next_trg_field);
sphead->m_cur_instr_trig_field_items.link_in_list(trg_fld,
&trg_fld->next_trg_field);
return trg_fld;
}
......
......@@ -866,6 +866,41 @@ static void build_trig_stmt_query(THD *thd, TABLE_LIST *tables,
}
/**
Visit every Item_trigger_field object associated with a trigger
and run the code supplied in the last argument, passing
the Item_trigger_fgield object being visited.
@param trg_table_fields Item_trigger_field objects owned by a trigger
@param fn a function to invoke for every Item_trigger_field
object
@return false on success, true on failure.
*/
template <typename FN>
static
bool iterate_trigger_fields_and_run_func(
SQL_I_List<SQL_I_List<Item_trigger_field> > &trg_table_fields,
FN fn
)
{
for (SQL_I_List<Item_trigger_field>
*trg_fld_lst= trg_table_fields.first;
trg_fld_lst;
trg_fld_lst= trg_fld_lst->first->next_trig_field_list)
{
for (Item_trigger_field *trg_field= trg_fld_lst->first;
trg_field;
trg_field= trg_field->next_trg_field)
{
if (fn(trg_field))
return true;
}
}
return false;
}
/**
Create trigger for table.
......@@ -902,7 +937,6 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,
char trg_definer_holder[USER_HOST_BUFF_SIZE];
LEX_CSTRING backup_name= { backup_file_buff, 0 };
LEX_CSTRING file, trigname_file;
Item_trigger_field *trg_field;
struct st_trigname trigname;
String trigger_definition;
Trigger *trigger= 0;
......@@ -939,18 +973,20 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables,
*/
old_field= new_field= table->field;
for (trg_field= lex->sphead->m_trg_table_fields.first;
trg_field; trg_field= trg_field->next_trg_field)
{
/*
NOTE: now we do not check privileges at CREATE TRIGGER time. This will
be changed in the future.
*/
trg_field->setup_field(thd, table, NULL);
if (iterate_trigger_fields_and_run_func(
lex->sphead->m_trg_table_fields,
[thd, table] (Item_trigger_field* trg_field)
{
/*
NOTE: now we do not check privileges at CREATE TRIGGER time.
This will be changed in the future.
*/
trg_field->setup_field(thd, table, nullptr);
if (trg_field->fix_fields_if_needed(thd, (Item **)0))
DBUG_RETURN(true);
}
return trg_field->fix_fields_if_needed(thd, (Item **)0);
}
))
DBUG_RETURN(true);
/* Ensure anchor trigger exists */
if (lex->trg_chistics.ordering_clause != TRG_ORDER_NONE)
......@@ -1797,12 +1833,6 @@ bool Table_triggers_list::check_n_load(THD *thd, const LEX_CSTRING *db,
continue;
}
/*
Gather all Item_trigger_field objects representing access to fields
in old/new versions of row in trigger into lists containing all such
objects for the trigger_list with same action and timing.
*/
trigger->trigger_fields= sp->m_trg_table_fields.first;
/*
Also let us bind these objects to Field objects in table being
opened.
......@@ -1812,13 +1842,15 @@ bool Table_triggers_list::check_n_load(THD *thd, const LEX_CSTRING *db,
SELECT)...
Anyway some things can be checked only during trigger execution.
*/
for (Item_trigger_field *trg_field= sp->m_trg_table_fields.first;
trg_field;
trg_field= trg_field->next_trg_field)
{
trg_field->setup_field(thd, table,
&trigger->subject_table_grants);
}
(void)iterate_trigger_fields_and_run_func(
sp->m_trg_table_fields,
[thd, table, trigger] (Item_trigger_field* trg_field)
{
trg_field->setup_field(thd, table, &trigger->subject_table_grants);
return false;
}
);
sp->m_trg= trigger;
lex_end(&lex);
......@@ -2579,7 +2611,6 @@ add_tables_and_routines_for_triggers(THD *thd,
void Table_triggers_list::mark_fields_used(trg_event_type event)
{
int action_time;
Item_trigger_field *trg_field;
DBUG_ENTER("Table_triggers_list::mark_fields_used");
for (action_time= 0; action_time < (int)TRG_ACTION_MAX; action_time++)
......@@ -2588,20 +2619,28 @@ void Table_triggers_list::mark_fields_used(trg_event_type event)
trigger ;
trigger= trigger->next)
{
for (trg_field= trigger->trigger_fields;
trg_field;
trg_field= trg_field->next_trg_field)
{
/* We cannot mark fields which does not present in table. */
if (trg_field->field_idx != NO_CACHED_FIELD_INDEX)
/*
Skip a trigger that was parsed with an error.
*/
if (trigger->body == nullptr)
continue;
(void)iterate_trigger_fields_and_run_func(
trigger->body->m_trg_table_fields,
[this] (Item_trigger_field* trg_field)
{
DBUG_PRINT("info", ("marking field: %u", (uint) trg_field->field_idx));
if (trg_field->get_settable_routine_parameter())
bitmap_set_bit(trigger_table->write_set, trg_field->field_idx);
trigger_table->mark_column_with_deps(
trigger_table->field[trg_field->field_idx]);
/* We cannot mark fields which does not present in table. */
if (trg_field->field_idx != NO_CACHED_FIELD_INDEX)
{
DBUG_PRINT("info", ("marking field: %u", (uint) trg_field->field_idx));
if (trg_field->get_settable_routine_parameter())
bitmap_set_bit(trigger_table->write_set, trg_field->field_idx);
trigger_table->mark_column_with_deps(
trigger_table->field[trg_field->field_idx]);
}
return false;
}
}
);
}
}
trigger_table->file->column_bitmaps_signal();
......
......@@ -113,7 +113,7 @@ class Trigger :public Sql_alloc
{
public:
Trigger(Table_triggers_list *base_arg, sp_head *code):
base(base_arg), body(code), next(0), trigger_fields(0), action_order(0)
base(base_arg), body(code), next(0), action_order(0)
{
bzero((char *)&subject_table_grants, sizeof(subject_table_grants));
}
......@@ -122,11 +122,6 @@ class Trigger :public Sql_alloc
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;
LEX_CSTRING name;
LEX_CSTRING on_table_name; /* Raw table name */
LEX_CSTRING definition;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment