/* Copyright (c) 2016, 2024, 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 designed to work 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 either included with the program or referenced in the documentation. 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 "sql/dd/dd_trigger.h" #include #include #include "m_string.h" #include "my_alloc.h" #include "my_dbug.h" #include "my_inttypes.h" #include "my_sys.h" // my_error, resolve_collation #include "mysqld_error.h" // ER_UNKNOWN_COLLATION #include "sql/dd/cache/dictionary_client.h" // dd::cache::Dictionary_client #include "sql/dd/string_type.h" #include "sql/dd/types/table.h" // dd::Table #include "sql/dd/types/trigger.h" // dd::Trigger #include "sql/dd_table_share.h" // dd_get_mysql_charset #include "sql/item_create.h" #include "sql/key.h" #include "sql/sql_class.h" // THD #include "sql/sql_list.h" // List #include "sql/sql_servers.h" #include "sql/strfunc.h" #include "sql/system_variables.h" #include "sql/table.h" #include "sql/trigger.h" // Trigger struct CHARSET_INFO; namespace dd { /** Get DD API value of event type for a trigger. @param [in] new_trigger pointer to a Trigger object from sql-layer. @return Value of enumeration dd::Trigger::enum_event_type */ static inline Trigger::enum_event_type get_dd_event_type( const ::Trigger *new_trigger) { switch (new_trigger->get_event()) { case TRG_EVENT_INSERT: return Trigger::enum_event_type::ET_INSERT; case TRG_EVENT_UPDATE: return Trigger::enum_event_type::ET_UPDATE; case TRG_EVENT_DELETE: return Trigger::enum_event_type::ET_DELETE; case TRG_EVENT_MAX: break; } /* Since a trigger event is supplied by parser and parser controls that a value for trigger event can take only values from the set (TRG_EVENT_INSERT, TRG_EVENT_UPDATE, TRG_EVENT_DELETE), it's allowable to add an assert to catch violation of this invariant just in case. */ assert(false); return Trigger::enum_event_type::ET_INSERT; } /** Get DD API value of action timing for a trigger. @param [in] new_trigger pointer to a Trigger object from sql-layer. @return Value of enumeration Trigger::enum_action_timing */ static inline Trigger::enum_action_timing get_dd_action_timing( const ::Trigger *new_trigger) { switch (new_trigger->get_action_time()) { case TRG_ACTION_BEFORE: return Trigger::enum_action_timing::AT_BEFORE; case TRG_ACTION_AFTER: return Trigger::enum_action_timing::AT_AFTER; case TRG_ACTION_MAX: break; } /* Since a trigger action time is supplied by parser and parser controls that a value for trigger action time can take only values from the set (TRG_ACTION_BEFORE, TRG_ACTION_AFTER), it's allowable to add an assert to catch violation of this invariant just in case. */ assert(false); return Trigger::enum_action_timing::AT_BEFORE; } /** Fill in a dd::Trigger object based on a Trigger object supplied by sql-layer. @param [in] new_trigger Trigger object supplied by sql-layer @param [out] dd_trig_obj dd::Trigger object to fill in @return Operation status @retval true Failure @retval false Success */ static bool fill_in_dd_trigger_object(const ::Trigger *new_trigger, Trigger *dd_trig_obj) { dd_trig_obj->set_name(String_type(new_trigger->get_trigger_name().str, new_trigger->get_trigger_name().length)); dd_trig_obj->set_definer(String_type(new_trigger->get_definer_user().str, new_trigger->get_definer_user().length), String_type(new_trigger->get_definer_host().str, new_trigger->get_definer_host().length)); dd_trig_obj->set_event_type(get_dd_event_type(new_trigger)); dd_trig_obj->set_action_timing(get_dd_action_timing(new_trigger)); dd_trig_obj->set_action_statement(String_type( new_trigger->get_definition().str, new_trigger->get_definition().length)); dd_trig_obj->set_action_statement_utf8( String_type(new_trigger->get_definition_utf8().str, new_trigger->get_definition_utf8().length)); dd_trig_obj->set_sql_mode(new_trigger->get_sql_mode()); const CHARSET_INFO *collation; if (resolve_charset(new_trigger->get_client_cs_name().str, system_charset_info, &collation)) { // resolve_charset will not cause an error to be reported if the // collation was not found, so we must report error here. my_error(ER_UNKNOWN_COLLATION, MYF(0), new_trigger->get_client_cs_name().str); return true; } dd_trig_obj->set_client_collation_id(collation->number); if (resolve_collation(new_trigger->get_connection_cl_name().str, system_charset_info, &collation)) { // resolve_charset will not cause an error to be reported if the // collation was not found, so we must report error here. my_error(ER_UNKNOWN_COLLATION, MYF(0), new_trigger->get_connection_cl_name().str); return true; } dd_trig_obj->set_connection_collation_id(collation->number); if (resolve_collation(new_trigger->get_db_cl_name().str, system_charset_info, &collation)) { // resolve_charset will not cause an error to be reported if the // collation was not found, so we must report error here. my_error(ER_UNKNOWN_COLLATION, MYF(0), new_trigger->get_db_cl_name().str); return true; } dd_trig_obj->set_schema_collation_id(collation->number); return false; } bool create_trigger(THD *thd, const ::Trigger *new_trigger, enum_trigger_order_type ordering_clause, const LEX_CSTRING &referenced_trigger_name) { DBUG_TRACE; cache::Dictionary_client *dd_client = thd->dd_client(); cache::Dictionary_client::Auto_releaser releaser(dd_client); Table *new_table = nullptr; DBUG_EXECUTE_IF("create_trigger_fail", { my_error(ER_LOCK_DEADLOCK, MYF(0)); return true; }); if (dd_client->acquire_for_modification( new_trigger->get_db_name().str, new_trigger->get_subject_table_name().str, &new_table)) { // Error is reported by the dictionary subsystem. return true; } assert(new_table != nullptr); Trigger *dd_trig_obj; if (ordering_clause != TRG_ORDER_NONE) { const Trigger *referenced_trg = new_table->get_trigger(referenced_trigger_name.str); /* Checking for presence of a trigger referenced by FOLLOWS/PRECEDES clauses is done in Trigger_chain::add_trigger() that is called from Table_trigger_dispatcher::create_trigger() before storing trigger in the data dictionary. It means that call of dd::Trigger::get_trigger() for referenced trigger name must return NOT NULL pointer. Therefore it just added assert to check this invariant. */ assert(referenced_trg != nullptr); if (ordering_clause == TRG_ORDER_FOLLOWS) dd_trig_obj = new_table->add_trigger_following( referenced_trg, get_dd_action_timing(new_trigger), get_dd_event_type(new_trigger)); else dd_trig_obj = new_table->add_trigger_preceding( referenced_trg, get_dd_action_timing(new_trigger), get_dd_event_type(new_trigger)); } else dd_trig_obj = new_table->add_trigger(get_dd_action_timing(new_trigger), get_dd_event_type(new_trigger)); if (dd_trig_obj == nullptr) // NOTE: It's expected that an error is reported // by the dd::cache::Dictionary_client::add_trigger. return true; if (fill_in_dd_trigger_object(new_trigger, dd_trig_obj)) return true; return dd_client->update(new_table); } /** Convert event type value from DD presentation to generic SQL presentation. @param [in] event_type Event type value from the Data Dictionary @return Event type value as it's presented in generic SQL-layer */ static enum_trigger_event_type convert_event_type_from_dd( dd::Trigger::enum_event_type event_type) { switch (event_type) { case dd::Trigger::enum_event_type::ET_INSERT: return TRG_EVENT_INSERT; case dd::Trigger::enum_event_type::ET_UPDATE: return TRG_EVENT_UPDATE; case dd::Trigger::enum_event_type::ET_DELETE: return TRG_EVENT_DELETE; }; assert(false); return TRG_EVENT_MAX; } /** Convert action timing value from DD presentation to generic SQL presentation. @param [in] action_timing Action timing value from the Data Dictionary @return Action timing value as it's presented in generic SQL-layer */ static enum_trigger_action_time_type convert_action_time_from_dd( dd::Trigger::enum_action_timing action_timing) { switch (action_timing) { case dd::Trigger::enum_action_timing::AT_BEFORE: return TRG_ACTION_BEFORE; case dd::Trigger::enum_action_timing::AT_AFTER: return TRG_ACTION_AFTER; } assert(false); return TRG_ACTION_MAX; } bool load_triggers(THD *thd, MEM_ROOT *mem_root, const char *schema_name, const char *table_name, const dd::Table &table, List<::Trigger> *triggers) { DBUG_TRACE; for (const auto &trigger : table.triggers()) { LEX_CSTRING db_name_str = {schema_name, strlen(schema_name)}; LEX_CSTRING subject_table_name = {table_name, strlen(table_name)}; LEX_CSTRING definition, definition_utf8; if (lex_string_strmake(mem_root, &definition, trigger->action_statement().c_str(), trigger->action_statement().length())) return true; if (lex_string_strmake(mem_root, &definition_utf8, trigger->action_statement_utf8().c_str(), trigger->action_statement_utf8().length())) return true; LEX_CSTRING definer_user; if (lex_string_strmake(mem_root, &definer_user, trigger->definer_user().c_str(), trigger->definer_user().length())) return true; LEX_CSTRING definer_host; if (lex_string_strmake(mem_root, &definer_host, trigger->definer_host().c_str(), trigger->definer_host().length())) return true; const CHARSET_INFO *client_cs = dd_get_mysql_charset(trigger->client_collation_id()); if (client_cs == nullptr) client_cs = thd->variables.character_set_client; const CHARSET_INFO *connection_cs = dd_get_mysql_charset(trigger->connection_collation_id()); if (connection_cs == nullptr) connection_cs = thd->variables.collation_connection; const CHARSET_INFO *schema_cs = dd_get_mysql_charset(trigger->schema_collation_id()); if (schema_cs == nullptr) schema_cs = thd->variables.collation_database; LEX_CSTRING client_cs_name, connection_cl_name, db_cl_name, trigger_name; const char *csname = client_cs->csname; if (lex_string_strmake(mem_root, &client_cs_name, csname, strlen(csname)) || lex_string_strmake(mem_root, &connection_cl_name, connection_cs->m_coll_name, strlen(connection_cs->m_coll_name)) || lex_string_strmake(mem_root, &db_cl_name, schema_cs->m_coll_name, strlen(schema_cs->m_coll_name)) || lex_string_strmake(mem_root, &trigger_name, trigger->name().c_str(), trigger->name().length())) return true; ::Trigger *trigger_to_add = ::Trigger::create_from_dd( mem_root, trigger_name, db_name_str, subject_table_name, definition, definition_utf8, trigger->sql_mode(), definer_user, definer_host, client_cs_name, connection_cl_name, db_cl_name, convert_event_type_from_dd(trigger->event_type()), convert_action_time_from_dd(trigger->action_timing()), trigger->action_order(), trigger->created()); if (trigger_to_add == nullptr) return true; if (triggers->push_back(trigger_to_add, mem_root)) { ::destroy_at(trigger_to_add); return true; } } return false; } // Only used by NDB bool table_has_triggers(THD *thd, const char *schema_name, const char *table_name, bool *table_has_trigger) { DBUG_TRACE; cache::Dictionary_client::Auto_releaser releaser(thd->dd_client()); const Table *table = nullptr; if (thd->dd_client()->acquire(schema_name, table_name, &table)) { // Error is reported by the dictionary subsystem. return true; } *table_has_trigger = (table != nullptr && table->has_trigger()); return false; } } // namespace dd