Server IP : 104.21.38.3 / Your IP : 162.158.88.74 Web Server : Apache System : Linux krdc-ubuntu-s-2vcpu-4gb-amd-blr1-01.localdomain 5.15.0-142-generic #152-Ubuntu SMP Mon May 19 10:54:31 UTC 2025 x86_64 User : www ( 1000) PHP Version : 7.4.33 Disable Function : passthru,exec,system,putenv,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /www/server/mysql/src/sql/ |
Upload File : |
/* Copyright (c) 2004, 2023, Oracle and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. This program is also distributed with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have included with MySQL. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. 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 */ #include "my_global.h" // NO_EMBEDDED_ACCESS_CHECKS #include "sql_trigger.h" #include "auth_common.h" // check_table_access #include "sp.h" // sp_add_to_query_tables() #include "sql_base.h" // find_temporary_table() #include "sql_table.h" // build_table_filename() // write_bin_log() #include "sql_handler.h" // mysql_ha_rm_tables() #include "sp_cache.h" // sp_invalidate_cache() #include "trigger_loader.h" // Trigger_loader #include "table_trigger_dispatcher.h" // Table_trigger_dispatcher #include "binlog.h" #include "sp_head.h" // sp_name #include "mysql/psi/mysql_sp.h" /////////////////////////////////////////////////////////////////////////// /** Create or drop trigger for table. @param thd current thread context (including trigger definition in LEX) @param tables table list containing one table for which trigger is created. @param create whenever we create (TRUE) or drop (FALSE) trigger @note This function is mainly responsible for opening and locking of table and invalidation of all its instances in table cache after trigger creation. Real work on trigger creation/dropping is done inside Table_trigger_dispatcher methods. @todo TODO: We should check if user has TRIGGER privilege for table here. Now we just require SUPER privilege for creating/dropping because we don't have proper privilege checking for triggers in place yet. @retval FALSE Success @retval TRUE error */ bool mysql_create_or_drop_trigger(THD *thd, TABLE_LIST *tables, bool create) { /* FIXME: The code below takes too many different paths depending on the 'create' flag, so that the justification for a single function 'mysql_create_or_drop_trigger', compared to two separate functions 'mysql_create_trigger' and 'mysql_drop_trigger' is not apparent. This is a good candidate for a minor refactoring. */ TABLE *table; bool result= TRUE; String stmt_query; bool lock_upgrade_done= FALSE; MDL_ticket *mdl_ticket= NULL; Query_tables_list backup; DBUG_ENTER("mysql_create_or_drop_trigger"); /* Charset of the buffer for statement must be system one. */ stmt_query.set_charset(system_charset_info); /* QQ: This function could be merged in mysql_alter_table() function But do we want this ? */ /* Note that once we will have check for TRIGGER privilege in place we won't need second part of condition below, since check_access() function also checks that db is specified. */ if (!thd->lex->spname->m_db.length || (create && !tables->db_length)) { my_error(ER_NO_DB_ERROR, MYF(0)); DBUG_RETURN(TRUE); } /* We don't allow creating triggers on tables in the 'mysql' schema */ if (create && !my_strcasecmp(system_charset_info, "mysql", tables->db)) { my_error(ER_NO_TRIGGERS_ON_SYSTEM_SCHEMA, MYF(0)); DBUG_RETURN(TRUE); } /* There is no DETERMINISTIC clause for triggers, so can't check it. But a trigger can in theory be used to do nasty things (if it supported DROP for example) so we do the check for privileges. For now there is already a stronger test right above; but when this stronger test will be removed, the test below will hold. Because triggers have the same nature as functions regarding binlogging: their body is implicitly binlogged, so they share the same danger, so trust_function_creators applies to them too. */ if (!trust_function_creators && mysql_bin_log.is_open() && !(thd->security_context()->check_access(SUPER_ACL))) { my_error(ER_BINLOG_CREATE_ROUTINE_NEED_SUPER, MYF(0)); DBUG_RETURN(TRUE); } if (!create) { bool if_exists= thd->lex->drop_if_exists; /* Protect the query table list from the temporary and potentially 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 (check_readonly(thd, true)) goto end; if (add_table_for_trigger(thd, thd->lex->spname->m_db, thd->lex->spname->m_name, if_exists, & tables)) goto end; if (!tables) { assert(if_exists); /* Since the trigger does not exist, there is no associated table, and therefore : - no TRIGGER privileges to check, - no trigger to drop, - no table to lock/modify, so the drop statement is successful. */ result= FALSE; /* Still, we need to log the query ... */ stmt_query.append(thd->query().str, thd->query().length); goto end; } } /* Check that the user has TRIGGER privilege on the subject table. */ { bool err_status; 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, FALSE, 1, FALSE); thd->lex->query_tables_own_last= save_query_tables_own_last; if (err_status) goto end; } /* We should have only one table in table list. */ assert(tables->next_global == 0); /* We do not allow creation of triggers on temporary tables. */ if (create && find_temporary_table(thd, tables)) { my_error(ER_TRG_ON_VIEW_OR_TEMP_TABLE, MYF(0), tables->alias); goto end; } /* 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 the subject 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); if (thd->locked_tables_mode) { /* 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 { 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; table->pos_in_table_list= tables; /* 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) { my_error(ER_TRG_DOES_NOT_EXIST, MYF(0)); goto end; } if (!(table->triggers= Table_trigger_dispatcher::create(table))) goto end; } if (create) { result= table->triggers->create_trigger(thd, &stmt_query); } else { bool trigger_found; result= table->triggers->drop_trigger(thd, thd->lex->spname->m_name, &trigger_found); if (!result && trigger_found) result= stmt_query.append(thd->query().str, thd->query().length); } if (result) goto end; close_all_tables_for_name(thd, table->s, false, NULL); /* 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); /* Invalidate SP-cache. That's needed because triggers may change list of pre-locking tables. */ sp_cache_invalidate(); end: if (!result) { if (tables) thd->add_to_binlog_accessed_dbs(tables->db); result= write_bin_log(thd, TRUE, stmt_query.ptr(), stmt_query.length()); } /* 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_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 (!result) { #ifdef HAVE_PSI_SP_INTERFACE /* Drop statistics for this stored program from performance schema. */ MYSQL_DROP_SP(SP_TYPE_TRIGGER, thd->lex->spname->m_db.str, thd->lex->spname->m_db.length, thd->lex->spname->m_name.str, thd->lex->spname->m_name.length); #endif my_ok(thd); } DBUG_RETURN(result); } /** Find trigger's table from trigger identifier and add it to the statement table list. @param[in] thd Thread context. @param[in] trg_name Trigger name. @param[in] if_exists TRUE if SQL statement contains "IF EXISTS" clause. That means a warning instead of error should be thrown if trigger with given name does not exist. @param[out] table Pointer to TABLE_LIST object for the table trigger. @return Operation status @retval FALSE On success. @retval TRUE Otherwise. */ bool add_table_for_trigger(THD *thd, const LEX_CSTRING &db_name, const LEX_STRING &trigger_name, bool continue_if_not_exist, TABLE_LIST **table) { LEX *lex= thd->lex; char trn_path_buff[FN_REFLEN]; LEX_STRING tbl_name= { NULL, 0 }; DBUG_ENTER("add_table_for_trigger"); LEX_STRING trn_path= Trigger_loader::build_trn_path(trn_path_buff, FN_REFLEN, db_name.str, trigger_name.str); if (Trigger_loader::check_trn_exists(trn_path)) { if (continue_if_not_exist) { push_warning(thd, Sql_condition::SL_NOTE, ER_TRG_DOES_NOT_EXIST, ER(ER_TRG_DOES_NOT_EXIST)); *table= NULL; DBUG_RETURN(FALSE); } my_error(ER_TRG_DOES_NOT_EXIST, MYF(0)); DBUG_RETURN(TRUE); } if (Trigger_loader::load_trn_file(thd, trigger_name, trn_path, &tbl_name)) DBUG_RETURN(TRUE); *table= sp_add_to_query_tables(thd, lex, db_name.str, tbl_name.str); DBUG_RETURN(*table ? FALSE : TRUE); } /** Update .TRG and .TRN files after renaming triggers' subject table. @param[i]] thd Thread context @param[in] db_name Current database of subject table @param[in] table_alias Current alias of subject table @param[in] table_name Current name of subject table @param[in] new_db_name New database for subject table @param[in] new_table_name New name of subject table @note This method tries to leave trigger related files in consistent state, i.e. it either will complete successfully, or will fail leaving files in their initial state. @note This method assumes that subject table is not renamed to itself. @note This method needs to be called under an exclusive table metadata lock. @return Operation status. @retval false Success @retval true Failure */ bool change_trigger_table_name(THD *thd, const char *db_name, const char *table_alias, const char *table_name, const char *new_db_name, const char *new_table_name) { // Check if there is at least one trigger for the given table. if (!Trigger_loader::trg_file_exists(db_name, table_name)) return false; /* Since triggers should be in the same schema as their subject tables moving table with them between two schemas raises too many questions. (E.g. what should happen if in new schema we already have trigger with same name ?). In case of "ALTER DATABASE `#mysql50#db1` UPGRADE DATA DIRECTORY NAME" we will be given table name with "#mysql50#" prefix To remove this prefix we use check_n_cut_mysql50_prefix(). */ bool upgrading50to51= false; if (my_strcasecmp(table_alias_charset, db_name, new_db_name)) { char dbname[NAME_LEN + 1]; if (check_n_cut_mysql50_prefix(db_name, dbname, sizeof(dbname)) && !my_strcasecmp(table_alias_charset, dbname, new_db_name)) { upgrading50to51= true; } else { my_error(ER_TRG_IN_WRONG_SCHEMA, MYF(0)); return true; } } /* This method interfaces the mysql server code protected by an exclusive metadata lock. */ assert(thd->mdl_context.owns_equal_or_stronger_lock(MDL_key::TABLE, db_name, table_name, MDL_EXCLUSIVE)); assert(my_strcasecmp(table_alias_charset, db_name, new_db_name) || my_strcasecmp(table_alias_charset, table_alias, new_table_name)); Table_trigger_dispatcher d(db_name, table_name); return d.check_n_load(thd, true) || d.check_for_broken_triggers() || d.rename_subject_table(thd, db_name, new_db_name, table_alias, new_table_name, upgrading50to51); } /** Drop all triggers for table. @param thd current thread context @param db_name name of the table schema @param table_name table name @return Operation status. @retval false Success @retval true Failure */ bool drop_all_triggers(THD *thd, const char *db_name, const char *table_name) { // Check if there is at least one trigger for the given table. if (!Trigger_loader::trg_file_exists(db_name, table_name)) return false; /* Here we have to 1) load trigger definitions from TRG-files and 2) parse them to find out trigger names. Since trigger names are not stored in the TRG-file, it is impossible to avoid parsing just to delete triggers. */ Table_trigger_dispatcher d(db_name, table_name); return d.check_n_load(thd, true) || Trigger_loader::drop_all_triggers(db_name, table_name, &d.get_trigger_list()); }