summaryrefslogtreecommitdiff
path: root/sql
diff options
context:
space:
mode:
Diffstat (limited to 'sql')
-rw-r--r--sql/item.h8
-rw-r--r--sql/sp.cc53
-rw-r--r--sql/sp_head.cc53
-rw-r--r--sql/sp_head.h6
-rw-r--r--sql/sql_lex.cc21
-rw-r--r--sql/sql_lex.h2
-rw-r--r--sql/sql_parse.cc5
-rw-r--r--sql/sql_prepare.cc1
-rw-r--r--sql/sql_trigger.cc28
-rw-r--r--sql/sql_trigger.h8
-rw-r--r--sql/sql_view.cc13
-rw-r--r--sql/sql_yacc.yy4
-rw-r--r--sql/table.cc129
-rw-r--r--sql/table.h19
14 files changed, 293 insertions, 57 deletions
diff --git a/sql/item.h b/sql/item.h
index 58e3ec439b4..07bf7fec0ea 100644
--- a/sql/item.h
+++ b/sql/item.h
@@ -2309,14 +2309,6 @@ enum trg_action_time_type
TRG_ACTION_BEFORE= 0, TRG_ACTION_AFTER= 1, TRG_ACTION_MAX
};
-/*
- Event on which trigger is invoked.
-*/
-enum trg_event_type
-{
- TRG_EVENT_INSERT= 0 , TRG_EVENT_UPDATE= 1, TRG_EVENT_DELETE= 2, TRG_EVENT_MAX
-};
-
class Table_triggers_list;
/*
diff --git a/sql/sp.cc b/sql/sp.cc
index 3c8ebed4ae6..c0e7d5e2271 100644
--- a/sql/sp.cc
+++ b/sql/sp.cc
@@ -440,6 +440,19 @@ db_load_routine(THD *thd, int type, sp_name *name, sp_head **sphp,
lex_start(thd);
thd->spcont= NULL;
ret= MYSQLparse(thd);
+
+ if (ret == 0)
+ {
+ /*
+ Not strictly necessary to invoke this method here, since we know
+ that we've parsed CREATE PROCEDURE/FUNCTION and not an
+ UPDATE/DELETE/INSERT/REPLACE/LOAD/CREATE TABLE, but we try to
+ maintain the invariant that this method is called for each
+ distinct statement, in case its logic is extended with other
+ types of analyses in future.
+ */
+ newlex.set_trg_event_type_for_tables();
+ }
}
if (ret || thd->is_fatal_error || newlex.sphead == NULL)
@@ -1742,31 +1755,39 @@ sp_cache_routines_and_add_tables_for_triggers(THD *thd, LEX *lex,
TABLE_LIST *table)
{
int ret= 0;
- Table_triggers_list *triggers= table->table->triggers;
- if (add_used_routine(lex, thd->stmt_arena, &triggers->sroutines_key,
- table->belong_to_view))
+
+ Sroutine_hash_entry **last_cached_routine_ptr=
+ (Sroutine_hash_entry **)lex->sroutines_list.next;
+
+ if (static_cast<int>(table->lock_type) >=
+ static_cast<int>(TL_WRITE_ALLOW_WRITE))
{
- Sroutine_hash_entry **last_cached_routine_ptr=
- (Sroutine_hash_entry **)lex->sroutines_list.next;
for (int i= 0; i < (int)TRG_EVENT_MAX; i++)
{
- for (int j= 0; j < (int)TRG_ACTION_MAX; j++)
+ if (table->trg_event_map &
+ static_cast<uint8>(1 << static_cast<int>(i)))
{
- if (triggers->bodies[i][j])
+ for (int j= 0; j < (int)TRG_ACTION_MAX; j++)
{
- (void)triggers->bodies[i][j]->
- add_used_tables_to_table_list(thd, &lex->query_tables_last,
- table->belong_to_view);
- sp_update_stmt_used_routines(thd, lex,
- &triggers->bodies[i][j]->m_sroutines,
- table->belong_to_view);
+ /* We can have only one trigger per action type currently */
+ sp_head *trigger= table->table->triggers->bodies[i][j];
+ if (trigger &&
+ add_used_routine(lex, thd->stmt_arena, &trigger->m_sroutines_key,
+ table->belong_to_view))
+ {
+ trigger->add_used_tables_to_table_list(thd, &lex->query_tables_last,
+ table->belong_to_view);
+ sp_update_stmt_used_routines(thd, lex,
+ &trigger->m_sroutines,
+ table->belong_to_view);
+ }
}
}
}
- ret= sp_cache_routines_and_add_tables_aux(thd, lex,
- *last_cached_routine_ptr,
- FALSE, NULL);
}
+ ret= sp_cache_routines_and_add_tables_aux(thd, lex,
+ *last_cached_routine_ptr,
+ FALSE, NULL);
return ret;
}
diff --git a/sql/sp_head.cc b/sql/sp_head.cc
index d939fd20b9b..0ac1db336d0 100644
--- a/sql/sp_head.cc
+++ b/sql/sp_head.cc
@@ -478,12 +478,35 @@ sp_head::init(LEX *lex)
*/
lex->trg_table_fields.empty();
my_init_dynamic_array(&m_instr, sizeof(sp_instr *), 16, 8);
- m_param_begin= m_param_end= m_body_begin= 0;
- m_qname.str= m_db.str= m_name.str= m_params.str=
- m_body.str= m_defstr.str= 0;
- m_qname.length= m_db.length= m_name.length= m_params.length=
- m_body.length= m_defstr.length= 0;
+
+ m_param_begin= NULL;
+ m_param_end= NULL;
+
+ m_body_begin= NULL ;
+
+ m_qname.str= NULL;
+ m_qname.length= 0;
+
+ m_db.str= NULL;
+ m_db.length= 0;
+
+ m_name.str= NULL;
+ m_name.length= 0;
+
+ m_params.str= NULL;
+ m_params.length= 0;
+
+ m_body.str= NULL;
+ m_body.length= 0;
+
+ m_defstr.str= NULL;
+ m_defstr.length= 0;
+
+ m_sroutines_key.str= NULL;
+ m_sroutines_key.length= 0;
+
m_return_field_def.charset= NULL;
+
DBUG_VOID_RETURN;
}
@@ -509,9 +532,14 @@ sp_head::init_sp_name(THD *thd, sp_name *spname)
if (spname->m_qname.length == 0)
spname->init_qname(thd);
- m_qname.length= spname->m_qname.length;
- m_qname.str= strmake_root(thd->mem_root, spname->m_qname.str,
- m_qname.length);
+ m_sroutines_key.length= spname->m_sroutines_key.length;
+ m_sroutines_key.str= memdup_root(thd->mem_root,
+ spname->m_sroutines_key.str,
+ spname->m_sroutines_key.length + 1);
+ m_sroutines_key.str[0]= static_cast<char>(m_type);
+
+ m_qname.length= m_sroutines_key.length - 1;
+ m_qname.str= m_sroutines_key.str + 1;
DBUG_VOID_RETURN;
}
@@ -1796,8 +1824,11 @@ sp_head::restore_lex(THD *thd)
{
DBUG_ENTER("sp_head::restore_lex");
LEX *sublex= thd->lex;
- LEX *oldlex= (LEX *)m_lex.pop();
+ LEX *oldlex;
+
+ sublex->set_trg_event_type_for_tables();
+ oldlex= (LEX *)m_lex.pop();
if (! oldlex)
return; // Nothing to restore
@@ -3429,6 +3460,7 @@ typedef struct st_sp_table
thr_lock_type lock_type; /* lock type used for prelocking */
uint lock_count;
uint query_lock_count;
+ uint8 trg_event_map;
} SP_TABLE;
byte *
@@ -3515,6 +3547,7 @@ sp_head::merge_table_list(THD *thd, TABLE_LIST *table, LEX *lex_for_tmp_check)
tab->query_lock_count++;
if (tab->query_lock_count > tab->lock_count)
tab->lock_count++;
+ tab->trg_event_map|= table->trg_event_map;
}
else
{
@@ -3536,6 +3569,7 @@ sp_head::merge_table_list(THD *thd, TABLE_LIST *table, LEX *lex_for_tmp_check)
tab->db_length= table->db_length;
tab->lock_type= table->lock_type;
tab->lock_count= tab->query_lock_count= 1;
+ tab->trg_event_map= table->trg_event_map;
my_hash_insert(&m_sptabs, (byte *)tab);
}
}
@@ -3613,6 +3647,7 @@ sp_head::add_used_tables_to_table_list(THD *thd,
table->cacheable_table= 1;
table->prelocking_placeholder= 1;
table->belong_to_view= belong_to_view;
+ table->trg_event_map= stab->trg_event_map;
/* Everyting else should be zeroed */
diff --git a/sql/sp_head.h b/sql/sp_head.h
index ed99885ae9a..ebe40ce9c87 100644
--- a/sql/sp_head.h
+++ b/sql/sp_head.h
@@ -130,6 +130,12 @@ public:
st_sp_chistics *m_chistics;
ulong m_sql_mode; // For SHOW CREATE and execution
LEX_STRING m_qname; // db.name
+ /**
+ Key representing routine in the set of stored routines used by statement.
+ [routine_type]db.name\0
+ @sa sp_name::m_sroutines_key
+ */
+ LEX_STRING m_sroutines_key;
LEX_STRING m_db;
LEX_STRING m_name;
LEX_STRING m_params;
diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc
index dbce1e38139..c37d77345b6 100644
--- a/sql/sql_lex.cc
+++ b/sql/sql_lex.cc
@@ -2034,6 +2034,27 @@ void st_select_lex_unit::set_limit(SELECT_LEX *sl)
}
+/**
+ Update the parsed tree with information about triggers that
+ may be fired when executing this statement.
+*/
+
+void st_lex::set_trg_event_type_for_tables()
+{
+ /*
+ Do not iterate over sub-selects, only the tables in the outermost
+ SELECT_LEX can be modified, if any.
+ */
+ TABLE_LIST *tables= select_lex.get_table_list();
+
+ while (tables)
+ {
+ tables->set_trg_event_type(this);
+ tables= tables->next_local;
+ }
+}
+
+
/*
Unlink the first table from the global table list and the first table from
outer select (lex->select_lex) local list
diff --git a/sql/sql_lex.h b/sql/sql_lex.h
index 25a6c31e21c..bfa6c05974f 100644
--- a/sql/sql_lex.h
+++ b/sql/sql_lex.h
@@ -1188,6 +1188,8 @@ typedef struct st_lex : public Query_tables_list
un->uncacheable|= cause;
}
}
+ void set_trg_event_type_for_tables();
+
TABLE_LIST *unlink_first_table(bool *link_to_local);
void link_first_table_back(TABLE_LIST *first, bool link_to_local);
void first_lists_tables_same();
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index 124fcff9517..91c51641fc0 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -6080,8 +6080,9 @@ void mysql_parse(THD *thd, const char *inBuf, uint length,
(thd->query_length= (ulong)(lip.found_semicolon - thd->query)))
thd->query_length--;
/* Actually execute the query */
- mysql_execute_command(thd);
- query_cache_end_of_result(thd);
+ lex->set_trg_event_type_for_tables();
+ mysql_execute_command(thd);
+ query_cache_end_of_result(thd);
}
}
}
diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc
index 567f92b55ba..c993ce32e50 100644
--- a/sql/sql_prepare.cc
+++ b/sql/sql_prepare.cc
@@ -2826,6 +2826,7 @@ bool Prepared_statement::prepare(const char *packet, uint packet_len)
lex_start(thd);
lex->safe_to_cache_query= FALSE;
int err= MYSQLparse((void *)thd);
+ lex->set_trg_event_type_for_tables();
error= err || thd->is_fatal_error ||
thd->net.report_error || init_param_array(this);
diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc
index 5762614e47f..6e4b5defb97 100644
--- a/sql/sql_trigger.cc
+++ b/sql/sql_trigger.cc
@@ -943,17 +943,6 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
table->triggers= triggers;
/*
- Construct key that will represent triggers for this table in the set
- of routines used by statement.
- */
- triggers->sroutines_key.length= 1+strlen(db)+1+strlen(table_name)+1;
- if (!(triggers->sroutines_key.str=
- alloc_root(&table->mem_root, triggers->sroutines_key.length)))
- DBUG_RETURN(1);
- triggers->sroutines_key.str[0]= TYPE_ENUM_TRIGGER;
- strxmov(triggers->sroutines_key.str+1, db, ".", table_name, NullS);
-
- /*
TODO: This could be avoided if there is no triggers
for UPDATE and DELETE.
*/
@@ -991,6 +980,15 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db,
DBUG_ASSERT(lex.sphead == 0);
goto err_with_lex_cleanup;
}
+ /*
+ Not strictly necessary to invoke this method here, since we know
+ that we've parsed CREATE TRIGGER and not an
+ UPDATE/DELETE/INSERT/REPLACE/LOAD/CREATE TABLE, but we try to
+ maintain the invariant that this method is called for each
+ distinct statement, in case its logic is extended with other
+ types of analyses in future.
+ */
+ lex.set_trg_event_type_for_tables();
lex.sphead->set_info(0, 0, &lex.sp_chistics, (ulong) *trg_sql_mode);
@@ -1550,6 +1548,12 @@ bool Table_triggers_list::process_triggers(THD *thd, trg_event_type event,
new_field= record1_field;
old_field= trigger_table->field;
}
+ /*
+ This trigger must have been processed by the pre-locking
+ algorithm.
+ */
+ DBUG_ASSERT(trigger_table->pos_in_table_list->trg_event_map &
+ static_cast<uint>(1 << static_cast<int>(event)));
thd->reset_sub_statement_state(&statement_state, SUB_STMT_TRIGGER);
err_status= sp_trigger->execute_trigger
@@ -1568,7 +1572,7 @@ bool Table_triggers_list::process_triggers(THD *thd, trg_event_type event,
SYNOPSIS
mark_fields_used()
thd Current thread context
- event Type of event triggers for which we are going to inspect
+ event Type of event triggers for which we are going to ins
DESCRIPTION
This method marks fields of subject table which are read/set in its
diff --git a/sql/sql_trigger.h b/sql/sql_trigger.h
index b029a70ca20..1dc573995f1 100644
--- a/sql/sql_trigger.h
+++ b/sql/sql_trigger.h
@@ -56,14 +56,6 @@ class Table_triggers_list: public Sql_alloc
updating trigger definitions during RENAME TABLE.
*/
List<LEX_STRING> on_table_names_list;
- /*
- Key representing triggers for this table in set of all stored
- routines used by statement.
- TODO: We won't need this member once triggers namespace will be
- database-wide instead of table-wide because then we will be able
- to use key based on sp_name as for other stored routines.
- */
- LEX_STRING sroutines_key;
/*
Grant information for each trigger (pair: subject table, trigger definer).
diff --git a/sql/sql_view.cc b/sql/sql_view.cc
index 6c94d388d0e..7857ba267c5 100644
--- a/sql/sql_view.cc
+++ b/sql/sql_view.cc
@@ -1153,7 +1153,20 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table,
*/
for (tbl= view_main_select_tables; tbl; tbl= tbl->next_local)
tbl->lock_type= table->lock_type;
+ /*
+ If the view is mergeable, we might want to
+ INSERT/UPDATE/DELETE into tables of this view. Preserve the
+ original sql command and 'duplicates' of the outer lex.
+ This is used later in set_trg_event_type_for_command.
+ */
+ lex->sql_command= old_lex->sql_command;
+ lex->duplicates= old_lex->duplicates;
}
+ /*
+ This method has a dependency on the proper lock type being set,
+ so in case of views should be called here.
+ */
+ lex->set_trg_event_type_for_tables();
/*
If we are opening this view as part of implicit LOCK TABLES, then
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index 949f3ed4161..8cc6642ae7e 100644
--- a/sql/sql_yacc.yy
+++ b/sql/sql_yacc.yy
@@ -9644,13 +9644,13 @@ trigger_tail:
MYSQL_YYABORT;
sp->reset_thd_mem_root(thd);
sp->init(lex);
+ sp->m_type= TYPE_ENUM_TRIGGER;
sp->init_sp_name(thd, $3);
lex->stmt_definition_begin= $2;
lex->ident.str= $7;
lex->ident.length= $10 - $7;
- sp->m_type= TYPE_ENUM_TRIGGER;
lex->sphead= sp;
lex->spname= $3;
/*
@@ -9728,9 +9728,9 @@ sp_tail:
sp= new sp_head();
sp->reset_thd_mem_root(YYTHD);
sp->init(lex);
+ sp->m_type= TYPE_ENUM_PROCEDURE;
sp->init_sp_name(YYTHD, $3);
- sp->m_type= TYPE_ENUM_PROCEDURE;
lex->sphead= sp;
/*
* We have to turn of CLIENT_MULTI_QUERIES while parsing a
diff --git a/sql/table.cc b/sql/table.cc
index 899d0ab2ed0..9c3e7618aa0 100644
--- a/sql/table.cc
+++ b/sql/table.cc
@@ -1776,6 +1776,135 @@ void st_table::reset_item_list(List<Item> *item_list) const
}
}
+
+/**
+ Set the initial purpose of this TABLE_LIST object in the list of
+ used tables. We need to track this information on table-by-
+ table basis, since when this table becomes an element of the
+ pre-locked list, it's impossible to identify which SQL
+ sub-statement it has been originally used in.
+
+ E.g.:
+
+ User request: SELECT * FROM t1 WHERE f1();
+ FUNCTION f1(): DELETE FROM t2; RETURN 1;
+ BEFORE DELETE trigger on t2: INSERT INTO t3 VALUES (old.a);
+
+ For this user request, the pre-locked list will contain t1, t2, t3
+ table elements, each needed for different DML.
+
+ This method is called immediately after parsing for tables
+ of the table list of the top-level select lex.
+
+ The trigger event map is updated to reflect INSERT, UPDATE, DELETE,
+ REPLACE, LOAD DATA, CREATE TABLE .. SELECT, CREATE TABLE ..
+ REPLACE SELECT statements, and additionally ON DUPLICATE KEY UPDATE
+ clause.
+*/
+
+void
+TABLE_LIST::set_trg_event_type(const st_lex *lex)
+{
+ enum trg_event_type trg_event;
+
+ /*
+ Some auxiliary operations
+ (e.g. GRANT processing) create TABLE_LIST instances outside
+ the parser. Additionally, some commands (e.g. OPTIMIZE) change
+ the lock type for a table only after parsing is done. Luckily,
+ these do not fire triggers and do not need to pre-load them.
+ For these TABLE_LISTs set_trg_event_type is never called, and
+ trg_event_map is always empty. That means that the pre-locking
+ algorithm will ignore triggers defined on these tables, if
+ any, and the execution will either fail with an assert in
+ sql_trigger.cc or with an error that a used table was not
+ pre-locked, in case of a production build.
+
+ TODO: this usage pattern creates unnecessary module dependencies
+ and should be rewritten to go through the parser.
+ Table list instances created outside the parser in most cases
+ refer to mysql.* system tables. It is not allowed to have
+ a trigger on a system table, but keeping track of
+ initialization provides extra safety in case this limitation
+ is circumvented.
+ */
+
+ /*
+ This is a fast check to filter out statements that do
+ not change data, or tables on the right side, in case of
+ INSERT .. SELECT, CREATE TABLE .. SELECT and so on.
+ Here we also filter out OPTIMIZE statement and non-updateable
+ views, for which lock_type is TL_UNLOCK or TL_READ after
+ parsing.
+ */
+ if (static_cast<int>(lock_type) < static_cast<int>(TL_WRITE_ALLOW_WRITE))
+ return;
+
+ switch (lex->sql_command) {
+ /*
+ Basic INSERT. If there is an additional ON DUPLIATE KEY UPDATE
+ clause, it will be handled later in this method.
+ */
+ case SQLCOM_INSERT: /* fall through */
+ case SQLCOM_INSERT_SELECT:
+ /*
+ LOAD DATA ... INFILE is expected to fire BEFORE/AFTER INSERT
+ triggers.
+ If the statement also has REPLACE clause, it will be
+ handled later in this method.
+ */
+ case SQLCOM_LOAD: /* fall through */
+ /*
+ REPLACE is semantically equivalent to INSERT. In case
+ of a primary or unique key conflict, it deletes the old
+ record and inserts a new one. So we also may need to
+ fire ON DELETE triggers. This functionality is handled
+ later in this method.
+ */
+ case SQLCOM_REPLACE: /* fall through */
+ case SQLCOM_REPLACE_SELECT:
+ /*
+ CREATE TABLE ... SELECT defaults to INSERT if the table or
+ view already exists. REPLACE option of CREATE TABLE ...
+ REPLACE SELECT is handled later in this method.
+ */
+ case SQLCOM_CREATE_TABLE:
+ trg_event= TRG_EVENT_INSERT;
+ break;
+ /* Basic update and multi-update */
+ case SQLCOM_UPDATE: /* fall through */
+ case SQLCOM_UPDATE_MULTI:
+ trg_event= TRG_EVENT_UPDATE;
+ break;
+ /* Basic delete and multi-delete */
+ case SQLCOM_DELETE: /* fall through */
+ case SQLCOM_DELETE_MULTI:
+ trg_event= TRG_EVENT_DELETE;
+ break;
+ default:
+ /*
+ OK to return, since value of 'duplicates' is irrelevant
+ for non-updating commands.
+ */
+ return;
+ }
+ trg_event_map|= static_cast<uint8>(1 << static_cast<int>(trg_event));
+
+ switch (lex->duplicates) {
+ case DUP_UPDATE:
+ trg_event= TRG_EVENT_UPDATE;
+ break;
+ case DUP_REPLACE:
+ trg_event= TRG_EVENT_DELETE;
+ break;
+ case DUP_ERROR:
+ default:
+ return;
+ }
+ trg_event_map|= static_cast<uint8>(1 << static_cast<int>(trg_event));
+}
+
+
/*
calculate md5 of query
diff --git a/sql/table.h b/sql/table.h
index b29ef8c6566..f8f7d7f06b7 100644
--- a/sql/table.h
+++ b/sql/table.h
@@ -59,6 +59,17 @@ enum tmp_table_type {NO_TMP_TABLE=0,
NON_TRANSACTIONAL_TMP_TABLE=1, TRANSACTIONAL_TMP_TABLE=2,
SYSTEM_TMP_TABLE=3};
+
+/** Event on which trigger is invoked. */
+enum trg_event_type
+{
+ TRG_EVENT_INSERT= 0,
+ TRG_EVENT_UPDATE= 1,
+ TRG_EVENT_DELETE= 2,
+ TRG_EVENT_MAX
+};
+
+
enum frm_type_enum
{
FRMTYPE_ERROR= 0,
@@ -702,6 +713,13 @@ struct TABLE_LIST
*/
bool create;
+ /**
+ Indicates what triggers we need to pre-load for this TABLE_LIST
+ when opening an associated TABLE. This is filled after
+ the parsed tree is created.
+ */
+ uint8 trg_event_map;
+
enum enum_schema_table_state schema_table_state;
void calc_md5(char *buffer);
void set_underlying_merge();
@@ -752,6 +770,7 @@ struct TABLE_LIST
void reinit_before_use(THD *thd);
Item_subselect *containing_subselect();
+ void set_trg_event_type(const st_lex *lex);
private:
bool prep_check_option(THD *thd, uint8 check_opt_type);
bool prep_where(THD *thd, Item **conds, bool no_where_clause);