diff options
Diffstat (limited to 'sql/sql_trigger.cc')
-rw-r--r-- | sql/sql_trigger.cc | 253 |
1 files changed, 161 insertions, 92 deletions
diff --git a/sql/sql_trigger.cc b/sql/sql_trigger.cc index 4e056739d96..4269c468982 100644 --- a/sql/sql_trigger.cc +++ b/sql/sql_trigger.cc @@ -12,15 +12,26 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define MYSQL_LEX 1 -#include "mysql_priv.h" +#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include "sql_priv.h" +#include "unireg.h" #include "sp_head.h" #include "sql_trigger.h" +#include "sql_parse.h" // parse_sql #include "parse_file.h" +#include "sp.h" +#include "sql_base.h" // find_temporary_table +#include "sql_show.h" // append_definer, append_identifier +#include "sql_table.h" // build_table_filename, + // check_n_cut_mysql50_prefix +#include "sql_db.h" // get_default_db_collation +#include "sql_acl.h" // *_ACL, is_acl_user +#include "sql_handler.h" // mysql_ha_rm_tables +#include "sp_cache.h" // sp_invalidate_cache #include <mysys_err.h> /*************************************************************************/ @@ -315,8 +326,12 @@ public: Deprecated_trigger_syntax_handler() : m_trigger_name(NULL) {} - virtual bool handle_error(uint sql_errno, const char *message, - MYSQL_ERROR::enum_warning_level level, THD *thd) + virtual bool handle_condition(THD *thd, + uint sql_errno, + const char* sqlstate, + MYSQL_ERROR::enum_warning_level level, + const char* message, + MYSQL_ERROR ** cond_hdl) { if (sql_errno != EE_OUTOFMEMORY && sql_errno != ER_OUT_OF_RESOURCES) @@ -325,12 +340,11 @@ public: m_trigger_name= &thd->lex->spname->m_name; if (m_trigger_name) my_snprintf(m_message, sizeof(m_message), - "Trigger '%s' has an error in its body: '%s'", + ER(ER_ERROR_IN_TRIGGER_BODY), m_trigger_name->str, message); else my_snprintf(m_message, sizeof(m_message), - "Unknown trigger has an error in its body: '%s'", - message); + ER(ER_ERROR_IN_UNKNOWN_TRIGGER_BODY), message); return true; } return false; @@ -376,8 +390,9 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) TABLE *table; bool result= TRUE; String stmt_query; + bool lock_upgrade_done= FALSE; + MDL_ticket *mdl_ticket= NULL; Query_tables_list backup; - bool need_start_waiting= FALSE; DBUG_ENTER("mysql_create_or_drop_trigger"); @@ -426,19 +441,6 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) DBUG_RETURN(TRUE); } - /* - We don't want perform our operations while global read lock is held - so we have to wait until its end and then prevent it from occurring - again until we are done, unless we are under lock tables. (Acquiring - LOCK_open is not enough because global read lock is held without holding - LOCK_open). - */ - if (!thd->locked_tables && - !(need_start_waiting= !wait_if_global_read_lock(thd, 0, 1))) - DBUG_RETURN(TRUE); - - VOID(pthread_mutex_lock(&LOCK_open)); - if (!create) { bool if_exists= thd->lex->drop_if_exists; @@ -448,6 +450,13 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) destructive changes necessary to open the trigger's table. */ thd->lex->reset_n_backup_query_tables_list(&backup); + /* + Restore Query_tables_list::sql_command, which was + reset above, as the code that writes the query to the + binary log assumes that this value corresponds to the + statement that is being executed. + */ + thd->lex->sql_command= backup.sql_command; if (add_table_for_trigger(thd, thd->lex->spname, if_exists, & tables)) goto end; @@ -478,7 +487,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) TABLE_LIST **save_query_tables_own_last= thd->lex->query_tables_own_last; thd->lex->query_tables_own_last= 0; - err_status= check_table_access(thd, TRIGGER_ACL, tables, 1, FALSE); + err_status= check_table_access(thd, TRIGGER_ACL, tables, FALSE, 1, FALSE); thd->lex->query_tables_own_last= save_query_tables_own_last; @@ -490,7 +499,7 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) DBUG_ASSERT(tables->next_global == 0); /* We do not allow creation of triggers on temporary tables. */ - if (create && find_temporary_table(thd, tables->db, tables->table_name)) + if (create && find_temporary_table(thd, tables)) { my_error(ER_TRG_ON_VIEW_OR_TEMP_TABLE, MYF(0), tables->alias); goto end; @@ -498,31 +507,41 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) /* We also don't allow creation of triggers on views. */ tables->required_type= FRMTYPE_TABLE; + /* + Also prevent DROP TRIGGER from opening temporary table which might + shadow base table on which trigger to be dropped is defined. + */ + tables->open_type= OT_BASE_ONLY; /* Keep consistent with respect to other DDL statements */ - mysql_ha_rm_tables(thd, tables, TRUE); + mysql_ha_rm_tables(thd, tables); - if (thd->locked_tables) + if (thd->locked_tables_mode) { - /* Table must be write locked */ - if (name_lock_locked_table(thd, tables)) + /* Under LOCK TABLES we must only accept write locked tables. */ + if (!(tables->table= find_table_for_mdl_upgrade(thd, tables->db, + tables->table_name, + FALSE))) goto end; } else { - /* Grab the name lock and insert the placeholder*/ - if (lock_table_names(thd, tables)) - goto end; - - /* Convert the placeholder to a real table */ - if (reopen_name_locked_table(thd, tables, TRUE)) - { - unlock_table_name(thd, tables); + tables->table= open_n_lock_single_table(thd, tables, + TL_READ_NO_INSERT, 0); + if (! tables->table) goto end; - } + tables->table->use_all_columns(); } table= tables->table; + /* Later on we will need it to downgrade the lock */ + mdl_ticket= table->mdl_ticket; + + if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN)) + goto end; + + lock_upgrade_done= TRUE; + if (!table->triggers) { if (!create) @@ -539,42 +558,41 @@ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) table->triggers->create_trigger(thd, tables, &stmt_query): table->triggers->drop_trigger(thd, tables, &stmt_query)); - /* Under LOCK TABLES we must reopen the table to activate the trigger. */ - if (!result && thd->locked_tables) - { - /* Make table suitable for reopening */ - close_data_files_and_morph_locks(thd, tables->db, tables->table_name); - thd->in_lock_tables= 1; - if (reopen_tables(thd, 1, 1)) - { - /* To be safe remove this table from the set of LOCKED TABLES */ - unlink_open_table(thd, tables->table, FALSE); + if (result) + goto end; - /* - Ignore reopen_tables errors for now. It's better not leave master/slave - in a inconsistent state. - */ - thd->clear_error(); - } - thd->in_lock_tables= 0; - } + close_all_tables_for_name(thd, table->s, FALSE); + /* + Reopen the table if we were under LOCK TABLES. + Ignore the return value for now. It's better to + keep master/slave in consistent state. + */ + thd->locked_tables_list.reopen_tables(thd); -end: + /* + Invalidate SP-cache. That's needed because triggers may change list of + pre-locking tables. + */ + sp_cache_invalidate(); +end: if (!result) { result= write_bin_log(thd, TRUE, stmt_query.ptr(), stmt_query.length()); } - VOID(pthread_mutex_unlock(&LOCK_open)); + /* + If we are under LOCK TABLES we should restore original state of + meta-data locks. Otherwise all locks will be released along + with the implicit commit. + */ + if (thd->locked_tables_mode && tables && lock_upgrade_done) + mdl_ticket->downgrade_exclusive_lock(MDL_SHARED_NO_READ_WRITE); /* Restore the query table list. Used only for drop trigger. */ if (!create) thd->lex->restore_backup_query_tables_list(&backup); - if (need_start_waiting) - start_waiting_global_read_lock(thd); - if (!result) my_ok(thd); @@ -866,7 +884,7 @@ bool Table_triggers_list::create_trigger(THD *thd, TABLE_LIST *tables, return 0; err_with_cleanup: - my_delete(trigname_buff, MYF(MY_WME)); + mysql_file_delete(key_file_trn, trigname_buff, MYF(MY_WME)); return 1; } @@ -889,7 +907,7 @@ static bool rm_trigger_file(char *path, const char *db, const char *table_name) { build_table_filename(path, FN_REFLEN-1, db, table_name, TRG_EXT, 0); - return my_delete(path, MYF(MY_WME)); + return mysql_file_delete(key_file_trg, path, MYF(MY_WME)); } @@ -911,7 +929,7 @@ static bool rm_trigname_file(char *path, const char *db, const char *trigger_name) { build_table_filename(path, FN_REFLEN - 1, db, trigger_name, TRN_EXT, 0); - return my_delete(path, MYF(MY_WME)); + return mysql_file_delete(key_file_trn, path, MYF(MY_WME)); } @@ -1343,6 +1361,7 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db, thd->reset_db((char*) db, strlen(db)); while ((trg_create_str= it++)) { + sp_head *sp; trg_sql_mode= itm++; LEX_STRING *trg_definer= it_definer++; @@ -1429,8 +1448,11 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db, int event= lex.trg_chistics.event; int action_time= lex.trg_chistics.action_time; - lex.sphead->set_creation_ctx(creation_ctx); - triggers->bodies[event][action_time]= lex.sphead; + sp= triggers->bodies[event][action_time]= lex.sphead; + lex.sphead= NULL; /* Prevent double cleanup. */ + + sp->set_info(0, 0, &lex.sp_chistics, (ulong) *trg_sql_mode); + sp->set_creation_ctx(creation_ctx); if (!trg_definer->length) { @@ -1443,29 +1465,28 @@ bool Table_triggers_list::check_n_load(THD *thd, const char *db, push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_WARN, ER_TRG_NO_DEFINER, ER(ER_TRG_NO_DEFINER), (const char*) db, - (const char*) lex.sphead->m_name.str); + (const char*) sp->m_name.str); /* Set definer to the '' to correct displaying in the information schema. */ - lex.sphead->set_definer((char*) "", 0); + sp->set_definer((char*) "", 0); /* Triggers without definer information are executed under the authorization of the invoker. */ - lex.sphead->m_chistics->suid= SP_IS_NOT_SUID; + sp->m_chistics->suid= SP_IS_NOT_SUID; } else - lex.sphead->set_definer(trg_definer->str, trg_definer->length); + sp->set_definer(trg_definer->str, trg_definer->length); + + if (triggers->names_list.push_back(&sp->m_name, &table->mem_root)) + goto err_with_lex_cleanup; - if (triggers->names_list.push_back(&lex.sphead->m_name, - &table->mem_root)) - goto err_with_lex_cleanup; - if (!(on_table_name= alloc_lex_string(&table->mem_root))) goto err_with_lex_cleanup; @@ -1731,7 +1752,8 @@ bool add_table_for_trigger(THD *thd, DBUG_RETURN(TRUE); *table= sp_add_to_query_tables(thd, lex, trg_name->m_db.str, - tbl_name.str, TL_IGNORE); + tbl_name.str, TL_IGNORE, + MDL_SHARED_NO_WRITE); DBUG_RETURN(*table ? FALSE : TRUE); } @@ -1744,9 +1766,6 @@ bool add_table_for_trigger(THD *thd, @param db schema for table @param name name for table - @note - The calling thread should hold the LOCK_open mutex; - @retval False success @retval @@ -1761,7 +1780,7 @@ bool Table_triggers_list::drop_all_triggers(THD *thd, char *db, char *name) DBUG_ENTER("drop_all_triggers"); bzero(&table, sizeof(table)); - init_alloc_root(&table.mem_root, 8192, 0); + init_sql_alloc(&table.mem_root, 8192, 0); if (Table_triggers_list::check_n_load(thd, db, name, &table, 1)) { @@ -1952,6 +1971,7 @@ Table_triggers_list::change_table_name_in_trignames(const char *old_db_name, @param[in,out] thd Thread context @param[in] db Old database of subject table + @param[in] old_alias Old alias of subject table @param[in] old_table Old name of subject table @param[in] new_db New database for subject table @param[in] new_table New name of subject table @@ -1961,13 +1981,14 @@ Table_triggers_list::change_table_name_in_trignames(const char *old_db_name, i.e. it either will complete successfully, or will fail leaving files in their initial state. Also this method assumes that subject table is not renamed to itself. - This method needs to be called under an exclusive table name lock. + This method needs to be called under an exclusive table metadata lock. @retval FALSE Success @retval TRUE Error */ bool Table_triggers_list::change_table_name(THD *thd, const char *db, + const char *old_alias, const char *old_table, const char *new_db, const char *new_table) @@ -1979,24 +2000,17 @@ bool Table_triggers_list::change_table_name(THD *thd, const char *db, DBUG_ENTER("change_table_name"); bzero(&table, sizeof(table)); - init_alloc_root(&table.mem_root, 8192, 0); + init_sql_alloc(&table.mem_root, 8192, 0); /* This method interfaces the mysql server code protected by - either LOCK_open mutex or with an exclusive table name lock. - In the future, only an exclusive table name lock will be enough. + an exclusive metadata lock. */ -#ifndef DBUG_OFF - uchar key[MAX_DBKEY_LENGTH]; - uint key_length= (uint) (strmov(strmov((char*)&key[0], db)+1, - old_table)-(char*)&key[0])+1; - - if (!is_table_name_exclusively_locked_by_this_thread(thd, key, key_length)) - safe_mutex_assert_owner(&LOCK_open); -#endif + DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, db, old_table, + MDL_EXCLUSIVE)); DBUG_ASSERT(my_strcasecmp(table_alias_charset, db, new_db) || - my_strcasecmp(table_alias_charset, old_table, new_table)); + my_strcasecmp(table_alias_charset, old_alias, new_table)); if (Table_triggers_list::check_n_load(thd, db, old_table, &table, TRUE)) { @@ -2010,7 +2024,7 @@ bool Table_triggers_list::change_table_name(THD *thd, const char *db, result= 1; goto end; } - LEX_STRING old_table_name= { (char *) old_table, strlen(old_table) }; + LEX_STRING old_table_name= { (char *) old_alias, strlen(old_alias) }; LEX_STRING new_table_name= { (char *) new_table, strlen(new_table) }; /* Since triggers should be in the same schema as their subject tables @@ -2144,6 +2158,61 @@ bool Table_triggers_list::process_triggers(THD *thd, /** + Add triggers for table to the set of routines used by statement. + Add tables used by them to statement table list. Do the same for + routines used by triggers. + + @param thd Thread context. + @param prelocking_ctx Prelocking context of the statement. + @param table_list Table list element for table with trigger. + + @retval FALSE Success. + @retval TRUE Failure. +*/ + +bool +Table_triggers_list:: +add_tables_and_routines_for_triggers(THD *thd, + Query_tables_list *prelocking_ctx, + TABLE_LIST *table_list) +{ + DBUG_ASSERT(static_cast<int>(table_list->lock_type) >= + static_cast<int>(TL_WRITE_ALLOW_WRITE)); + + for (int i= 0; i < (int)TRG_EVENT_MAX; i++) + { + if (table_list->trg_event_map & + static_cast<uint8>(1 << static_cast<int>(i))) + { + for (int j= 0; j < (int)TRG_ACTION_MAX; j++) + { + /* We can have only one trigger per action type currently */ + sp_head *trigger= table_list->table->triggers->bodies[i][j]; + + if (trigger) + { + MDL_key key(MDL_key::TRIGGER, trigger->m_db.str, trigger->m_name.str); + + if (sp_add_used_routine(prelocking_ctx, thd->stmt_arena, + &key, table_list->belong_to_view)) + { + trigger->add_used_tables_to_table_list(thd, + &prelocking_ctx->query_tables_last, + table_list->belong_to_view); + sp_update_stmt_used_routines(thd, prelocking_ctx, + &trigger->m_sroutines, + table_list->belong_to_view); + trigger->propagate_attributes(prelocking_ctx); + } + } + } + } + } + return FALSE; +} + + +/** Mark fields of subject table which we read/set in its triggers as such. |