diff options
Diffstat (limited to 'sql')
-rw-r--r-- | sql/item.h | 8 | ||||
-rw-r--r-- | sql/sp.cc | 53 | ||||
-rw-r--r-- | sql/sp_head.cc | 53 | ||||
-rw-r--r-- | sql/sp_head.h | 6 | ||||
-rw-r--r-- | sql/sql_lex.cc | 21 | ||||
-rw-r--r-- | sql/sql_lex.h | 2 | ||||
-rw-r--r-- | sql/sql_parse.cc | 5 | ||||
-rw-r--r-- | sql/sql_prepare.cc | 1 | ||||
-rw-r--r-- | sql/sql_trigger.cc | 28 | ||||
-rw-r--r-- | sql/sql_trigger.h | 8 | ||||
-rw-r--r-- | sql/sql_view.cc | 13 | ||||
-rw-r--r-- | sql/sql_yacc.yy | 4 | ||||
-rw-r--r-- | sql/table.cc | 129 | ||||
-rw-r--r-- | sql/table.h | 19 |
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); |